├── examples ├── add-scripts │ ├── bar.css │ ├── baz.js │ ├── foo.css │ ├── qux.js │ ├── slides.md │ └── remarker.yml ├── remark │ ├── symlink-asset │ ├── README.md │ ├── remarker.yml │ └── slides.md ├── livereload-false │ ├── slides.md │ └── remarker.yml ├── favicon │ ├── remarker.yml │ ├── favicon.ico │ └── slides.md ├── livereload-port │ ├── remarker.yml │ └── slides.md ├── replace-remark │ ├── my-own-remark.js │ ├── remarker.yml │ └── slides.md ├── has-assets │ ├── assets │ │ └── degu.png │ └── slides.md ├── simple │ ├── README.md │ ├── my-slides.md │ ├── my-slides-2.md │ └── slides.md ├── metatags │ ├── README.md │ ├── remarker.yml │ ├── my-slides.md │ ├── my-slides-2.md │ └── slides.md └── latex-mathjax │ ├── remarker.yml │ └── slides.md ├── .editorconfig ├── .bmp.yml ├── .gitignore ├── assets ├── default.css └── help-message.txt ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── package.json ├── layout.njk ├── index.js ├── test.js ├── README.md └── vendor └── livereload.js /examples/add-scripts/bar.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/add-scripts/baz.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/add-scripts/foo.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/add-scripts/qux.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/remark/symlink-asset: -------------------------------------------------------------------------------- 1 | slides.md -------------------------------------------------------------------------------- /examples/livereload-false/slides.md: -------------------------------------------------------------------------------- 1 | # hey 2 | -------------------------------------------------------------------------------- /examples/add-scripts/slides.md: -------------------------------------------------------------------------------- 1 | # add script example 2 | -------------------------------------------------------------------------------- /examples/favicon/remarker.yml: -------------------------------------------------------------------------------- 1 | assets: 2 | - favicon.ico 3 | -------------------------------------------------------------------------------- /examples/livereload-false/remarker.yml: -------------------------------------------------------------------------------- 1 | livereload: false 2 | -------------------------------------------------------------------------------- /examples/livereload-port/remarker.yml: -------------------------------------------------------------------------------- 1 | livereloadPort: 12345 2 | -------------------------------------------------------------------------------- /examples/replace-remark/my-own-remark.js: -------------------------------------------------------------------------------- 1 | console.log("remark.js"); 2 | -------------------------------------------------------------------------------- /examples/livereload-port/slides.md: -------------------------------------------------------------------------------- 1 | # demo of livereloading in different port 2 | 3 | test test 4 | -------------------------------------------------------------------------------- /examples/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kt3k/remarker/HEAD/examples/favicon/favicon.ico -------------------------------------------------------------------------------- /examples/replace-remark/remarker.yml: -------------------------------------------------------------------------------- 1 | remarkPath: my-own-remark.js 2 | script: console.log("injected script") 3 | -------------------------------------------------------------------------------- /examples/replace-remark/slides.md: -------------------------------------------------------------------------------- 1 | Slide 0 2 | 3 | --- 4 | 5 | Slide 1 6 | 7 | --- 8 | 9 | Slide 2 10 | -------------------------------------------------------------------------------- /examples/has-assets/assets/degu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kt3k/remarker/HEAD/examples/has-assets/assets/degu.png -------------------------------------------------------------------------------- /examples/remark/README.md: -------------------------------------------------------------------------------- 1 | run: 2 | 3 | node ../../index.js 4 | 5 | and slides are up at `http://localhost:6275/` 6 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | run: 2 | 3 | node ../../index.js 4 | 5 | and slides are up at `http://localhost:6275/` 6 | -------------------------------------------------------------------------------- /examples/metatags/README.md: -------------------------------------------------------------------------------- 1 | run: 2 | 3 | node ../../index.js 4 | 5 | and slides are up at `http://localhost:6275/` 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | [*] 3 | indent_style=space 4 | indent_size=2 5 | trim_trailing_whitespace=true 6 | insert_final_newline=true 7 | -------------------------------------------------------------------------------- /.bmp.yml: -------------------------------------------------------------------------------- 1 | version: 1.15.0 2 | commit: Bump to version v%.%.% 3 | files: 4 | README.md: remarker v%.%.% 5 | package.json: '"version": "%.%.%"' 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | /node_modules 3 | 4 | # Sorry, npm. We prefer yarn.lock 5 | /package-lock.json 6 | 7 | # coverage 8 | /coverage 9 | /.nyc_output 10 | -------------------------------------------------------------------------------- /examples/add-scripts/remarker.yml: -------------------------------------------------------------------------------- 1 | cssFiles: 2 | - foo.css 3 | - bar.css 4 | - https://example.com/style.css 5 | scriptFiles: 6 | - baz.js 7 | - qux.js 8 | - https://example.com/script.js 9 | -------------------------------------------------------------------------------- /examples/metatags/remarker.yml: -------------------------------------------------------------------------------- 1 | title: Example slides 2 | description: This is example slides 3 | ogImage: https://avatars.githubusercontent.com/kt3k 4 | ogImageWidth: 400 5 | ogImageHeight: 400 6 | twitter: kt3k 7 | favicon: https://avatars.githubusercontent.com/kt3k 8 | url: https://github.com/kt3k/remarker 9 | -------------------------------------------------------------------------------- /examples/latex-mathjax/remarker.yml: -------------------------------------------------------------------------------- 1 | scriptFiles: 2 | - https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML&delayStartupUntil=configured 3 | script: 4 | - "MathJax.Hub.Config({ tex2jax: { skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'] } }); MathJax.Hub.Configured();" 5 | -------------------------------------------------------------------------------- /examples/favicon/slides.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # My Awesome Presentation 4 | 5 | This is my-slides.md 6 | 7 | --- 8 | 9 | # Agenda 10 | 11 | 1. Introduction 12 | 2. Deep-dive 13 | 3. ... 14 | 15 | ## [NOTE]: Note that you need remark.js alongside this html file, but no internet connection. 16 | 17 | # Introduction 18 | -------------------------------------------------------------------------------- /examples/simple/my-slides.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # My Awesome Presentation 4 | 5 | This is my-slides.md 6 | 7 | --- 8 | 9 | # Agenda 10 | 11 | 1. Introduction 12 | 2. Deep-dive 13 | 3. ... 14 | 15 | ## [NOTE]: Note that you need remark.js alongside this html file, but no internet connection. 16 | 17 | # Introduction 18 | -------------------------------------------------------------------------------- /examples/metatags/my-slides.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # My Awesome Presentation 4 | 5 | This is my-slides.md 6 | 7 | --- 8 | 9 | # Agenda 10 | 11 | 1. Introduction 12 | 2. Deep-dive 13 | 3. ... 14 | 15 | ## [NOTE]: Note that you need remark.js alongside this html file, but no internet connection. 16 | 17 | # Introduction 18 | -------------------------------------------------------------------------------- /examples/simple/my-slides-2.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # My Awesome Presentation 4 | 5 | This is my-slides-2.md 6 | 7 | --- 8 | 9 | # Agenda 10 | 11 | 1. Introduction 12 | 2. Deep-dive 13 | 3. ... 14 | 15 | ## [NOTE]: Note that you need remark.js alongside this html file, but no internet connection. 16 | 17 | # Introduction 18 | -------------------------------------------------------------------------------- /examples/metatags/my-slides-2.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # My Awesome Presentation 4 | 5 | This is my-slides-2.md 6 | 7 | --- 8 | 9 | # Agenda 10 | 11 | 1. Introduction 12 | 2. Deep-dive 13 | 3. ... 14 | 15 | ## [NOTE]: Note that you need remark.js alongside this html file, but no internet connection. 16 | 17 | # Introduction 18 | -------------------------------------------------------------------------------- /examples/has-assets/slides.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # My Awesome Presentation 4 | 5 | ??? 6 | 7 | Notes for the _first_ slide! 8 | 9 | --- 10 | 11 | # Agenda 12 | 13 | 1. Introduction 14 | 2. Deep-dive 15 | 3. ... 16 | 17 | ## [NOTE]: Note that you need remark.js alongside this html file, but no internet connection. 18 | 19 | # Introduction 20 | -------------------------------------------------------------------------------- /examples/metatags/slides.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # My Awesome Presentation 4 | 5 | ??? 6 | 7 | Notes for the _first_ slide! 8 | 9 | --- 10 | 11 | # Agenda 12 | 13 | 1. Introduction :mag: 14 | 2. Deep-dive :swimmer: 15 | 3. ... 16 | 17 | ## [NOTE]: Note that you need remark.js alongside this html file, but no internet connection. 18 | 19 | # Introduction 20 | -------------------------------------------------------------------------------- /examples/simple/slides.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # My Awesome Presentation 4 | 5 | ??? 6 | 7 | Notes for the _first_ slide! 8 | 9 | --- 10 | 11 | # Agenda 12 | 13 | 1. Introduction :mag: 14 | 2. Deep-dive :swimmer: 15 | 3. ... 16 | 17 | ## [NOTE]: Note that you need remark.js alongside this html file, but no internet connection. 18 | 19 | # Introduction 20 | -------------------------------------------------------------------------------- /assets/default.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Avenir Next', 'Hiragino Kaku Gothic ProN', 'Meiryo', 'メイリオ', sans-serif; 3 | } 4 | h1, h2, h3 { 5 | font-weight: bold; 6 | } 7 | .remark-code, 8 | .remark-inline-code { 9 | font-family: 'Menlo', 'Monaco', 'Courier new', monospace; 10 | } 11 | 12 | .remark-slide-content.inverse { 13 | color: #f3f3f3; 14 | background-color: #272822; 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v4 13 | - run: npm install 14 | - run: npm run cov 15 | - uses: codecov/codecov-action@v3 16 | with: 17 | token: ${{ secrets.CODECOV_TOKEN }} 18 | -------------------------------------------------------------------------------- /assets/help-message.txt: -------------------------------------------------------------------------------- 1 | 2 | Usage: 3 | remarker [options] serve Serves all the assets at localhost 4 | remarker [options] build Builds all the assets to the dest 5 | 6 | Options: 7 | -h, --help Shows the help message and exits 8 | -v, --version Shows the version number and exits 9 | -s, --source Specifies the slide's markdown file. 10 | This overrides 'source' property of the config file. 11 | -o, --out The output filename of the slides. Default is index.html. 12 | -d, --dest The destination directory for built slides. 13 | -p, --port The port number for dev server. 14 | -b, --open-browser Open the browser to the page when server starts. Default is false. 15 | 16 | See https://npm.im/remarker for more details. 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | Copyright © 2016 Yoshiya Hinosawa ( @kt3k ) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remarker", 3 | "version": "1.15.0", 4 | "description": "Remark cli", 5 | "main": "index.js", 6 | "bin": "index.js", 7 | "files": [ 8 | "index.js", 9 | "assets", 10 | "vendor", 11 | "layout.njk" 12 | ], 13 | "scripts": { 14 | "test": "kocha test", 15 | "cov": "nyc --reporter=lcov --reporter=text-summary npm test", 16 | "codecov": "npm run cov && codecov", 17 | "precommit": "lint-staged" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/kt3k/remarker.git" 22 | }, 23 | "keywords": [ 24 | "remark", 25 | "markdown", 26 | "cli", 27 | "slides" 28 | ], 29 | "author": "Yoshiya Hinosawa", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/kt3k/remarker/issues" 33 | }, 34 | "homepage": "https://github.com/kt3k/remarker#readme", 35 | "dependencies": { 36 | "berber": "^1.4.1", 37 | "connect-livereload": "^0.6.0", 38 | "gulp-livereload": "^3.8.1", 39 | "gulp-rename": "^1.2.2", 40 | "layout1": "^1.1.0", 41 | "map-stream": "0.0.7", 42 | "minimisted": "^2.0.0", 43 | "node-emoji": "^1.8.1", 44 | "nunjucks": "^3.0.1", 45 | "openurl": "^1.1.1", 46 | "vinyl-transform": "^1.0.0", 47 | "yaml-hook": "^1.0.0" 48 | }, 49 | "devDependencies": { 50 | "axios": "^1.6.0", 51 | "chai": "^4.1.0", 52 | "codecov": "^3.6.5", 53 | "kocha": "^1.8.0", 54 | "nyc": "^13.0.0", 55 | "rimraf": "^2.6.1", 56 | "touch": "^3.1.0", 57 | "ws": "^7.4.6" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/latex-mathjax/slides.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # Configuring remarker to display LaTeX via MathJax 4 | 5 | Michael Liebling 6 | 7 | 26 September 2019 8 | 9 | This is an example [Remark](https://github.com/gnab/remark) presentation that 10 | demonstrates the configuration of 11 | [Remarker](https://www.npmjs.com/package/remarker) for the use of LaTeX math via 12 | [MathJax](https://www.mathjax.org). 13 | 14 | --- 15 | 16 | # LaTeX, Mathjax, & Remark 17 | 18 | The use of MathJax with Remark is documented on a 19 | [Remark wiki page](https://github.com/gnab/remark/wiki/LaTeX-using-MathJax). 20 | Specifically, the syntax requires that LaTeX math commands be placed between a 21 | pair of backticks: 22 | 23 | ```markdown 24 | `\(\LaTeX{}\)` 25 | 26 | 1. This is an inline integral: `\(\int_a^bf(x)dx\)` 27 | 2. More `\(x={a \over b}\)` formulae. 28 | 29 | Display formula: 30 | 31 | $$e^{i\pi} + 1 = 0$$ 32 | ``` 33 | 34 | _Note:_ this is different from the syntax used in 35 | [marked2app](https://marked2app.com/help/MathJax.html), where LaTeX math is 36 | indicated with an extra backslash: 37 | 38 | ```markdown 39 | \\( x^2+y^2 \\) 40 | ``` 41 | 42 | --- 43 | 44 | # LaTeX and Mathjax with Remarker 45 | 46 | In order for the presentation to be properly generated, one creates a 47 | `remarker.yml` configuration file (at the same level as the `slides.md` file) 48 | with the following content: 49 | 50 | ```YML 51 | scriptFiles: 52 | - https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML&delayStartupUntil=configured 53 | script: 54 | - "MathJax.Hub.Config({ tex2jax: { skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'] } }); MathJax.Hub.Configured();" 55 | ``` 56 | 57 | --- 58 | 59 | # Math example 60 | 61 | `\(\LaTeX{}\)` 62 | 63 | 1. This is an inline integral: `\(\int_a^bf(x)dx\)` 64 | 2. More `\(x={a \over b}\)` formulae. 65 | 66 | Display formula: 67 | 68 | $$e^{i\pi} + 1 = 0$$ 69 | -------------------------------------------------------------------------------- /layout.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} 5 | 6 | {% if url %} 7 | 8 | {% endif %} 9 | 10 | {% if ogImage %} 11 | 12 | {% if ogImageWidth %} 13 | 14 | {% endif %} 15 | {% if ogImageHeight %} 16 | 17 | {% endif %} 18 | {% endif %} 19 | {% if description %} 20 | 21 | {% endif %} 22 | {% if twitter %} 23 | 24 | {% endif %} 25 | {% if ogImage %} 26 | 27 | 28 | {% endif %} 29 | {% if favicon %} 30 | 31 | {% endif %} 32 | {% for file in cssFiles %} 33 | 34 | {% endfor %} 35 | 39 | 40 | 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | loading... 49 |
50 | 51 | {% for file in scriptFiles %} 52 | 53 | {% endfor %} 54 | 57 | {% for file in scriptFilesAfterCreate %} 58 | 59 | {% endfor %} 60 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /examples/remark/remarker.yml: -------------------------------------------------------------------------------- 1 | remarkConfig: 2 | highlightStyle: monokai 3 | highlightLanguage: remark 4 | highlightLines: true 5 | assets: 6 | - unknown-assets 7 | - symlink-asset 8 | - fifo-asset 9 | css: | 10 | @import url(https://fonts.googleapis.com/css?family=Droid+Serif); 11 | @import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz); 12 | @import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic); 13 | 14 | body { 15 | font-family: 'Droid Serif'; 16 | } 17 | h1, h2, h3 { 18 | font-family: 'Yanone Kaffeesatz'; 19 | font-weight: 400; 20 | margin-bottom: 0; 21 | } 22 | .remark-slide-content h1 { font-size: 3em; } 23 | .remark-slide-content h2 { font-size: 2em; } 24 | .remark-slide-content h3 { font-size: 1.6em; } 25 | .footnote { 26 | position: absolute; 27 | bottom: 3em; 28 | } 29 | li p { line-height: 1.25em; } 30 | .red { color: #fa0000; } 31 | .large { font-size: 2em; } 32 | a, a > code { 33 | color: rgb(249, 38, 114); 34 | text-decoration: none; 35 | } 36 | code { 37 | background: #e7e8e2; 38 | border-radius: 5px; 39 | } 40 | .remark-code, .remark-inline-code { font-family: 'Ubuntu Mono'; } 41 | .remark-code-line-highlighted { background-color: #373832; } 42 | .pull-left { 43 | float: left; 44 | width: 47%; 45 | } 46 | .pull-right { 47 | float: right; 48 | width: 47%; 49 | } 50 | .pull-right ~ p { 51 | clear: both; 52 | } 53 | #slideshow .slide .content code { 54 | font-size: 0.8em; 55 | } 56 | #slideshow .slide .content pre code { 57 | font-size: 0.9em; 58 | padding: 15px; 59 | } 60 | .inverse { 61 | background: #272822; 62 | color: #777872; 63 | text-shadow: 0 0 20px #333; 64 | } 65 | .inverse h1, .inverse h2 { 66 | color: #f3f3f3; 67 | line-height: 0.8em; 68 | } 69 | 70 | /* Slide-specific styling */ 71 | #slide-inverse .footnote { 72 | bottom: 12px; 73 | left: 20px; 74 | } 75 | #slide-how .slides { 76 | font-size: 0.9em; 77 | position: absolute; 78 | top: 151px; 79 | right: 140px; 80 | } 81 | #slide-how .slides h3 { 82 | margin-top: 0.2em; 83 | } 84 | #slide-how .slides .first, #slide-how .slides .second { 85 | padding: 1px 20px; 86 | height: 90px; 87 | width: 120px; 88 | -moz-box-shadow: 0 0 10px #777; 89 | -webkit-box-shadow: 0 0 10px #777; 90 | box-shadow: 0 0 10px #777; 91 | } 92 | #slide-how .slides .first { 93 | background: #fff; 94 | position: absolute; 95 | top: 20%; 96 | left: 20%; 97 | z-index: 1; 98 | } 99 | #slide-how .slides .second { 100 | position: relative; 101 | background: #fff; 102 | z-index: 0; 103 | } 104 | 105 | /* Two-column layout */ 106 | .left-column { 107 | color: #777; 108 | width: 20%; 109 | height: 92%; 110 | float: left; 111 | } 112 | .left-column h2:last-of-type, .left-column h3:last-child { 113 | color: #000; 114 | } 115 | .right-column { 116 | width: 75%; 117 | float: right; 118 | padding-top: 1em; 119 | } 120 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { 4 | asset, 5 | dest, 6 | name, 7 | on, 8 | port, 9 | debugPagePath, 10 | helpMessage, 11 | loggerTitle, 12 | addMiddleware, 13 | } = require("berber"); 14 | const layout1 = require("layout1"); 15 | const rename = require("gulp-rename"); 16 | const { readFileSync, existsSync, statSync } = require("fs"); 17 | const { join } = require("path"); 18 | const minimisted = require("minimisted"); 19 | const openurl = require("openurl"); 20 | const transform = require("vinyl-transform"); 21 | const map = require("map-stream"); 22 | const emoji = require("node-emoji"); 23 | require("yaml-hook/register"); 24 | 25 | const emojify = () => 26 | transform((filename) => 27 | map((chunk, next) => next(null, emoji.emojify(chunk.toString()))) 28 | ); 29 | 30 | const read = (path) => readFileSync(join(__dirname, path)).toString(); 31 | 32 | const layoutFilename = join(__dirname, "layout.njk"); 33 | 34 | const defaultCss = read("assets/default.css"); 35 | 36 | const defaultAssetsPath = "assets"; 37 | 38 | const defaultConfig = { 39 | title: "", // The page title of the result html 40 | description: "", // The description og:description meta tag 41 | ogImage: "", // The url for og:image and twitter:image 42 | ogImageWidth: null, // The width of og:image 43 | ogImageHeight: null, // The height of og:image 44 | twitter: null, // twitter display id 45 | favicon: null, // The path to favicon 46 | url: null, // The url of the slides 47 | port: 6275, // The port number of dev server 48 | livereload: true, 49 | livereloadPort: 35729, 50 | dest: "build", // The destination directory of built assets 51 | out: "index.html", // The result html filename of the slides 52 | source: "slides.md", // The source of the slides 53 | css: defaultCss, // The additional css for the slides 54 | cssFiles: [], // The additional css files 55 | script: "", // The additional script for the slides 56 | scriptFiles: [], // The additional script files 57 | remarkConfig: {}, // The config object passed to remark 58 | remarkPath: join(__dirname, "vendor", "remark.js"), // The remark path 59 | scriptFilesAfterCreate: [], // The additional script files loaded after remark.create 60 | assets: [defaultAssetsPath], // The asset paths 61 | "open-browser": false, // open the browser to the page when the server starts 62 | }; 63 | 64 | name("remarker"); 65 | debugPagePath("__remarker__"); 66 | loggerTitle("remarker"); 67 | helpMessage(read("assets/help-message.txt")); 68 | 69 | const onConfig = (config, argv) => { 70 | config = Object.assign({}, defaultConfig, config, argv); 71 | 72 | port(config.port); 73 | dest(config.dest); 74 | 75 | const cssFiles = !Array.isArray(config.cssFiles) 76 | ? Array(config.cssFiles) 77 | : config.cssFiles; 78 | const scriptFiles = !Array.isArray(config.scriptFiles) 79 | ? Array(config.scriptFiles) 80 | : config.scriptFiles; 81 | const scriptFilesAfterCreate = !Array.isArray(config.scriptFilesAfterCreate) 82 | ? Array(config.scriptFilesAfterCreate) 83 | : config.scriptFilesAfterCreate; 84 | 85 | const slidePipeline = asset(config.source) 86 | .assetOptions({ allowEmpty: true }) 87 | .pipe(emojify()) 88 | .pipe(rename(config.out)) 89 | .pipe( 90 | layout1.nunjucks(layoutFilename, { 91 | data: { 92 | css: config.css, 93 | cssFiles: cssFiles, 94 | script: config.script, 95 | scriptFiles: scriptFiles, 96 | title: config.title, 97 | description: config.description, 98 | ogImage: config.ogImage, 99 | ogImageWidth: config.ogImageWidth, 100 | ogImageHeight: config.ogImageHeight, 101 | twitter: config.twitter, 102 | favicon: config.favicon, 103 | url: config.url, 104 | remarkConfig: config.remarkConfig, 105 | scriptFilesAfterCreate: scriptFilesAfterCreate, 106 | livereloadPort: config.livereloadPort, 107 | }, 108 | }), 109 | ); 110 | 111 | // livereload settings 112 | if (config.livereload) { 113 | on("serve", () => { 114 | onLivereloadConfig(slidePipeline, config); 115 | }); 116 | } 117 | 118 | // open browser if the option specified 119 | if (config["open-browser"]) { 120 | on("serve", () => { 121 | openurl.open("http://localhost:" + config.port); 122 | }); 123 | } 124 | 125 | asset(config.remarkPath).pipe(rename("remark.js")); 126 | 127 | cssFiles 128 | .filter((src) => !/^http/.test(src)) 129 | .forEach((src) => { 130 | asset(src).base(process.cwd()); 131 | }); 132 | 133 | scriptFiles 134 | .filter((src) => !/^http/.test(src)) 135 | .forEach((src) => { 136 | asset(src).base(process.cwd()); 137 | }); 138 | 139 | config.assets.forEach((src) => { 140 | if (existsSync(src)) { 141 | const stat = statSync(src); 142 | 143 | if (stat.isDirectory()) { 144 | asset(join(src, "**/*.*")).base(process.cwd()); 145 | } else if (stat.isFile()) { 146 | asset(src).base(process.cwd()); 147 | } else { 148 | console.log( 149 | `Warning: asset entry '${src}' has unknown type, skipping this entry`, 150 | ); 151 | } 152 | } else if (src === defaultAssetsPath) { 153 | // do nothing, ignore silently 154 | } else { 155 | console.log( 156 | `Warning: asset entry '${src}' not found, skipping this entry`, 157 | ); 158 | } 159 | }); 160 | }; 161 | 162 | const livereloadScriptMiddleware = (req, res, next) => { 163 | if (require("url").parse(req.url).pathname !== "/livereload.js") { 164 | next(); 165 | return; 166 | } 167 | 168 | const livereloadScript = read("vendor/livereload.js"); 169 | 170 | res.setHeader("Content-Type", "text/javascript"); 171 | res.end(livereloadScript); 172 | }; 173 | 174 | const onLivereloadConfig = (slidePipeline, config) => { 175 | const livereload = require("connect-livereload"); 176 | const gulplivereload = require("gulp-livereload"); 177 | 178 | const port = config.livereloadPort; 179 | 180 | addMiddleware(() => livereload({ port, src: "/livereload.js" })); 181 | 182 | addMiddleware(() => livereloadScriptMiddleware); 183 | 184 | gulplivereload.listen({ port }); 185 | slidePipeline.pipe(gulplivereload({ port })); 186 | }; 187 | 188 | on("config", (config) => 189 | minimisted((argv) => onConfig(config, argv), { 190 | string: ["source", "out", "dest", "port"], 191 | boolean: ["open-browser"], 192 | alias: { 193 | s: "source", 194 | o: "out", 195 | p: "port", 196 | d: "dest", 197 | b: "open-browser", 198 | }, 199 | })); 200 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const { before, after, describe, it, context, timeout } = require("kocha"); 2 | const { expect } = require("chai"); 3 | const { spawn, execSync } = require("child_process"); 4 | const { readFileSync, existsSync } = require("fs"); 5 | const { join } = require("path"); 6 | const rimraf = require("rimraf"); 7 | const touch = require("touch"); 8 | const WebSocket = require("ws"); 9 | const axios = require("axios"); 10 | 11 | const examples = { 12 | simple: join(__dirname, "examples/simple"), 13 | replaceRemark: join(__dirname, "examples/replace-remark"), 14 | hasAssets: join(__dirname, "examples/has-assets"), 15 | favicon: join(__dirname, "examples/favicon"), 16 | remark: join(__dirname, "examples/remark"), 17 | addScripts: join(__dirname, "examples/add-scripts"), 18 | }; 19 | 20 | const processes = []; 21 | 22 | before((done) => { 23 | rimraf("examples/*/build", done); 24 | }); 25 | 26 | after((done) => { 27 | processes.forEach((child) => child.kill()); 28 | rimraf("examples/*/{build,build2}", done); 29 | }); 30 | 31 | describe("remarker", () => { 32 | it("creates index.html from slides.md", () => { 33 | execSync("node ../../index.js build", { cwd: examples.simple }); 34 | 35 | expect( 36 | readFileSync(join(examples.simple, "build", "index.html")).toString(), 37 | ).to.include("My Awesome Presentation"); 38 | }); 39 | 40 | it("replaces :emoji: notations", () => { 41 | execSync("node ../../index.js build", { cwd: examples.simple }); 42 | 43 | const result = readFileSync( 44 | join(examples.simple, "build", "index.html"), 45 | ).toString(); 46 | 47 | expect(result).to.include("🏊"); 48 | expect(result).to.not.include(":swimmer:"); 49 | }); 50 | 51 | it("replaces stylesheets by remarker.yml's css property", () => { 52 | execSync("node ../../index.js build", { cwd: examples.remark }); 53 | 54 | expect( 55 | readFileSync(join(examples.remark, "build", "index.html")).toString(), 56 | ).to.include( 57 | "@import url(https://fonts.googleapis.com/css?family=Droid+Serif);", 58 | ); 59 | }); 60 | 61 | it("replaces remark.js by remarker.yml's remarkPath", () => { 62 | execSync("node ../../index.js build", { cwd: examples.replaceRemark }); 63 | 64 | expect( 65 | readFileSync( 66 | join(examples.replaceRemark, "build", "remark.js"), 67 | ).toString(), 68 | ).to.include('console.log("remark.js")'); 69 | }); 70 | 71 | it("injects scripts by remarker.yml's script", () => { 72 | execSync("node ../../index.js build", { 73 | cwd: examples.replaceRemark, 74 | }); 75 | 76 | expect( 77 | readFileSync( 78 | join(examples.replaceRemark, "build", "index.html"), 79 | ).toString(), 80 | ).to.include('console.log("injected script")'); 81 | }); 82 | 83 | describe("-s, --source option", () => { 84 | it("specifies the slide's markdown path", () => { 85 | execSync("node ../../index.js build --source my-slides.md", { 86 | cwd: examples.simple, 87 | }); 88 | 89 | expect( 90 | readFileSync(join(examples.simple, "build", "index.html")).toString(), 91 | ).to.include("This is my-slides.md"); 92 | 93 | execSync("node ../../index.js build -s my-slides-2.md", { 94 | cwd: examples.simple, 95 | }); 96 | 97 | expect( 98 | readFileSync(join(examples.simple, "build", "index.html")).toString(), 99 | ).to.include("This is my-slides-2.md"); 100 | }); 101 | }); 102 | 103 | describe("-d, --dest option", () => { 104 | it("specifies destination directory", () => { 105 | execSync("node ../../index.js build --dest build2", { 106 | cwd: examples.simple, 107 | }); 108 | 109 | expect( 110 | readFileSync(join(examples.simple, "build2", "index.html")).toString(), 111 | ).to.include("My Awesome Presentation"); 112 | }); 113 | }); 114 | 115 | describe("-o, --out option", () => { 116 | it("specifies destination directory", () => { 117 | execSync("node ../../index.js build --out slides.html", { 118 | cwd: examples.simple, 119 | }); 120 | 121 | expect( 122 | readFileSync(join(examples.simple, "build", "slides.html")).toString(), 123 | ).to.include("My Awesome Presentation"); 124 | }); 125 | }); 126 | 127 | describe("contents in assets directory", () => { 128 | it("are copied to build directory", () => { 129 | execSync("node ../../index.js build", { cwd: examples.hasAssets }); 130 | 131 | expect(existsSync("examples/has-assets/build/assets/degu.png")).to.equal( 132 | true, 133 | ); 134 | }); 135 | }); 136 | 137 | describe("file asset entry", () => { 138 | it("builds the given file to build dir", () => { 139 | execSync("node ../../index.js build", { cwd: examples.favicon }); 140 | 141 | expect(existsSync("examples/favicon/build/favicon.ico")).to.equal(true); 142 | }); 143 | }); 144 | 145 | describe("scriptFiles option", () => { 146 | it("adds script files", () => { 147 | execSync("node ../../index.js build", { cwd: examples.addScripts }); 148 | 149 | const html = readFileSync( 150 | join(examples.addScripts, "build", "index.html"), 151 | ).toString(); 152 | 153 | expect(html).to.include("baz.js"); 154 | expect(html).to.include("qux.js"); 155 | }); 156 | }); 157 | 158 | describe("cssFiles option", () => { 159 | it("adds css files", () => { 160 | execSync("node ../../index.js build", { cwd: examples.addScripts }); 161 | 162 | const html = readFileSync( 163 | join(examples.addScripts, "build", "index.html"), 164 | ).toString(); 165 | 166 | expect(html).to.include("foo.css"); 167 | expect(html).to.include("bar.css"); 168 | }); 169 | }); 170 | 171 | context("when serving", () => { 172 | it("starts livereload server by default", (done) => { 173 | timeout(8000); 174 | const child = spawn("node", ["../../index.js"], { cwd: examples.remark }); 175 | 176 | processes.push(child); 177 | 178 | setTimeout(() => { 179 | const ws = new WebSocket("http://localhost:35729/livereload"); 180 | 181 | ws.on("message", (data) => { 182 | expect(JSON.parse(data).command).to.equal("reload"); 183 | 184 | child.kill(); 185 | 186 | done(); 187 | }); 188 | 189 | touch(join(examples.remark, "slides.md")); 190 | }, 2000); 191 | }); 192 | 193 | it("responses livereload.js at /livereload.js", (done) => { 194 | timeout(8000); 195 | const child = spawn("node", ["../../index.js"], { cwd: examples.remark }); 196 | 197 | processes.push(child); 198 | 199 | setTimeout(() => { 200 | axios.get("http://localhost:6275/index.html"); 201 | axios.get("http://localhost:6275/livereload.js").then((res) => { 202 | expect(res.data).to.include("livereload"); 203 | 204 | done(); 205 | }); 206 | }, 2000); 207 | }); 208 | }); 209 | }); 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remarker v1.15.0 2 | 3 | [![ci](https://github.com/kt3k/remarker/actions/workflows/ci.yml/badge.svg)](https://github.com/kt3k/remarker/actions/workflows/ci.yml) 4 | [![codecov](https://codecov.io/gh/kt3k/remarker/branch/master/graph/badge.svg)](https://codecov.io/gh/kt3k/remarker) 5 | 6 | > [Remark][remark] cli 7 | 8 | [remark][remark] is a simple, in-browser, markdown-driven slideshow tool. 9 | `remarker` is a command line tool for building a remark-based slideshow page 10 | very easily. 11 | 12 | # Usage 13 | 14 | Install via npm: 15 | 16 | ```console 17 | $ npm install --save remarker 18 | ``` 19 | 20 | Write your slide in markdown: 21 | 22 | ```md 23 | # My Slide 24 | 25 | --- 26 | 27 | # My Slide 2 28 | 29 | ??? 30 | 31 | Presenter notes here 32 | 33 | --- 34 | ``` 35 | 36 | save the above as `slides.md` 37 | 38 | Invoke `remarker` command. 39 | 40 | ```console 41 | $ ./node_modules/.bin/remarker 42 | ``` 43 | 44 | Or if you have `npx` command, then hit: 45 | 46 | ```console 47 | $ npx remarker 48 | ``` 49 | 50 | This starts a local server at port 6275 (this is configurable) and you can see 51 | your slides at [http://localhost:6275/](http://localhost:6275/). 52 | 53 | See remark's [slide](https://remarkjs.com/) and 54 | [documentation](https://github.com/gnab/remark#remark) for more details about 55 | its syntax, features etc. 56 | 57 | ## Build slides 58 | 59 | You can build your slides as static page as `remarker build` command. 60 | 61 | ```console 62 | $ ./node_modules/.bin/remarker build 63 | ``` 64 | 65 | This builds your slides as html page under `build/` directory. The output 66 | directory is configurable. See the below for details. 67 | 68 | ## Installing globally 69 | 70 | You can instead install it globally, in one of these two ways: 71 | 72 | ```bash 73 | sudo npm i -g remarker # from the npm repository 74 | sudo npm i -g . # if there's a clone in the current directory 75 | ``` 76 | 77 | After that, you should be able to invoke it this way from any directory in your 78 | system: 79 | 80 | ```bash 81 | remarker [build] 82 | ``` 83 | 84 | # Configuration 85 | 86 | You can configure remarker with the configuration file `remarker.yml`: 87 | 88 | (Note: remarker.yml should be put in the current directory.) 89 | 90 | Default settings are as follows: 91 | 92 | ```yml 93 | port: 6275 94 | dest: build 95 | out: index.html 96 | source: slides.md 97 | title: '' 98 | assets: ['assets'] 99 | css: '' 100 | cssFiles: [] 101 | script: '' 102 | scriptFiles: [] 103 | remarkConfig: {} 104 | remarkPath: moduleDir + '/vendor/remark.js' 105 | scriptFilesAfterCreate: [] 106 | livereload: true 107 | livereloadPort: 35729 108 | ``` 109 | 110 | ## Basic options 111 | 112 | - `port` is the port number of remarker server. Default is `6275`. 113 | - `dest` is the destination of `remarker build` command. Default is `build` 114 | - `out` is the filename of the result html page. Default is `index.html` 115 | - `source` is the source markdown filename. Default is `slides.md`. 116 | - `title` is the page title of the slides. Default is an empty string. 117 | - `css` is css text you want to add to slides' html page. 118 | - `cssFiles` is the list of additional stylesheet files (URL or the file path 119 | relative to your current working director) you want to add to slides' html 120 | page. If you provide file paths, these files are copied/served statically. 121 | Default is an empty array. 122 | 123 | ## Advanced options 124 | 125 | - `assets` is the list of assets directory. These directories are copied/served 126 | statically. 127 | - `scriptFiles` is the list of additional JavaScript files (URL or the file path 128 | relative to your current working director) appended after the remark.js. If 129 | you provide file paths, these files are copied/served statically. Default is 130 | an empty array. 131 | - `script` is additional JavaScript code appended after the remark.js and 132 | `scriptFiles`. Default is an empty string. 133 | - `remarkConfig` is the config object which is passed to remark.create(options). 134 | Default is an empty object. 135 | - See 136 | [the remark source code](https://github.com/gnab/remark/blob/develop/src/remark/models/slideshow.js#L41-L48) 137 | for what option is available. 138 | - `remarkPath` is the path to remark.js. This replaces the original remark.js 139 | with specified one. 140 | - `scriptFilesAfterCreate` is the list of additional JavaScript files (URL or 141 | the file path relative to your current working director) appended after the 142 | `remark.create()`. If you provide file paths, these files are copied/served 143 | statically. Default is an empty array. 144 | - `livereload` is the flag to toggle livereloading feature. Default is true. 145 | - `livereloadPort` is the port number for livereloading websocket connection. 146 | Default is 35729. 147 | 148 | ## CLI Usage 149 | 150 | 154 | 155 | ``` 156 | Usage: 157 | remarker [options] serve Serves all the assets at localhost 158 | remarker [options] build Builds all the assets to the dest 159 | 160 | Options: 161 | -h, --help Shows the help message and exits 162 | -v, --version Shows the version number and exits 163 | -s, --source Specifies the slide's markdown file. 164 | This overrides 'source' property of the config file. 165 | -o, --out The output filename of the slides. Default is index.html. 166 | -d, --dest The destination directory for built slides. 167 | -p, --port The port number for dev server. 168 | -b, --open-browser Open the browser to the page when server starts. Default is false. 169 | ``` 170 | 171 | # Examples 172 | 173 | - [simple example](https://github.com/kt3k/remarker/tree/master/examples/simple) 174 | - [remark slides](https://github.com/kt3k/remarker/tree/master/examples/remark) 175 | - The original `remark` slides in `remarker` configuration. 176 | - [LaTeX and Mathjax](https://github.com/kt3k/remarker/tree/master/examples/latex-mathjax) 177 | - Usage of LaTeX and Mathjax in remarker. Contributed by @michaelliebling. 178 | 179 | # Motivation of `remarker` 180 | 181 | `remark` is a great presentation tool and you can write your slide's contents in 182 | markdown. The problem is when you simply use remark, you need to maintain the 183 | html, css and scripts as well as markdown. If you care the details of design and 184 | style of the slides, that's fine. However if you don't care the design of the 185 | slides that much and want to focus only on the contents, then the settings of 186 | css, html, scripts seem quite messy. `remarker` solves this problem. `remarker` 187 | separates the contents (= markdown) from the settings (css, html, scripts). So 188 | you can only focus on and keep maintaining the contents of the slides and let 189 | `remarker` do the rest of the work. This is easier than using `remark` directly. 190 | 191 | # How-tos 192 | 193 | ## How to use images in slides 194 | 195 | Put the images under `./assets` directory and they are automatically 196 | served/copied and you can reference it like 197 | `` in your slides. 198 | 199 | The directory name of `assets` can be configured in `remarker.yml`. See the 200 | configuration section for details. 201 | 202 | # Who uses this tool? 203 | 204 | See [this search](https://github.com/search?q=filename%3Aremarker.yml). 205 | 206 | # History 207 | 208 | - 2022-11-24 v1.15.0 Added loading indicator. 209 | - 2022-02-13 v1.14.0 Fixed error while changing slides.md 210 | - 2020-05-11 v1.12.0 Added scriptFilesAfterCreate option. 211 | - 2019-10-18 v1.11.1 Fix dependency. 212 | - 2019-10-18 v1.11.0 Added `-b, --open-browser` option. 213 | - 2019-05-14 v1.10.0 Added `--dest`, `--out`, and `--port` CLI options. 214 | - 2018-08-06 v1.9.0 Added :emoji: transformation. Modify `cssFiles` option 215 | handling (#11). 216 | - 2018-06-10 v1.8.1 Fixed help and version options. 217 | - 2018-06-10 v1.8.0 Added livereloading feature. 218 | - 2018-01-29 v1.7.0 Enabled file asset (#8). 219 | - 2018-01-13 v1.6.1 Fixed -s option. 220 | - 2018-01-12 v1.6.0 Added --source cli option. 221 | - 2017-08-05 v1.3.0 Added remarkConfig prop. 222 | 223 | # License 224 | 225 | MIT 226 | 227 | [remark]: https://github.com/gnab/remark 228 | -------------------------------------------------------------------------------- /examples/remark/slides.md: -------------------------------------------------------------------------------- 1 | name: inverse 2 | 3 | ## layout: true class: center, middle, inverse 4 | 5 | ## #remark [ri-mahrk] .footnote[Go directly to [project site](https://github.com/gnab/remark)] 6 | 7 | ## What is it and why should I be using it? 8 | 9 | --- 10 | 11 | layout: false .left-column[ 12 | 13 | ## What is it? 14 | 15 | ] .right-column[ A simple, in-browser, Markdown-driven slideshow tool targeted 16 | at people who know their way around HTML and CSS, featuring: 17 | 18 | - Markdown formatting, with smart extensions 19 | 20 | - Presenter mode, with cloned slideshow view 21 | 22 | - Syntax highlighting, supporting a range of languages 23 | 24 | - Slide scaling, thus similar appearance on all devices / resolutions .red[*] 25 | 26 | - Touch support for smart phones and pads, i.e. swipe to navigate slides 27 | 28 | ## .footnote[.red[*] At least browsers try their best] ] 29 | 30 | .left-column[ 31 | 32 | ## What is it? 33 | 34 | ## Why use it? 35 | 36 | ] .right-column[ If your ideal slideshow creation workflow contains any of the 37 | following steps: 38 | 39 | - Just write what's on your mind 40 | 41 | - Do some basic styling 42 | 43 | - Easily collaborate with others 44 | 45 | - Share with and show to everyone 46 | 47 | Then remark might be perfect for your next.red[*] slideshow! 48 | 49 | ## .footnote[.red[*] You probably want to convert existing slideshows as well] ] 50 | 51 | .left-column[ 52 | 53 | ## What is it? 54 | 55 | ## Why use it? 56 | 57 | ] .right-column[ As the slideshow is expressed using Markdown, you may: 58 | 59 | - Focus on the content, expressing yourself in next to plain text not worrying 60 | what flashy graphics and disturbing effects to put where 61 | 62 | As the slideshow is actually an HTML document, you may: 63 | 64 | - Display it in any decent browser 65 | 66 | - Style it using regular CSS, just like any other HTML content 67 | 68 | - Use it offline! 69 | 70 | As the slideshow is contained in a plain file, you may: 71 | 72 | - Store it wherever you like; on your computer, hosted from your Dropbox, hosted 73 | on Github Pages alongside the stuff you're presenting... 74 | 75 | - Easily collaborate with others, keeping track of changes using your favourite 76 | SCM tool, like Git or Mercurial ] 77 | 78 | --- 79 | 80 | template: inverse 81 | 82 | ## How does it work, then? 83 | 84 | --- 85 | 86 | name: how 87 | 88 | .left-column[ 89 | 90 | ## How does it work? 91 | 92 | ### - Markdown 93 | 94 | ] .right-column[ A Markdown-formatted chunk of text is transformed into 95 | individual slides by JavaScript running in the browser: 96 | 97 | ```remark 98 | # Slide 1 99 | This is slide 1 100 | 101 | --- 102 | 103 | # Slide 2 104 | This is slide 2 105 | ``` 106 | 107 | .slides[ .first[ 108 | 109 | ### Slide 1 110 | 111 | This is slide 1 ] .second[ 112 | 113 | ### Slide 2 114 | 115 | This is slide 2 ] ] 116 | 117 | Regular Markdown rules apply with only a single exception: 118 | 119 | - A line containing three dashes constitutes a new slide (not a horizontal rule, 120 | `<hr />`) 121 | 122 | ## Have a look at the [Markdown website](http://daringfireball.net/projects/markdown/) if you're not familiar with Markdown formatting. ] 123 | 124 | .left-column[ 125 | 126 | ## How does it work? 127 | 128 | ### - Markdown 129 | 130 | ### - Inside HTML 131 | 132 | ] .right-column[ A simple HTML document is needed for hosting the styles, 133 | Markdown and the generated slides themselves: 134 | 135 | ```xml 136 | 137 | 138 | 139 | 142 | 143 | 144 | *