├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── bin └── react-shower.js ├── config.json ├── css ├── main.css └── theme.css ├── index.html ├── js ├── AppState.js ├── History.js ├── Persistence.js ├── app.js ├── components │ ├── .keep │ ├── App.react.js │ ├── Code.react.js │ ├── Deck.react.js │ └── Slide.react.js ├── constants │ ├── .keep │ ├── AppConstants.js │ └── Shortcuts.js ├── presentation.js └── utils │ └── assign.js ├── makewebpackconfig.js ├── package.json ├── server.dev.js ├── webpack.dev.config.js └── webpack.prod.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | # node_modules are ignored by default 2 | 3 | # Ignore build folder 4 | build 5 | 6 | # Ignore config stuff 7 | server.*.js 8 | *config.js 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "ecmaFeatures": { 9 | "forOf": true, 10 | "jsx": true, 11 | "es6": true 12 | }, 13 | "rules": { 14 | "comma-dangle": 0, 15 | "indent": [2, 2, {"SwitchCase": 1}], 16 | "react/prop-types": 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | 53 | # git config 54 | .gitattributes text 55 | .gitignore text 56 | .gitconfig text 57 | 58 | # code analysis config 59 | .jshintrc text 60 | .jscsrc text 61 | .jshintignore text 62 | .csslintrc text 63 | 64 | # misc config 65 | *.yaml text 66 | *.yml text 67 | .editorconfig text 68 | 69 | # build config 70 | *.npmignore text 71 | *.bowerrc text 72 | 73 | # Heroku 74 | Procfile text 75 | .slugignore text 76 | 77 | # Documentation 78 | *.md text 79 | LICENSE text 80 | AUTHORS text 81 | 82 | 83 | # 84 | ## These files are binary and should be left untouched 85 | # 86 | 87 | # (binary is a macro for -text -diff) 88 | *.png binary 89 | *.jpg binary 90 | *.jpeg binary 91 | *.gif binary 92 | *.ico binary 93 | *.mov binary 94 | *.mp4 binary 95 | *.mp3 binary 96 | *.flv binary 97 | *.fla binary 98 | *.swf binary 99 | *.gz binary 100 | *.zip binary 101 | *.7z binary 102 | *.ttf binary 103 | *.eot binary 104 | *.woff binary 105 | *.pyc binary 106 | *.pdf binary 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | js/bundle.js 4 | js/bundle.min.js 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Maximilian Stoiber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-shower 2 | [Shower](https://shwr.me) presentation engine running on React and Redux. 3 | 4 | ## Known issues 5 | 6 | Does not work on node.js 6.x yet! 7 | 8 | ## Usage 9 | 10 | I built [reshower](https://github.com/Termina1/reshower) cli-tool specifically to streamline 11 | work with react-shower. 12 | 13 | To start a new project just go to some directory, then: 14 | ```javascript 15 | npm i -g reshower 16 | mkdir somedir && reshower init 17 | ``` 18 | 19 | and follow instructions. 20 | 21 | To start working just do: 22 | ```javascript 23 | reshower start 24 | ``` 25 | 26 | in your project folder. 27 | 28 | You can read more about reshower [here](https://github.com/Termina1/reshower). 29 | 30 | ## Configuration 31 | 32 | You can use config.json file in your directory to specify desired configuration. 33 | 34 | Options: 35 | 36 | 1. ```theme``` — shower theme you want to use, by default ```shower-ribbon``` is used. 37 | If you want to use another theme (e.g. ```shower-material```), you need first to use npm to install it 38 | 39 | 2. ```proportions``` — every shower theme should support 16x9 mode, and 4x3 mode, you can use 40 | this option to specify desired mode 41 | 42 | ## Components 43 | I tried to keep this engine reasonably close to original Shower API, but this is a list 44 | of components that I decided to introduce to increase usability or for some technical reasons. 45 | 46 | ### Deck 47 | This is just a wrapper for every presentation, it has no properties. 48 | 49 | ### Slide 50 | Basically ```
``` tag from original Shower engine, but less verbose. 51 | 52 | Properties: 53 | 1. ```className``` — additional class for this slide 54 | 55 | ### Code 56 | You can use it to embed some code in your slide. Prism.js is used for highlighting. 57 | 58 | Properties: 59 | 1. ```code``` — code you want to display 60 | 2. ```lang``` — prism.js language preset 61 | 62 | Example: 63 | ```javascript 64 | import example from "./examples/example.hs"; 65 | 66 | ... 67 | 68 | 69 | ``` 70 | 71 | If you want to know more about shower themes capabilities you should visit their documentation. 72 | 73 | ## Why? 74 | The purpose of this port is not to use fanciest technologies available, 75 | but because current Shower engine lacks features that are critical for me: 76 | 77 | ###1. Flexible file structure 78 | I don't want to keep all my presentation in one file. There should be easy way to split my presentation as 79 | any other kind of code into separate files to enchance its logical structure. 80 | 81 | ###2. Components 82 | I need a way to reuse markup, to not copy-paste every pattern I need on 2 or more slides, 83 | in addition over time there are more patterns that I want to reuse in my different presentation, 84 | instead I have to copy-paste them, this leads me to copying a lot of boilerplate code when I start another presentation. 85 | 86 | ###3. Extensibility 87 | Current Shower engine uses ad-hoc modules, which is not part of any specification. 88 | This port uses ES6 modules, which leads to better interop with other libraries. 89 | 90 | ###4. Hot-reload 91 | If you used it once, you're addicted. Of course, you can use things like Browsersync, livereload, etc, 92 | but it's not even close to instant updates that are available if you use webpack hot-reload feature. 93 | 94 | ###5. (Almost) Pure CSS 95 | Actually, the reason I started this project is my experience with Spectacle presentation engine. 96 | It has React, Redux and hot-reload as well. However, in my opinion their CSS-in-JS approach may be viable 97 | for developing isolated components in real project, presentation is not the same as your common web project. 98 | 99 | I struggled with radium for a bit, and understood, that it is easier to rewrite whole Spectacle project 100 | than customize their theme for my needs. So it goes. 101 | 102 | ###6. Beautiful and clean Shower themes 103 | I am a big fan of Shower themes. It's a pleasure to create presentations with them, they are clean 104 | and battle-tested by many speakers. 105 | 106 | ## WIP 107 | Currently this engine does not support ```Timer``` feature and ```next``` feature for lists. 108 | 109 | ## Contributions 110 | All contributions are welcome, just make a PR. 111 | -------------------------------------------------------------------------------- /bin/react-shower.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var path = require('path'); 3 | var exec = require('child_process').exec; 4 | 5 | var cmd = process.argv[2] 6 | switch (cmd) { 7 | case "build": 8 | var confpath = path.join(__dirname, "..", "webpack.prod.config.js"); 9 | var utils = path.join(__dirname, "..", "js/utils"); 10 | var utilsDest = path.join(process.cwd(), "build/js"); 11 | var dest = process.argv[3]; 12 | var stream = exec("node_modules/.bin/webpack --config " + confpath + " --progress --colors -p && cp -R " + utils + " " + utilsDest, function(err) { 13 | if (!err && dest) { 14 | exec("mkdir -p " + dest + " && cp -R -f ./build/* " + dest + " && rm -rf ./build"); 15 | } 16 | }); 17 | stream.stdout.pipe(process.stdout); 18 | stream.stderr.pipe(process.stderr); 19 | break; 20 | default: 21 | require('../server.dev.js'); 22 | break; 23 | } 24 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "proportions": "16x10", 3 | "theme": "ribbon" 4 | } 5 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | .shower.full .slide.active { 2 | margin: 0; 3 | visibility: visible; 4 | } 5 | 6 | body { 7 | background: black; 8 | } 9 | 10 | html, body { 11 | height: 100%; 12 | } 13 | 14 | .shower.full .progress { 15 | transform: translateZ(0); 16 | } 17 | 18 | #app { 19 | min-height: 100%; 20 | } 21 | 22 | .shower.list { 23 | min-height: 100%; 24 | } 25 | -------------------------------------------------------------------------------- /css/theme.css: -------------------------------------------------------------------------------- 1 | #app .picture h2 { 2 | color:#FFF; 3 | } 4 | 5 | #app .cover-me h2 { 6 | margin:30px 0 0; 7 | color:#FFF; 8 | text-align:center; 9 | font-size:70px; 10 | } 11 | 12 | #app .cover-me p { 13 | margin:10px 0 0; 14 | text-align:center; 15 | color:#FFF; 16 | font-style:italic; 17 | font-size:20px; 18 | } 19 | #app .cover-me p a { 20 | color:#FFF; 21 | } 22 | 23 | #app .see-more h2 { 24 | font-size:100px 25 | } 26 | 27 | #app .see-more img { 28 | width:0.72em; 29 | height:0.72em; 30 | } 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Shower 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /js/AppState.js: -------------------------------------------------------------------------------- 1 | import * as Derivable from 'derivable'; 2 | import * as Persistence from 'Persistence'; 3 | import * as History from 'History'; 4 | import assign from 'utils/assign'; 5 | 6 | /** 7 | * We store current slide number in location.hash. 8 | */ 9 | const slide = History.location.derive(location => 10 | parseInt(location.hash.slice(1), 10) || 1); 11 | 12 | /** 13 | * We store current mode in persistent atom (syncs with localStorage). 14 | */ 15 | const mode = Persistence.atom('__react_shower_mode__', 'list'); 16 | 17 | /** 18 | * App state is a {slide, mode} 19 | * 20 | * We expose struct derivation to prevent direct modification of the state. 21 | */ 22 | export const state = Derivable.struct({mode, slide}); 23 | 24 | /** 25 | * Change currently active slide. 26 | */ 27 | export function changeActiveSlide(nextSlide) { 28 | const hash = '#' + nextSlide; 29 | // XXX: Because browser bugs with :target we must do this instead of fancy 30 | // History.location.swap(...) :( 31 | window.location.hash = hash; 32 | } 33 | 34 | /** 35 | * Change mode. 36 | */ 37 | export function changeMode(nextMode) { 38 | mode.set(nextMode); 39 | } 40 | -------------------------------------------------------------------------------- /js/History.js: -------------------------------------------------------------------------------- 1 | import {atom} from 'derivable'; 2 | import createHistory from 'history/lib/createBrowserHistory'; 3 | 4 | const history = createHistory(); 5 | 6 | /** 7 | * Current location exposed as an atom. 8 | * 9 | * XXX: This functionality is probably worth be a separate npm package. 10 | */ 11 | export const location = atom(history.getCurrentLocation()); 12 | 13 | // Each time something pops up from history (browser back button is used) we 14 | // update current location. 15 | history.listen(nextLocation => { 16 | if (nextLocation.action === 'POP') { 17 | location.set(nextLocation); 18 | } 19 | }); 20 | 21 | // Each time someone updates location atom we push location to history. 22 | location.react(nextLocation => { 23 | history.push(nextLocation); 24 | }, {skipFirst: true}); 25 | -------------------------------------------------------------------------------- /js/Persistence.js: -------------------------------------------------------------------------------- 1 | import * as Derivable from 'derivable'; 2 | 3 | function tryParseJSON(blob) { 4 | try { 5 | return JSON.parse(blob); 6 | } catch (err) { 7 | return undefined; 8 | } 9 | } 10 | 11 | function read(key) { 12 | const blob = localStorage.getItem(key); 13 | return tryParseJSON(blob); 14 | } 15 | 16 | function write(key, value) { 17 | const blob = JSON.stringify(value); 18 | localStorage.setItem(key, blob); 19 | } 20 | 21 | /** 22 | * Create an atom which value is persisted in localStorage under the defined 23 | * key. 24 | * 25 | * XXX: This functionality is probably worth be a separate npm package. 26 | */ 27 | export function atom(key, initialValue) { 28 | let currentValue = read(key); 29 | if (currentValue === undefined) { 30 | currentValue = initialValue; 31 | } 32 | const value = Derivable.atom(currentValue); 33 | // Each time someone writes into atom we write value into localStorage. 34 | value.react(val => { 35 | write(key, val); 36 | }, {skipFirst: true}); 37 | return value; 38 | } 39 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import FontFaceObserver from 'fontfaceobserver'; 4 | import RedBox from "redbox-react"; 5 | import "prismjs"; 6 | 7 | const openSansObserver = new FontFaceObserver('Open Sans', {}); 8 | 9 | import 'css/main.css'; 10 | 11 | var styles; 12 | 13 | function render() { 14 | const rootEl = document.getElementById('app'); 15 | try { 16 | const config = require('config.json'); 17 | 18 | if (styles) { 19 | styles.unuse(); 20 | } 21 | 22 | var req = require.context('style-theme'); 23 | 24 | styles = req(`./shower-${config.theme}/styles/screen-${config.proportions}.css`); 25 | 26 | styles.use(); 27 | 28 | const App = require('components/App.react').default; 29 | const Presentation = require('presentation').default; 30 | 31 | ReactDOM.render( 32 | 33 | 34 | , 35 | rootEl 36 | ); 37 | } catch(err) { 38 | ReactDOM.render( 39 | , 40 | rootEl 41 | ); 42 | } 43 | } 44 | 45 | if (module.hot) { 46 | 47 | module.hot.accept(function(err) { 48 | const rootEl = document.getElementById('app'); 49 | ReactDOM.render( 50 | , 51 | rootEl 52 | ); 53 | }) 54 | module.hot.accept('components/App.react', () => { 55 | setTimeout(render); 56 | }); 57 | module.hot.accept('presentation', (errors, t, d) => { 58 | setTimeout(render); 59 | }); 60 | 61 | module.hot.accept('config.json', () => { 62 | setTimeout(render); 63 | }); 64 | } 65 | 66 | render(); 67 | -------------------------------------------------------------------------------- /js/components/.keep: -------------------------------------------------------------------------------- 1 | This file exists so the empty directories (`actions`, `components`, `constants`, `dispatcher`, `stores` and `utils`) get committed and uploaded as well. -------------------------------------------------------------------------------- /js/components/App.react.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import config from "config.json"; 3 | 4 | export default class App extends Component { 5 | 6 | static childContextTypes = { 7 | proportions: React.PropTypes.string 8 | } 9 | 10 | getChildContext() { 11 | return { 12 | proportions: config.proportions 13 | } 14 | } 15 | 16 | render() { 17 | return this.props.children; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /js/components/Code.react.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import {PrismCode} from "react-prism"; 3 | 4 | export default class Code extends Component { 5 | render() { 6 | var lang = this.props.lang ? `language-${this.props.lang}` : "language-javascript"; 7 | return ( 8 |
 9 |         
10 |           {this.props.code}
11 |         
12 |       
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /js/components/Deck.react.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { reactive } from "react-derivable"; 3 | 4 | import {PREV_KEYS, NEXT_KEYS, ESC, ENTER} from "constants/Shortcuts"; 5 | import {PROPORTIONS} from "constants/AppConstants"; 6 | import * as AppState from "AppState"; 7 | 8 | 9 | function getTransformScale(mode, pos) { 10 | if (mode === 'list') { 11 | return 'scale(1)'; 12 | } 13 | 14 | var denominator = Math.max( 15 | PROPORTIONS[pos].width / window.innerWidth, 16 | PROPORTIONS[pos].height / window.innerHeight 17 | ); 18 | 19 | return 'scale(' + (1/denominator) + ')'; 20 | } 21 | 22 | function getCount(children) { 23 | return children 24 | .filter(el => el.type !== 'header').length; 25 | } 26 | 27 | class Deck extends Component { 28 | 29 | static contextTypes = { 30 | proportions: React.PropTypes.string 31 | } 32 | 33 | onKeyPress(ev) { 34 | const slide = AppState.state.get().slide; 35 | const count = getCount(this.props.children); 36 | const wholeLength = this.props.children.length; 37 | 38 | if (PREV_KEYS.indexOf(ev.which) >= 0) { 39 | ev.preventDefault(); 40 | if (slide - 1 >= wholeLength - count) { 41 | AppState.changeActiveSlide(slide - 1); 42 | } 43 | } 44 | 45 | if (NEXT_KEYS.indexOf(ev.which) >= 0) { 46 | ev.preventDefault(); 47 | if (slide + 1 <= count) { 48 | AppState.changeActiveSlide(slide + 1); 49 | } 50 | } 51 | 52 | if (ev.which === ESC) { 53 | ev.preventDefault(); 54 | AppState.changeMode("list"); 55 | } 56 | 57 | if (ev.which === ENTER) { 58 | ev.preventDefault(); 59 | AppState.changeMode("full"); 60 | } 61 | } 62 | 63 | componentDidMount() { 64 | this.listener = this.onKeyPress.bind(this); 65 | this.resizeListener = this.forceUpdate.bind(this, function() {}); 66 | document.addEventListener('keydown', this.listener); 67 | window.addEventListener('resize', this.resizeListener); 68 | } 69 | 70 | componentWillUnmount() { 71 | document.removeEventListener('keydown', this.listener); 72 | window.removeEventListener('resize', this.resizeListener); 73 | } 74 | 75 | render() { 76 | var index = 0; 77 | var {slide, mode} = AppState.state.get(); 78 | var children = React.Children.map(this.props.children, (child) => 79 | React.cloneElement(child, { 80 | index: index++, 81 | data: {slide, mode}, 82 | }) 83 | ); 84 | 85 | var progress = (slide - 1)/(getCount(this.props.children) - 1) * 100; 86 | 87 | return ( 88 |
89 | {children} 90 |
95 |
96 | ); 97 | } 98 | } 99 | 100 | export default reactive(Deck); 101 | -------------------------------------------------------------------------------- /js/components/Slide.react.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import * as Derivable from 'derivable'; 3 | import * as AppState from 'AppState'; 4 | 5 | export default class Slide extends Component { 6 | 7 | onClick() { 8 | Derivable.transact(() => { 9 | AppState.changeActiveSlide(this.props.index); 10 | AppState.changeMode('full'); 11 | }); 12 | } 13 | 14 | render() { 15 | let cls = ''; 16 | if (this.props.index === this.props.data.slide) { 17 | cls = 'active'; 18 | } 19 | return ( 20 |
21 | {this.props.children} 22 |
23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /js/constants/.keep: -------------------------------------------------------------------------------- 1 | This file exists so the empty directories (`actions`, `components`, `constants`, `dispatcher`, `stores` and `utils`) get committed and uploaded as well. 2 | -------------------------------------------------------------------------------- /js/constants/AppConstants.js: -------------------------------------------------------------------------------- 1 | export const PROPORTIONS = { 2 | "16x10": { 3 | width: 1024, 4 | height: 640 5 | }, 6 | "4x3": { 7 | width: 1024, 8 | height: 768 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /js/constants/Shortcuts.js: -------------------------------------------------------------------------------- 1 | export const PREV_KEYS = [37, 38, 33, 72, 75]; 2 | export const NEXT_KEYS = [39, 40, 34, 74, 76]; 3 | export const ESC = 27; 4 | export const ENTER = 13; 5 | -------------------------------------------------------------------------------- /js/presentation.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Deck from "components/Deck.react"; 3 | import Slide from "components/Slide.react"; 4 | import Code from "components/Code.react"; 5 | import DocumentTitle from "react-document-title"; 6 | 7 | // Prism JS configuration 8 | import 'prismjs/components/prism-json'; 9 | import 'prismjs/components/prism-jsx'; 10 | import "style!css!prismjs/themes/prism.css"; // Theme 11 | 12 | import "css/theme.css"; 13 | 14 | const TOPIC = 'Shower Presentation Engine'; 15 | const SPEAKER = (Brought to you by Vadim Makeev) 16 | 17 | export default () => 18 | 19 | 20 |
21 |

{TOPIC}

22 |

{SPEAKER}

23 |
24 | 25 |

{TOPIC}

26 |

{SPEAKER}

27 | 28 |
29 | 30 |

Shower key features

31 |
    32 |
  1. Built on HTML, CSS and vanilla JavaScript
  2. 33 |
  3. Works in all modern browsers
  4. 34 |
  5. Themes are separated from engine
  6. 35 |
  7. Modular and extensible
  8. 36 |
  9. Fully keyboard accessible
  10. 37 |
  11. Printable to PDF
  12. 38 |
39 |

Shower [ʃəuə] noun. A person or thing that shows.

40 |
41 | 42 |

Plain text on your slides

43 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in <culpa> qui officia deserunt mollit anim id est laborum.

44 |
45 | 46 |

Two columns if you like

47 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia.

48 |
49 | 50 |

All kind of lists

51 |
    52 |
  1. Simple lists are marked with bullets
  2. 53 |
  3. Ordered lists begin with a number
  4. 54 |
  5. You can even nest lists one inside another 55 |
      56 |
    • Or mix their types
    • 57 |
    • But do not go too far
    • 58 |
    • Otherwise audience will be bored
    • 59 |
    60 |
  6. 61 |
  7. Look, seven rows exactly!
  8. 62 |
63 |
64 | 65 |

Serious citations

66 |
67 |
68 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia.

69 |
70 |
Marcus Tullius Cicero
71 |
72 |
73 | 74 |

Support for different languages

75 | 76 | `} lang="jsx" /> 77 |
78 | 79 |

Code samples

80 |
 81 |           <!DOCTYPE html>
 82 |           <html lang="en">
 83 |           <head> <!--Comment-->
 84 |               <title>Shower</title>
 85 |               <meta charset="UTF-8">
 86 |               <link rel="stylesheet" href="screen.css">
 87 |           </head>
 88 |         
89 |
90 | 91 |

Even tables

92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
LocavoreUmamiHelveticaVegan
FingerstacheKaleChipsKeytar
SrirachaGluten-freeEnnuiKeffiyeh
ThundercatsJeanShortsBiodiesel
TerryRichardsonSwagBlog
126 |

It’s good to have information organized.

127 |
128 | 129 |

Pictures

130 | 131 |
132 | 133 |

You can even shout this way

134 |
135 | 136 |

All nicely aligned to grid

137 |
138 | 139 |

140 | Shower logo  141 | See more on GitHub 142 |

143 |
144 |
145 |
146 | -------------------------------------------------------------------------------- /js/utils/assign.js: -------------------------------------------------------------------------------- 1 | const assign = Object.assign || require('object.assign'); // Polyfill maybe needed for browser support 2 | 3 | const assignToEmpty = (oldObject, newObject) => { 4 | return assign({}, oldObject, newObject); 5 | }; 6 | 7 | export default assignToEmpty; 8 | -------------------------------------------------------------------------------- /makewebpackconfig.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 5 | var fs = require('fs'); 6 | 7 | const showerThemeRegEx = /shower-[^\/]+\/styles\/[^\/]+\.css/; 8 | 9 | module.exports = function(options) { 10 | var entry, jsLoaders, plugins, cssLoaders, devtool; 11 | 12 | // If production is true 13 | if (options.prod) { 14 | // Entry 15 | entry = [ 16 | path.resolve(__dirname, 'js/app.js') // Start with js/app.js... 17 | ]; 18 | cssLoaders = ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader'); 19 | // Plugins 20 | plugins = [// Plugins for Webpack 21 | new webpack.optimize.UglifyJsPlugin({ // Optimize the JavaScript... 22 | compress: { 23 | warnings: false // ...but do not show warnings in the console (there is a lot of them) 24 | } 25 | }), 26 | new HtmlWebpackPlugin({ 27 | template: path.join(__dirname, 'index.html'), // Move the index.html file... 28 | minify: { // Minifying it while it is parsed 29 | removeComments: true, 30 | collapseWhitespace: true, 31 | removeRedundantAttributes: true, 32 | useShortDoctype: true, 33 | removeEmptyAttributes: true, 34 | removeStyleLinkTypeAttributes: true, 35 | keepClosingSlash: true, 36 | minifyJS: true, 37 | minifyCSS: true, 38 | minifyURLs: true 39 | }, 40 | inject: true // inject all files that are generated by webpack, e.g. bundle.js, main.css with the correct HTML tags 41 | }), 42 | new ExtractTextPlugin("css/main.css"), 43 | new webpack.DefinePlugin({ 44 | "process.env": { 45 | NODE_ENV: JSON.stringify("production") 46 | } 47 | }), 48 | new webpack.ContextReplacementPlugin(/style-theme/, path.join(process.cwd(), "node_modules"), true, showerThemeRegEx) 49 | ]; 50 | 51 | // If app is in development 52 | } else { 53 | // Entry 54 | entry = [ 55 | "webpack-dev-server/client?http://localhost:3000", // Needed for hot reloading 56 | "webpack/hot/only-dev-server", // See above 57 | path.resolve(__dirname, 'js/app.js') // Start with js/app.js... 58 | ]; 59 | cssLoaders = 'style-loader!css-loader!postcss-loader'; 60 | devtool = 'cheap-module-eval-source-map'; 61 | // Only plugin is the hot module replacement plugin 62 | plugins = [ 63 | new webpack.HotModuleReplacementPlugin(), // Make hot loading work 64 | new HtmlWebpackPlugin({ 65 | template: path.join(__dirname, 'index.html'), // Move the index.html file 66 | inject: true // inject all files that are generated by webpack, e.g. bundle.js, main.css with the correct HTML tags 67 | }), 68 | new webpack.ContextReplacementPlugin(/style-theme/, path.join(process.cwd(), "node_modules"), true, showerThemeRegEx) 69 | ] 70 | } 71 | 72 | return { 73 | entry: entry, 74 | output: { // Compile into js/build.js 75 | path: path.resolve(process.cwd(), 'build'), 76 | filename: "js/bundle.js" 77 | }, 78 | resolve: { 79 | modulesDirectories: [ 80 | process.cwd(), 81 | path.resolve(__dirname, "js"), 82 | __dirname, 83 | path.join(process.cwd(), "node_modules") 84 | ] 85 | }, 86 | module: { 87 | loaders: [{ 88 | test: /\.js$/, 89 | loader: 'babel', 90 | include: [ 91 | path.join(__dirname, "js"), 92 | path.join(process.cwd(), "presentation.js") 93 | ], 94 | query: { 95 | "plugins": [ 96 | "syntax-class-properties", 97 | "transform-class-properties" 98 | ], 99 | "presets": ["es2015", "react", "stage-2"] 100 | } 101 | }, { 102 | test: /\.css$/, 103 | loader: cssLoaders, 104 | include: [ 105 | path.join(process.cwd(), 'css'), 106 | path.join(__dirname, 'css'), 107 | ], 108 | }, { 109 | test: showerThemeRegEx, 110 | loader: "style/useable!css", 111 | }, { 112 | test: /\.jpe?g$|\.gif$|\.png|\.woff$|\.woff2$|\.eot$|\.ttf$|\.svg$/i, 113 | loader: "url-loader?limit=10000" 114 | }, 115 | { 116 | test:/\.json$/, 117 | loader: "json-loader" 118 | }, { 119 | loader: "raw-loader", 120 | include: [path.join(process.cwd(), "code")] 121 | } 122 | ] 123 | }, 124 | devtool: devtool, 125 | plugins: plugins, 126 | postcss: function() { 127 | return [ 128 | require('postcss-import')({ // Import all the css files... 129 | glob: true, 130 | onImport: function (files) { 131 | files.forEach(this.addDependency); // ...and add dependecies from the main.css files to the other css files... 132 | }.bind(this) // ...so they get hot–reloaded when something changes... 133 | }), 134 | require('postcss-simple-vars')(), // ...then replace the variables... 135 | require('postcss-focus')(), // ...add a :focus to ever :hover... 136 | require('autoprefixer')({ // ...and add vendor prefixes... 137 | browsers: ['last 2 versions', 'IE > 8'] // ...supporting the last 2 major browser versions and IE 8 and up... 138 | }), 139 | require('postcss-reporter')({ // This plugin makes sure we get warnings in the console 140 | clearMessages: true 141 | }) 142 | ]; 143 | }, 144 | target: "web", // Make web variables accessible to webpack, e.g. window 145 | stats: false, // Don't show stats in the console 146 | progress: true 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-shower", 3 | "version": "1.1.1", 4 | "bin": { 5 | "react-shower": "./bin/react-shower.js" 6 | }, 7 | "dependencies": { 8 | "autoprefixer": "^6.0.2", 9 | "babel": "^6.0.0", 10 | "babel-core": "^6.0.0", 11 | "babel-eslint": "^4.1.2", 12 | "babel-loader": "^6.0.0", 13 | "babel-plugin-react-transform": "^2.0.0", 14 | "babel-plugin-syntax-class-properties": "^6.5.0", 15 | "babel-plugin-transform-class-properties": "^6.6.0", 16 | "babel-plugin-transform-react-constant-elements": "6.3.13", 17 | "babel-plugin-transform-react-inline-elements": "6.3.13", 18 | "babel-plugin-transform-react-remove-prop-types": "0.1.0", 19 | "babel-preset-es2015": "^6.0.0", 20 | "babel-preset-react": "^6.0.0", 21 | "babel-preset-stage-2": "^6.0.0", 22 | "css-loader": "^0.23.0", 23 | "cssnano": "^3.0.1", 24 | "derivable": "^1.0.0-beta9", 25 | "extract-text-webpack-plugin": "0.9.1", 26 | "file-loader": "^0.8.4", 27 | "fontfaceobserver": "^1.5.1", 28 | "history": "^3.0.0", 29 | "html-webpack-plugin": "^1.6.1", 30 | "json-loader": "^0.5.4", 31 | "lodash": "4.11.2", 32 | "object.assign": "^4.0.1", 33 | "postcss-focus": "^1.0.0", 34 | "postcss-import": "^7.0.0", 35 | "postcss-loader": "^0.8.0", 36 | "postcss-reporter": "^1.2.1", 37 | "postcss-simple-vars": "^1.0.0", 38 | "prismjs": "^1.5.1", 39 | "raw-loader": "^0.5.1", 40 | "react": "^0.14.2", 41 | "react-addons": "^0.9.1-deprecated", 42 | "react-addons-pure-render-mixin": "^0.14.7", 43 | "react-derivable": "^0.1.0", 44 | "react-document-title": "^2.0.1", 45 | "react-dom": "^0.14.2", 46 | "react-prism": "^3.1.1", 47 | "redbox-react": "^1.2.3", 48 | "shower-ribbon": "^2.0.4", 49 | "style-loader": "^0.13.0", 50 | "url-loader": "^0.5.6", 51 | "webpack": "^1.13.1", 52 | "webpack-dev-server": "^1.11.0" 53 | }, 54 | "devDependencies": { 55 | "eslint": "^1.4.1", 56 | "eslint-config-airbnb": "1.0.0", 57 | "eslint-plugin-react": "^3.3.2" 58 | }, 59 | "repository": { 60 | "type": "git", 61 | "url": "git://github.com/mxstbr/react-boilerplate.git" 62 | }, 63 | "scripts": { 64 | "lint": "eslint .", 65 | "start": "node server.dev.js", 66 | "build": "webpack --config webpack.prod.config.js --progress --colors -p && cp -R js/utils build/js" 67 | }, 68 | "author": "Termina1", 69 | "license": "MIT" 70 | } 71 | -------------------------------------------------------------------------------- /server.dev.js: -------------------------------------------------------------------------------- 1 | // Gets called when running npm start 2 | 3 | var webpack = require('webpack'); 4 | var WebpackDevServer = require('webpack-dev-server'); 5 | var config = require('./webpack.dev.config'); 6 | 7 | console.log('Starting server...\n'); 8 | 9 | new WebpackDevServer(webpack(config), { // Start a server 10 | publicPath: config.output.publicPath, 11 | hot: true, // With hot reloading 12 | inline: false, 13 | contentBase: __dirname, 14 | historyApiFallback: true 15 | }).listen(3000, 'localhost', function (err, result) { 16 | if (err) { 17 | console.log(err); 18 | } else { 19 | console.log('Server started'); 20 | console.log('Listening at localhost:3000'); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./makewebpackconfig")({ 2 | prod: false 3 | }); -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./makewebpackconfig")({ 2 | prod: true 3 | }); --------------------------------------------------------------------------------