1
1
mirror of https://github.com/jxnblk/mdx-deck.git synced 2024-11-29 13:58:02 +03:00

Merge pull request #270 from jxnblk/v2-monorepo

V2
This commit is contained in:
Brent Jackson 2019-03-10 15:07:44 -04:00 committed by GitHub
commit fff120b38e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
172 changed files with 13007 additions and 15373 deletions

View File

@ -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
View File

@ -1,5 +1,5 @@
dist dist
site public
coverage coverage
node_modules node_modules
yarn.lock package-lock.json

View File

@ -1,11 +0,0 @@
src
site
docs
coverage
test
.babelrc
.travis.yml
CHANGELOG.md
CONTRIBUTING.md
templates
create-deck

1
.npmrc
View File

@ -1 +0,0 @@
package-lock=true

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5"
}

View File

@ -1,14 +1,12 @@
language: node_js language: node_js
node_js: node_js:
- 10 - 10
before_deploy: beforeDeploy:
- npm install - yarn build
- npm run build
- cp docs/card.png site
deploy: deploy:
provider: pages provider: pages
skip_cleanup: true skip_cleanup: true
github_token: $GH_TOKEN github_token: $GH_TOKEN
local_dir: site local_dir: docs/dist
on: on:
branch: master branch: master

View File

@ -1,8 +1,18 @@
# Changelog # Changelog
## Unreleased ## 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 ## v1.10.2 2019-03-10
- Fix bad release - Fix bad release

View File

@ -1,4 +1,3 @@
# Contributing # Contributing
Thanks for contributing! Thanks for contributing!
@ -27,7 +26,6 @@ Run `npm test`
- Watch Mode: `npm test -- --watch` - Watch Mode: `npm test -- --watch`
- Coverage: `npm test -- --coverage` - Coverage: `npm test -- --coverage`
--- ---
# Contributor Covenant Code of Conduct # Contributor Covenant Code of Conduct
@ -87,7 +85,7 @@ further defined and clarified by project maintainers.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be 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 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 is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident. 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 available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org [homepage]: https://www.contributor-covenant.org

134
README.md
View File

@ -1,4 +1,4 @@
# mdx-deck # MDX Deck
![](https://s3.amazonaws.com/jxnblk/mdx-deck.gif) ![](https://s3.amazonaws.com/jxnblk/mdx-deck.gif)
@ -15,7 +15,7 @@
[npm]: https://npmjs.com/package/mdx-deck [npm]: https://npmjs.com/package/mdx-deck
```sh ```sh
npm i -D mdx-deck npm i -D mdx-deck@next
``` ```
- :memo: Write presentations in markdown - :memo: Write presentations in markdown
@ -33,21 +33,27 @@ Create an [MDX][] file and separate each slide with `---`.
````mdx ````mdx
# This is the title of my deck # This is the title of my deck
--- ---
# About Me # About Me
--- ---
```jsx ```jsx
<CodeSnippet /> <CodeSnippet />
``` ```
--- ---
import Demo from './components/Demo' import Demo from './components/Demo'
<Demo /> ## <Demo />
---
# The end # 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: pointing to the `.mdx` file to start the dev server:
```json ```json
@ -71,14 +77,14 @@ npm start
- [Build a Custom Provider Component for MDX-Deck](ks-egghead) by [Kyle Shevlin][] - [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 [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-video]: http://youtu.be/d2sQiI5NFAM?a
[kcd-medium]: https://blog.kentcdodds.com/mdx-deck-slide-decks-powered-by-markdown-and-react-bfc6d6af20da [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-video]: https://www.youtube.com/watch?v=LvP2EqCiQMg&feature=youtu.be
[hw-demo]: https://github.com/hswolff/mdx-deck-demo [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 [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 ## Quick Start
@ -94,21 +100,19 @@ MDX can use Markdown syntax and render React components with JSX.
### Imports ### 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 ```mdx
import { Box } from 'grid-styled' import { Box } from 'grid-styled'
<Box color='tomato'> <Box color="tomato">Hello</Box>
Hello
</Box>
``` ```
Read more about MDX syntax in the [MDX Docs][MDX]. Read more about MDX syntax in the [MDX Docs][mdx].
## Theming ## 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 ### 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' /> <img src='docs/images/yellow.png' width='256' />
</div> </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. Export `theme` from your MDX file to enable a theme.
```mdx ```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. 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 ```js
// example theme.js // example theme.js
import theme from 'mdx-deck/themes'
export default { export default {
// extends the default theme
...theme,
// add a custom font // add a custom font
font: 'Roboto, sans-serif', font: 'Roboto, sans-serif',
// custom colors // custom colors
@ -157,7 +156,7 @@ export default {
text: '#f0f', text: '#f0f',
background: 'black', background: 'black',
link: '#0ff', link: '#0ff',
} },
} }
``` ```
@ -165,18 +164,20 @@ Read more about theming in the [Theming docs](docs/theming.md)
### Components ### 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). Read more in the [components docs](docs/components.md).
### Libraries ### 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. - [CodeSurfer][]: React component for scrolling, zooming and highlighting code.
- [mdx-code][]: Runnable code playgrounds for MDX Deck. - [mdx-code][]: Runnable code playgrounds for MDX Deck.
- [mdx-deck-live-code][]: Live React and JS coding in slides. - [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 [codesurfer]: https://github.com/pomber/code-surfer
[mdx-code]: https://github.com/pranaygp/mdx-code [mdx-code]: https://github.com/pranaygp/mdx-code
[mdx-deck-live-code]: https://github.com/JReinhold/mdx-deck-live-code [mdx-deck-live-code]: https://github.com/JReinhold/mdx-deck-live-code
@ -195,8 +196,9 @@ export default ({ children }) => (
style={{ style={{
width: '100vw', width: '100vw',
height: '100vw', height: '100vw',
backgroundColor: 'tomato' backgroundColor: 'tomato',
}}> }}
>
{children} {children}
</div> </div>
) )
@ -208,6 +210,7 @@ import Layout from './Layout'
# No Layout # No Layout
--- ---
export default Layout export default Layout
# Custom 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 ### 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 ## 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) ![presenter mode screenshot](docs/images/presenter-mode.png)
To use presenter mode: 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) 1. Open your presentation and enter _Presenter Mode_
- In your window, press the `Option/Alt + P` keys (or add `?mode=presenter` to the URL) to enter presenter mode. 2. Click on the link in the bottom to open the presentation in another tab
- Display the other window on the screen for the audience to see. 3. Move the other window to 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 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 ### Speaker Notes
Notes that only show in presenter mode can be added to any slide. 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: Speaker notes can be added using the `<Notes />` component.
**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.
```mdx ```mdx
import { Notes } from 'mdx-deck' import { Notes } from 'mdx-deck'
# Slide Content # Slide Content
<Notes> <Notes>Only visible in presenter mode</Notes>
Only visible in presenter mode
</Notes>
``` ```
## Overview Mode ## Overview Mode
![Overview Mode](docs/images/overview-mode.png) ![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. This shows a list of all slides on the left and a preview of the current slide on the right.
## Keyboard Shortcuts ## Keyboard Shortcuts
Key | Description | Key | Description |
----------- | ----------- | ----------- | -------------------------------------------- |
Left Arrow | Go to previous slide (or step in [Appear][]) | Left Arrow | Go to previous slide (or step in [Appear][]) |
Right Arrow | Go to next slide (or step in [Appear][]) | Right Arrow | Go to next slide (or step in [Appear][]) |
Space | 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 | Option + P | Toggle [Presenter Mode](#presenter-mode) |
Down Arrow | Show next step in [Appear][] component without navigating slides | Option + O | Toggle [Overview Mode](#overview-mode) |
Option + P | Toggle [Presenter Mode](#presenter-mode)
Option + O | Toggle [Overview Mode](#overview-mode)
Option + G | Toggle grid view 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 ## Exporting
@ -298,15 +290,10 @@ See more exporting options in the [Exporting Documentation](docs/exporting.md)
``` ```
-p --port Dev server port -p --port Dev server port
--hot-port Dev server hot reload port
-h --host Host the dev server listens to -h --host Host the dev server listens to
--no-open Prevent from opening in default browser --no-open Prevent from opening in default browser
-d --out-dir Output directory for exporting -d --out-dir Output directory for exporting
--no-html Disable static HTML rendering --webpack Path to custom webpack config file
--out-file Filename for screenshot or PDF export
--width Width in pixels
--height Height in pixels
--webpack Path to webpack config file
``` ```
## Docs ## Docs
@ -317,7 +304,6 @@ See more exporting options in the [Exporting Documentation](docs/exporting.md)
- [Components](docs/components.md) - [Components](docs/components.md)
- [Exporting](docs/exporting.md) - [Exporting](docs/exporting.md)
- [Advanced Usage](docs/advanced.md) - [Advanced Usage](docs/advanced.md)
- [React API](docs/react.md)
## Examples ## Examples
@ -332,22 +318,14 @@ See more exporting options in the [Exporting Documentation](docs/exporting.md)
### Related ### Related
- [MDX][] - [MDX][]
- [mdx-go][] - [emotion][]
- [ok-mdx][]
- [Compositor x0][]
- [styled-components][]
- [styled-system][]
- [Spectacle][] - [Spectacle][]
[MIT License](LICENSE.md) [MIT License](LICENSE.md)
[MDX]: https://github.com/mdx-js/mdx [mdx]: https://mdxjs.com/
[ok-mdx]: https://github.com/jxnblk/ok-mdx [spectacle]: https://github.com/FormidableLabs/spectacle
[Compositor x0]: https://github.com/c8r/x0 [emotion]: https://emotion.sh
[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
<!-- examples --> <!-- examples -->

1
babel.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = require('./packages/mdx-deck/babel.config')

202
cli.js
View File

@ -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
}

View File

@ -1,56 +1,62 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from '@emotion/styled'
import { space, color } from 'styled-system' import { space, color } from 'styled-system'
const Root = styled.div([], { const Root = styled.div({
display: 'flex', display: 'flex',
alignItems: 'center' alignItems: 'center',
}) })
const Button = styled.button([], { const Button = styled.button(
appearance: 'none', {
fontFamily: 'inherit', appearance: 'none',
fontSize: 'inherit', fontFamily: 'inherit',
fontWeight: 'bold', fontSize: 'inherit',
borderRadius: '4px', fontWeight: 'bold',
border: 'none', borderRadius: '4px',
width: '2em', border: 'none',
'&:focus': { width: '2em',
outline: 'none', '&:focus': {
boxShadow: '0 0 0 2px magenta' outline: 'none',
} boxShadow: '0 0 0 2px magenta',
}, space, color) },
},
space,
color
)
Button.defaultProps = { Button.defaultProps = {
m: 0, m: 0,
px: 3, px: 3,
py: 2, py: 2,
color: 'background', color: 'background',
bg: 'text' bg: 'text',
} }
const Samp = styled.samp([], { const Samp = styled.samp(space)
}, space)
export default class Counter extends React.Component { export default class Counter extends React.Component {
state = { state = {
count: 0 count: 0,
} }
inc = () => { inc = () => {
this.setState(state => ({count: state.count + 1})) this.setState(state => ({ count: state.count + 1 }))
} }
dec = () => { dec = () => {
this.setState(state => ({count: state.count - 1})) this.setState(state => ({ count: state.count - 1 }))
} }
render() { render() {
return ( return (
<Root> <Root>
<Button ml='auto' onClick={this.dec}>-</Button> <Button ml="auto" onClick={this.dec}>
-
</Button>
<Samp mx={3}>{this.state.count}</Samp> <Samp mx={3}>{this.state.count}</Samp>
<Button mr='auto' onClick={this.inc}>+</Button> <Button mr="auto" onClick={this.inc}>
+
</Button>
</Root> </Root>
) )
} }

View File

@ -1,40 +1,35 @@
# Advanced Usage # Advanced Usage
## Custom MDX components ## 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 ```js
// example theme // example theme
import { theme } from 'mdx-deck/themes'
import Heading from './Heading' import Heading from './Heading'
export default { export default {
...theme,
components: { components: {
h1: Heading h1: Heading,
} },
} }
``` ```
See the [MDX][] docs for more or take a look 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 ## Custom Provider component
A custom Provider component can be added to the theme to wrap the entire application. 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 ```js
// example theme.js // example theme.js
import { theme } from 'mdx-deck/themes'
import Provider from './Provider' import Provider from './Provider'
export default { export default {
...theme,
font: 'Georgia, serif', 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 #### Props
- `slides`: (array) the components for each slide
- `index`: (number) the current slide index - `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'`)
- `mode`: (string) the current mode (one of `'NORMAL'`, `'PRESENTER'`, or `'OVERVIEW'`) - `step`: (number) the current visible step in an Appear or Step component
- `notes`: (object) custom [speaker notes](#speaker-notes) for all slides - Each slide includes a `meta` object with a `notes` field when the Notes component is used within a slide
- `step`: (number) the current visible step in an Appear component
## Combining multiple mdx files ## Combining multiple mdx files
Unlike the official `@mdx-js/loader`, 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. 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 ```mdx
# one.mdx # one.mdx
@ -78,16 +72,13 @@ Next, create a `.js` file to import and combine the two `.mdx` files.
```js ```js
// deck.js // deck.js
import one from './one.mdx' import { slides as one } from './one.mdx'
import two from './two.mdx' import { slides as two } from './two.mdx'
export default [ export const slides = [...one, ...two]
...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 ```json
"scripts": { "scripts": {
@ -97,7 +88,9 @@ Then, point the mdx-deck CLI comment in your `package.json` to the `deck.js` fil
## Custom webpack config ## 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 ```js
// webpack.config.js example // webpack.config.js example
@ -106,26 +99,23 @@ module.exports = {
rules: [ rules: [
{ {
test: /\.svg$/, test: /\.svg$/,
use: [ use: [{ loader: 'babel-loader' }, { loader: 'react-svg-loader' }],
{ 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 ## 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][]. 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 [mdx]: https://mdxjs.com
[MDXProvider]: https://github.com/mdx-js/mdx/blob/master/docs/getting-started/index.md#mdxprovider [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 [create-react-app]: https://github.com/facebook/create-react-app
[next.js]: https://github.com/zeit/next.js/ [next.js]: https://github.com/zeit/next.js/

View File

@ -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 }

View File

@ -1,7 +1,6 @@
# Components # 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 ## Head
@ -13,11 +12,11 @@ import { Head } from 'mdx-deck'
<Head> <Head>
<title>My Presentation</title> <title>My Presentation</title>
<meta name='twitter:card' content='summary_large_image' /> <meta name="twitter:card" content="summary_large_image" />
<meta name='twitter:site' content='@jxnblk' /> <meta name="twitter:site" content="@jxnblk" />
<meta name='twitter:title' content='My Presentation' /> <meta name="twitter:title" content="My Presentation" />
<meta name='twitter:description' content='A really great presentation' /> <meta name="twitter:description" content="A really great presentation" />
<meta name='twitter:image' content='https://example.com/card.png' /> <meta name="twitter:image" content="https://example.com/card.png" />
</Head> </Head>
``` ```
@ -28,10 +27,11 @@ Use the `<Image />` component to render a fullscreen image (using the CSS `backg
```mdx ```mdx
import { Image } from 'mdx-deck' import { Image } from 'mdx-deck'
<Image src='kitten.png' /> <Image src="kitten.png" />
``` ```
### Props ### Props
- `src` (string) image URL - `src` (string) image URL
- `size` (string) CSS background-size - `size` (string) CSS background-size
@ -52,31 +52,23 @@ import { Appear } from 'mdx-deck'
</ul> </ul>
``` ```
Internally, the `<Appear />` component uses the `<Step />` component, which can be used to build custom components with similar behavior.
## Speaker Notes ## 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. Speaker notes that only show in presenter mode can be added to any slide with the Notes component.
````mdx
# Markdown speaker notes
```notes
These are only visible in presenter mode
```
````
```mdx ```mdx
import { Notes } from 'mdx-deck' import { Notes } from 'mdx-deck'
# Slide Content # Slide Content
<Notes> <Notes>Only visible in presenter mode</Notes>
Only visible in presenter mode
</Notes>
``` ```
## Layouts ## 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. Export a layout as the `default` within a slide to wrap the contents.
### Invert ### Invert
@ -123,6 +115,10 @@ export default SplitRight
## Meow ## Meow
``` ```
### Horizontal
Similar to the Split components, but renders all children side-by-side
### FullScreenCode ### FullScreenCode
Render fenced code blocks fullscreen. Render fenced code blocks fullscreen.
@ -136,4 +132,3 @@ export default FullScreenCode
<Button>Beep</Button> <Button>Beep</Button>
``` ```
```` ````

View File

@ -1,8 +1,7 @@
export { future as theme } from '../themes' export { future as theme } from '@mdx-deck/themes'
import { Head, Image, Notes, Appear } from '../dist' import { Head, Image, Notes, Appear } from '@mdx-deck/components'
import { Invert, Split, FullScreenCode, Horizontal} from '../layouts' import { Invert, Split, SplitRight, FullScreenCode, Horizontal} from '@mdx-deck/layouts'
import Counter from './Counter' import Counter from './Counter'
import code from './code'
<Head> <Head>
<title>mdx-deck</title> <title>mdx-deck</title>
@ -13,23 +12,30 @@ import code from './code'
<meta name='twitter:image' content='https://jxnblk.com/mdx-deck/card.png' /> <meta name='twitter:image' content='https://jxnblk.com/mdx-deck/card.png' />
</Head> </Head>
# mdx-deck # MDX Deck
MDX-based presention decks MDX-based presention decks
--- ---
# Presentation decks # Presentation decks
--- ---
# Built with [MDX][] # Built with [MDX][]
[MDX]: https://github.com/mdx-js/mdx [MDX]: https://github.com/mdx-js/mdx
--- ---
import Box from 'superbox' import { Box } from '@rebass/emotion'
<Box <Box
fontSize={3} fontSize={[ 6, 7 ]}
p={4} p={4}
color='navy'
bg='magenta'> bg='magenta'>
Import React components Import React components
</Box> </Box>
--- ---
- Make bulleted lists - Make bulleted lists
@ -49,10 +55,11 @@ import Box from 'superbox'
<button>code example</button> <button>code example</button>
``` ```
```notes <Notes>
- These are speaker notes These are speaker notes
- And they won't be rendered in your slide - And they won't be rendered in your slide
``` </Notes>
--- ---
```jsx ```jsx
@ -66,7 +73,9 @@ class extends React.Component {
} }
} }
``` ```
--- ---
> “Blockquotes are essential to any good presentation” > “Blockquotes are essential to any good presentation”
Anonymous Anonymous
@ -79,6 +88,7 @@ class extends React.Component {
</Notes> </Notes>
--- ---
### Appear ### Appear
<ul> <ul>
@ -89,6 +99,7 @@ class extends React.Component {
<li>Four</li> <li>Four</li>
</Appear> </Appear>
</ul> </ul>
--- ---
<Image <Image
@ -96,9 +107,10 @@ class extends React.Component {
size='contain' size='contain'
/> />
```notes <Notes>
Testing object fit Testing object fit
``` </Notes>
--- ---
### Real React Components ### Real React Components
@ -110,6 +122,7 @@ Testing object fit
<Image src='https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20' /> <Image src='https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20' />
--- ---
export default Split export default Split
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20) ![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20)
@ -117,6 +130,17 @@ export default Split
## Split Layout ## Split Layout
--- ---
export default SplitRight
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20)
## Split Layout
(To the right)
---
export default Horizontal export default Horizontal
![](https://source.unsplash.com/random/1024x768') ![](https://source.unsplash.com/random/1024x768')
@ -157,4 +181,6 @@ Prop | Type | Description
export default Invert export default Invert
# Get started :sunglasses: # Get started :sunglasses:
[GitHub](https://github.com/jxnblk/mdx-deck) [GitHub](https://github.com/jxnblk/mdx-deck)

View File

@ -1,4 +1,3 @@
# Exporting # Exporting
## Static Bundle ## 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. To export a deck as PDF or create a PNG screenshot, install the export CLI package:
This works well as a backup option for any unforeseen technical difficulties.
```json ```sh
"script": { npm i @mdx-deck/export
"pdf": "mdx-deck pdf deck.mdx"
}
``` ```
## Screenshots Then run the following command to create a PDF:
A PNG image of the first slide can be exported with the `screenshot` command. ```sh
This is useful for creating open graph images for Twitter, Facebook, or Slack. mdx-deck-export pdf deck.mdx
```
```json Or export the first slide as a PNG:
"script": {
"screenshot": "mdx-deck screenshot deck.mdx" ```sh
} mdx-deck-export png deck.mdx
``` ```
### OG Image ### 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' import { Head } from 'mdx-deck'
<Head> <Head>
<meta name='og:image' content='https://example.com/card.png' /> <meta name="og:image" content="https://example.com/card.png" />
</Head> </Head>
``` ```

22
docs/package.json Normal file
View 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"
}
}

View File

@ -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

View File

@ -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/

View File

@ -1,11 +1,11 @@
# Themes # Themes
![](images/default.png) ![](images/default.png)
Default Default
```mdx ```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 Big
```mdx ```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 Book
```mdx ```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 Code
```mdx ```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 Comic
```mdx ```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 Condensed
```mdx ```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 Dark
```mdx ```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 Future
```mdx ```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 Hack
```mdx ```mdx
export { hack as theme } from 'mdx-deck/themes' export { hack as theme } from 'mdx-deck/themes
'
``` ```
--- ---
@ -91,7 +99,8 @@ Lobster
Notes Notes
```mdx ```mdx
export { notes as theme } from 'mdx-deck/themes' export { notes as theme } from 'mdx-deck/themes
'
``` ```
--- ---
@ -105,7 +114,8 @@ Rye
Script Script
```mdx ```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 Swiss
```mdx ```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 Yellow
```mdx ```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
'
```

View File

@ -1,7 +1,6 @@
# Theming # 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 ## 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. 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 ```js
// Example theme.js // Example theme.js
import theme from 'mdx-deck/themes'
export default { export default {
// extends the default theme
...theme,
// add a custom font // add a custom font
font: 'Roboto, sans-serif', font: 'Roboto, sans-serif',
// custom colors // custom colors
colors: { colors: {
...theme.colors, // include existing theme colors
text: '#f0f', text: '#f0f',
background: 'black', 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 ### Google Fonts
To use webfonts, mdx-deck will attempt to add `<link>` tags for any font from Google Fonts. Themes can specify a `googleFont` field to automatically add a `<link>` tag to the document head.
To load webfonts from other sources, use a custom [Provider component](advanced.md#custom-provider-component) to add custom `<link>` tags. Alternatively, use the `<Head />` component to add a custom `<link>` tag.
### Syntax Highlighting ### Syntax Highlighting
By default fenced code blocks do not include any 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. 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][].
The syntax highlighting is built with [react-syntax-highlighter][] and [PrismJS][].
Create a `theme.js` file and export it via the `MDX` file.
```js MDX Deck includes two themes for adding syntax highlighting with [react-syntax-highlighter][]: `syntaxHighlighter` and `syntaxHighlighterPrism`.
export { default as theme } from './theme'
//...
```
```js Since MDX supports using React components inline, you can also import a syntax highlighting component directly, if you prefer.
// 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'
}
```
### Styling Elements ### 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 ```js
// example theme // example theme
import theme from 'mdx-deck/themes'
export default { export default {
...theme,
h1: { h1: {
textTransform: 'uppercase', textTransform: 'uppercase',
letterSpacing: '0.1em' letterSpacing: '0.1em',
}, },
blockquote: { blockquote: {
fontStyle: 'italic' fontStyle: 'italic',
} },
} }
``` ```
See the [reference](#reference) below for a full list of element keys. See the [reference](#reference) below for a full list of element keys.
## Reference ## Reference
The following keys are available for theming: The following keys are available for theming:
- `font`: base font family - `font`: base font family
- `weights`: array of font weights for the main font
- `monospace`: font family for `<pre>` and `<code>` - `monospace`: font family for `<pre>` and `<code>`
- `fontSizes`: array of font sizes from smallest to largest
- `colors`: object of colors used for MDX components - `colors`: object of colors used for MDX components
- `text`: root foreground color - `text`: root foreground color
- `background`: root background color - `background`: root background color
- `link` - `code`: text color for `<pre>` and `<code>`
- `heading` - `codeBackground`: background color for `<pre>` and `<code>`
- `blockquote`
- `pre`
- `preBackground`
- `code`
- `codeBackground`
- `transitionTimingFunction`: timing function value for slide transitions
- `transitionDuration`: duration value for slide transitions. set to `0` to disable transitions
- `css`: root CSS object - `css`: root CSS object
- `heading`: CSS for all headings - `heading`: CSS for all headings
- `h1`: CSS for `<h1>` - `h1`: CSS for `<h1>`
@ -174,20 +112,25 @@ The following keys are available for theming:
- `h4`: CSS for `<h4>` - `h4`: CSS for `<h4>`
- `h5`: CSS for `<h5>` - `h5`: CSS for `<h5>`
- `h6`: CSS for `<h6>` - `h6`: CSS for `<h6>`
- `paragraph`: CSS for `<p>` - `p`: CSS for `<p>`
- `link`: CSS for `<a>` - `a`: CSS for `<a>`
- `ul`: CSS for `<ul>` - `ul`: CSS for `<ul>`
- `ol`: CSS for `<ol>` - `ol`: CSS for `<ol>`
- `li`: CSS for `<li>` - `li`: CSS for `<li>`
- `img`: CSS for `<img>` - `img`: CSS for `<img>`
- `blockquote`: CSS for `<blockquote>` - `blockquote`: CSS for `<blockquote>`
- `table`: CSS for `<table>` - `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 - `components`: object of MDX components to render markdown
- `Provider`: component for wrapping the entire app - `Provider`: component for wrapping the entire app
- `googleFont`: CSS HREF for adding a Google Font `<link>` tag
## Advanced Usage ## Advanced Usage
For more advanced customizations see the [Advanced Usage](advanced.md) docs. For more advanced customizations see the [Advanced Usage](advanced.md) docs.
[styled-components]: https://github.com/styled-components/styled-components [emotion]: https://emotion.sh
[MDX]: https://github.com/mdx-js/mdx [mdx]: https://github.com/mdx-js/mdx
[react-syntax-highlighter]: https://github.com/conorhastings/react-syntax-highlighter

View File

@ -1 +0,0 @@
module.exports = require('./dist/layouts')

6
lerna.json Normal file
View File

@ -0,0 +1,6 @@
{
"packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "2.0.0-8"
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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' })
})

View File

@ -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
}

View File

@ -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
}

View File

@ -1 +0,0 @@
module.exports = require('./lib/loader')

11282
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,110 +1,29 @@
{ {
"name": "mdx-deck", "private": true,
"version": "1.10.2", "version": "2.0.0-0",
"description": "MDX-based presentation decks", "workspaces": [
"main": "dist/index.js", "packages/*",
"bin": { "templates/*",
"mdx-deck": "./cli.js" "docs"
}, ],
"scripts": { "scripts": {
"prepare": "npm run babel", "start": "yarn workspace @mdx-deck/docs start",
"babel": "babel src -d dist", "build": "yarn workspace @mdx-deck/docs build",
"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",
"test": "jest" "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": { "devDependencies": {
"@babel/cli": "^7.0.0", "@babel/core": "^7.3.4",
"babel-jest": "^24.1.0", "@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"husky": "^1.3.1", "husky": "^1.3.1",
"jest": "^24.1.0", "jest": "^24.3.1",
"jest-styled-components": "^6.3.1", "lerna": "^3.13.1",
"lint-staged": "^7.3.0", "lint-staged": "^8.1.5",
"mdx-deck-code-surfer": "^0.5.5", "prettier": "^1.16.4"
"prettier": "^1.16.4",
"react-test-renderer": "^16.8.2"
}, },
"jest": { "jest": {
"roots": [
"<rootDir>/test/"
],
"testMatch": [
"**/test/**/*.js"
],
"testURL": "http://localhost/",
"coverageReporters": [ "coverageReporters": [
"lcov",
"html" "html"
] ]
}, },
@ -114,15 +33,9 @@
} }
}, },
"lint-staged": { "lint-staged": {
"*.{js,json}": [ "*.{md,js,json}": [
"prettier --write", "prettier --write",
"git add" "git add"
] ]
},
"prettier": {
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"jsxBracketSameLine": true
} }
} }

View File

@ -0,0 +1,5 @@
# @mdx-deck/components
React components use in MDX Deck
https://github.com/jxnblk/mdx-deck

View 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"
}
}

View 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

View 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

View 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

View 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

View File

@ -1,49 +1,28 @@
import React from 'react' import React from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
const noop = () => { export const HeadContext = React.createContext({
console.warn('Missing HeadProvider')
}
export const Context = React.createContext({
tags: [], tags: [],
push: noop push: () => {
console.warn('Missing HeadProvider')
},
}) })
export class HeadProvider extends React.Component { export const HeadProvider = ({ tags = [], children }) => {
static defaultProps = { const push = elements => {
tags: [] tags.push(...elements)
}
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>
)
} }
const context = { push }
return <HeadContext.Provider value={context}>{children}</HeadContext.Provider>
} }
export class Head extends React.Component { export class Head extends React.Component {
state = { state = {
didMount: false didMount: false,
} }
rehydrate = () => { rehydrate = () => {
const children = React.Children.toArray(this.props.children) const children = React.Children.toArray(this.props.children)
const nodes = [ const nodes = [...document.head.querySelectorAll('[data-head]')]
...document.head.querySelectorAll('[data-head]')
]
nodes.forEach(node => { nodes.forEach(node => {
node.remove() node.remove()
}) })
@ -59,27 +38,22 @@ export class Head extends React.Component {
if (meta) meta.remove() if (meta) meta.remove()
} }
}) })
this.setState({ didMount: true })
this.setState({
didMount: true
})
} }
componentDidMount () { componentDidMount() {
this.rehydrate() this.rehydrate()
} }
render () { render() {
const children = React.Children.toArray(this.props.children) const children = React.Children.toArray(this.props.children).map(child =>
.map(child => React.cloneElement(child, { React.cloneElement(child, {
'data-head': true 'data-head': true,
})) })
)
const { didMount } = this.state if (!this.state.didMount) {
if (!didMount) {
return ( return (
<Context.Consumer <HeadContext.Consumer
children={({ push }) => { children={({ push }) => {
push(children) push(children)
return false 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

View 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

View 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

View 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

View 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

View 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,
}}
/>
)

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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()
})

View File

@ -0,0 +1,5 @@
import React from 'react'
import TestRenderer from 'react-test-renderer'
import Clock from '../Clock'
test.todo('Clock renders')

View File

@ -0,0 +1,5 @@
import React from 'react'
import TestRenderer from 'react-test-renderer'
import GoogleFonts from '../GoogleFonts'
test.todo('GoogleFonts renders')

View File

@ -0,0 +1,15 @@
import React from 'react'
import TestRenderer from 'react-test-renderer'
import { Head, HeadProvider } from '../Head'
test.skip('Head populates HeadProviders tag prop', () => {
const tags = []
TestRenderer.create(
<HeadProvider tags={tags}>
<Head>
<title>Hello</title>
</Head>
</HeadProvider>
)
expect(tags.length).toBe(1)
})

View 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()
})

View 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()
})

View File

@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Appear renders 1`] = `
<h1
style={
Object {
"visibility": "hidden",
}
}
>
Hello
</h1>
`;

View 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>
`;

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Steps renders 1`] = `"hi"`;

View 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)

View 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'

View 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

View 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
}

View File

@ -1,4 +1,3 @@
# npm init deck # npm init deck
Create mdx-deck presentations Create mdx-deck presentations

View File

@ -15,28 +15,31 @@ log.error = (...args) => {
const template = 'jxnblk/mdx-deck/templates/basic' const template = 'jxnblk/mdx-deck/templates/basic'
const cli = meow(` const cli = meow(
`
Usage Usage
$ npm init deck my-presentation $ npm init deck my-presentation
$ npx create-deck my-presentation $ npx create-deck my-presentation
`, { `,
booleanDefault: undefined, {
flags: { booleanDefault: undefined,
help: { flags: {
type: 'boolean', help: {
alias: 'h' type: 'boolean',
alias: 'h',
},
version: {
type: 'boolean',
alias: 'v',
},
}, },
version: {
type: 'boolean',
alias: 'v'
}
} }
}) )
const [ name ] = cli.input const [name] = cli.input
if (!name) { if (!name) {
cli.showHelp(0) cli.showHelp(0)

View File

@ -1,6 +1,6 @@
{ {
"name": "create-deck", "name": "create-deck",
"version": "1.0.0", "version": "2.0.0-1",
"description": "Create mdx-deck presentations", "description": "Create mdx-deck presentations",
"bin": { "bin": {
"create-deck": "cli.js" "create-deck": "cli.js"

1
packages/export/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist

28
packages/export/README.md Normal file
View 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
View 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
View 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
}

View 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"
}
}

View 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()],
}
```

View 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

View 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"
}
}

View File

@ -0,0 +1,5 @@
# @mdx-deck/layouts
Layout components used in MDX Deck
https://github.com/jxnblk/mdx-deck

View 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"
}
}

View 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
)

View 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

View File

@ -1,9 +1,7 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from '@emotion/styled'
const FullCode = styled.div({ const FullCode = styled.div({
width: '100vw',
height: '100vh',
textAlign: 'left', textAlign: 'left',
'& pre': { '& pre': {
// needed to override inline styles from syntax highlighting // needed to override inline styles from syntax highlighting

View 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

View File

@ -1,5 +1,4 @@
import React from 'react' import styled from '@emotion/styled'
import styled from 'styled-components'
import { color } from 'styled-system' import { color } from 'styled-system'
const Invert = styled.div( const Invert = styled.div(
@ -7,6 +6,7 @@ const Invert = styled.div(
width: '100vw', width: '100vw',
height: '100vh', height: '100vh',
display: 'flex', display: 'flex',
flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
'& a': { '& a': {
@ -21,4 +21,4 @@ Invert.defaultProps = {
bg: 'text', bg: 'text',
} }
export default props => <Invert {...props} /> export default Invert

View 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

View 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

View 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
View 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)
}

View 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"
}
}

View File

@ -0,0 +1,4 @@
# MDX Deck
https://github.com/jxnblk/mdx-deck

View 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
View 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
View 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

View File

@ -0,0 +1,3 @@
export * from '@mdx-deck/components'
export * as themes from '@mdx-deck/themes'
export * as Layouts from '@mdx-deck/layouts'

View File

@ -0,0 +1 @@
export * from '@mdx-deck/layouts'

View 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

View 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