├── .gitignore ├── template ├── onepunch.json ├── index.html ├── src │ ├── index.css │ └── index.js └── readme.md ├── .editorconfig ├── LICENSE ├── package.json ├── readme.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.log 4 | .vscode 5 | -------------------------------------------------------------------------------- /template/onepunch.json: -------------------------------------------------------------------------------- 1 | { 2 | "width": 960, 3 | "height": 600, 4 | "progress": "line", 5 | "date": "October 21, 2015", 6 | "slideNumber": true 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | 12 | [{package.json}] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |

My Presentation

15 |
16 | 19 |
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /template/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #000000; 3 | } 4 | 5 | html, body { 6 | margin: 0; 7 | padding: 0; 8 | max-width: 100vw; 9 | overflow: hidden; 10 | } 11 | 12 | body, button { 13 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | -webkit-text-size-adjust: 100%; 17 | } 18 | 19 | main > article { 20 | margin: 0; 21 | padding: 10px; 22 | width: 100vw; 23 | height: 100vh; 24 | border-color: #eee; 25 | border-style: solid; 26 | border-width: 0; 27 | position: relative; 28 | box-sizing: border-box; 29 | } 30 | 31 | button.copy-to-clipboard { 32 | position: fixed; 33 | font-size: 0.9vw; 34 | padding: 0.2vw; 35 | cursor: pointer; 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Nextbit (https://nextbit.it/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nextbitlabs/onepunch", 3 | "version": "2.6.2", 4 | "description": "Command-line interface to create PDF presentations using web technology.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "xo index.js template/src/index.js", 8 | "release": "np" 9 | }, 10 | "bin": { 11 | "onepunch": "index.js" 12 | }, 13 | "keywords": [ 14 | "html", 15 | "css", 16 | "pdf", 17 | "web", 18 | "presentations", 19 | "slides" 20 | ], 21 | "files": [ 22 | "index.js", 23 | "template", 24 | "LICENSE" 25 | ], 26 | "repository": "nextbitlabs/onepunch", 27 | "bugs": "https://github.com/nextbitlabs/onepunch/issues", 28 | "homepage": "https://github.com/nextbitlabs/onepunch", 29 | "author": { 30 | "name": "Riccardo Scalco", 31 | "url": "https://riccardoscalco.github.io/" 32 | }, 33 | "license": "MIT", 34 | "dependencies": { 35 | "chalk": "^3.0.0", 36 | "fs-extra": "^8.1.0", 37 | "live-server": "^1.2.1", 38 | "meow": "^6.1.1", 39 | "ora": "^4.1.1", 40 | "puppeteer": "^5.5.0" 41 | }, 42 | "devDependencies": { 43 | "np": "^7.0.0", 44 | "xo": "^0.36.1" 45 | }, 46 | "publishConfig": { 47 | "access": "public" 48 | }, 49 | "xo": { 50 | "envs": [ 51 | "browser" 52 | ], 53 | "rules": { 54 | "comma-dangle": [ 55 | "error", 56 | "always-multiline" 57 | ] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /template/readme.md: -------------------------------------------------------------------------------- 1 | # **onepunch** presentation 2 | 3 | ### Configuration 4 | The configuration file `onepunch.json` contains configuration parameters, with the following available: 5 | - `width` and `height`, numeric: slide width and height in pixels; 6 | - `progress`, string: available `line`, to show a progress line at the bottom of the page, or `none`, to suppress it; 7 | - `date`, string: presentation date, used by tags `` along the presentation; 8 | - `slideNumber`, bool: whether to visualize the slide number in tags ``. 9 | 10 | ### View the presentation 11 | 12 | Inside the project directory, run: 13 | 14 | ```sh 15 | $ onepunch serve [-i htmlfile] 16 | ``` 17 | 18 | The command above starts a local server and opens the browser, use the arrow keys to see the next and previous slides. 19 | Flag `-i` (or `--input`) specifies the HTML file to open, it defaults to "index.html". 20 | 21 | ### Print the PDF 22 | 23 | Inside the project directory, run: 24 | 25 | ```sh 26 | $ onepunch print [-i htmlfile] [-o pdffile] 27 | ``` 28 | 29 | Flag `-i` (or `--input`) specifies the HTML file to print, it defaults to "index.html". 30 | Flag `-o` (or `--output`) specifies the name of the PDF file in output, it defaults to "index.pdf". 31 | 32 | ### Update 33 | 34 | To align the `src` directory of a past presentation to the latest release of **onepunch**, 35 | first update **onepunch** itself, then use: 36 | 37 | ```sh 38 | $ onepunch update 39 | ``` 40 | This will make any new feature available to the current presentation. 41 | Please note that any custom change inside directory `src` will be overwritten. 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # onepunch 2 | 3 | **onepunch** is a command-line interface to create PDF presentations using web technology. 4 | 5 | **onepunch** is designed for designers, it does not provide any default style. 6 | Designers can write CSS files and link them in the `index.html`. 7 | 8 | ### Prerequisites 9 | 10 | To use **onepunch**, you should have installed **node** and **npm** in your system. 11 | Please follow the [official instructions](https://www.npmjs.com/get-npm). 12 | 13 | ### Install 14 | 15 | Install **onepunch** globally with the following command: 16 | 17 | ```sh 18 | $ npm install -g @nextbitlabs/onepunch 19 | ``` 20 | 21 | Please note that onepunch makes use of [puppeteer](https://github.com/puppeteer/puppeteer/), which will download chromium. 22 | This is necessary to print the PDF file. 23 | 24 | Update **onepunch** to the latest release with: 25 | 26 | ```sh 27 | $ npm update -g @nextbitlabs/onepunch 28 | ``` 29 | 30 | ### Create a project 31 | 32 | ```sh 33 | $ onepunch init [-n directory-name] 34 | ``` 35 | 36 | The command above creates the directory `directory-name` with all the files needed to bootstrap the presentation. 37 | 38 | The configuration file `onepunch.json` contains configuration parameters, with the following available: 39 | - `width` and `height`, numeric: slide width and height in pixels; 40 | - `progress`, string: available `line`, to show a progress line at the bottom of the page, or `none`, to suppress it; 41 | - `date`, string: presentation date, used by tags `` along the presentation; 42 | - `slideNumber`, bool: whether to visualize the slide number in tags ``. 43 | 44 | ### View the presentation 45 | 46 | Inside the project directory, run: 47 | 48 | ```sh 49 | $ onepunch serve [-i htmlfile] 50 | ``` 51 | 52 | The command above starts a local server and opens the browser, use the arrow keys to see the next and previous slides. 53 | Flag `-i` (or `--input`) specifies the HTML file to open, it defaults to "index.html". 54 | 55 | ### Print the PDF 56 | 57 | Inside the project directory, run: 58 | 59 | ```sh 60 | $ onepunch print [-i htmlfile] [-o pdffile] 61 | ``` 62 | 63 | Flag `-i` (or `--input`) specifies the HTML file to print, it defaults to "index.html". 64 | Flag `-o` (or `--output`) specifies the name of the PDF file in output, it defaults to "index.pdf". 65 | 66 | ### Update 67 | Update **onepunch** to the latest release with: 68 | 69 | ```sh 70 | $ npm update -g @nextbitlabs/onepunch 71 | ``` 72 | 73 | To align the `src` directory of a past presentation to the latest release of **onepunch**, 74 | first update **onepunch** itself with the command above, then use: 75 | 76 | ```sh 77 | $ onepunch update 78 | ``` 79 | This will make any new feature available to the current presentation. 80 | Please note that any custom change inside directory `src` will be overwritten. 81 | 82 | ### Create custom styles 83 | 84 | Each slide is created by means of tag `article`, for example: 85 | 86 | ```html 87 |
88 | 89 | 90 |
91 |
92 |

My Presentation Title

93 |
94 |
95 | 96 | 97 |
98 |
99 |

My Presentation Title

100 |

We use web technology to create PDF presentations.

101 |
102 |
103 | 104 | ... 105 | 106 |
107 | ``` 108 | 109 | As usual, designers can define CSS classes to apply custom style. 110 | For example, the following class defines a specific grid layout: 111 | 112 | ```css 113 | .layout-1 { 114 | display: grid; 115 | grid-template-rows: minmax(50px, max-content) auto 50px; 116 | grid-template-columns: 100%; 117 | grid-template-areas: 118 | "A" 119 | "B" 120 | "C"; 121 | } 122 | ``` 123 | 124 | and can be used in the following way: 125 | 126 | ```html 127 |
128 |
129 | ... 130 |
131 |
132 | .. 133 |
134 |
135 | ... 136 |
137 |
138 | ``` 139 | -------------------------------------------------------------------------------- /template/src/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const isAutomated = navigator.webdriver; 3 | 4 | /* 5 | Get configuration parameters and initialize the web page. 6 | */ 7 | 8 | (async function () { 9 | const fecthResult = await fetch('./onepunch.json'); 10 | const config = await fecthResult.json(); 11 | init(config); 12 | })(); 13 | 14 | /* 15 | Initialize services. 16 | */ 17 | 18 | function init(config) { 19 | addSlideId(); 20 | setHash(); 21 | addEventListeners(config); 22 | setBorders(config.width, config.height); 23 | addProgress(config); 24 | addSlideNumber(config); 25 | addDate(config); 26 | if (!isAutomated) { 27 | addCopyToClipBoardButton(config); 28 | } 29 | } 30 | 31 | /* 32 | Set location hash 33 | */ 34 | 35 | function setHash() { 36 | window.location.hash = window.location.hash || getSlides()[0].id; 37 | } 38 | 39 | /* 40 | Add event listeners. 41 | */ 42 | 43 | function addEventListeners(config) { 44 | window.addEventListener( 45 | 'keydown', 46 | handleOnkeydown, 47 | ); 48 | window.addEventListener( 49 | 'resize', 50 | () => { 51 | setBorders(config.width, config.height); 52 | if (!isAutomated) { 53 | setCopyToClipBoardButtonPosition(config); 54 | } 55 | 56 | updateLocationHash(getSlideId()); 57 | }, 58 | ); 59 | } 60 | 61 | /* 62 | Set article borders. 63 | */ 64 | 65 | function setBorders(width, height) { 66 | const articles = getSlides(); 67 | const borderLeft = Math.max(0, (window.innerWidth - width) / 2); 68 | const borderTop = Math.max(0, (window.innerHeight - height) / 2); 69 | articles.forEach(elt => { 70 | elt.style.borderWidth = `${borderTop}px ${borderLeft}px`; 71 | }); 72 | } 73 | 74 | /* 75 | Change slide with arrow keys. 76 | */ 77 | 78 | function handleOnkeydown(event) { 79 | const {keyCode} = event; 80 | const LEFT = 37; 81 | const UP = 38; 82 | const RIGHT = 39; 83 | const DOWN = 40; 84 | switch (keyCode) { 85 | case RIGHT: 86 | case DOWN: 87 | goToNextPage(); 88 | break; 89 | case UP: 90 | case LEFT: 91 | goToPreviousPage(); 92 | break; 93 | // No default 94 | } 95 | } 96 | 97 | function goToNextPage() { 98 | updateUrl(+1); 99 | } 100 | 101 | function goToPreviousPage() { 102 | updateUrl(-1); 103 | } 104 | 105 | /* 106 | Add progress to each slide based on config. 107 | */ 108 | 109 | function addProgress(config) { 110 | if (!config.progress || config.progress === 'none') { 111 | return; 112 | } 113 | 114 | const articles = getSlides(); 115 | 116 | if (config.progress === 'line') { 117 | articles.forEach((article, index) => { 118 | const line = document.createElement('div'); 119 | line.classList.add('progress-line'); 120 | article.append(line); 121 | 122 | line.style.backgroundColor = 'var(--primary-color, #000)'; 123 | 124 | line.style.height = '3px'; 125 | line.style.position = 'absolute'; 126 | 127 | line.style.bottom = '0'; 128 | line.style.left = '0'; 129 | 130 | const normIndex = index / (articles.length - 1); 131 | 132 | line.style.width = `${config.width * normIndex}px`; 133 | }); 134 | } 135 | } 136 | 137 | /* 138 | Update location hash. 139 | */ 140 | 141 | function updateLocationHash(pageId) { 142 | window.location.hash = `#${pageId}`; 143 | } 144 | 145 | /* 146 | Get the id of the current slide 147 | */ 148 | 149 | function getSlideId() { 150 | return window.location.hash.slice(1); 151 | } 152 | 153 | /* 154 | Update url. 155 | */ 156 | 157 | function updateUrl(increment) { 158 | const index = getPageIndex(); 159 | if ( 160 | (increment === +1 && index < numberOfSlides() - 1) || 161 | (increment === -1 && index > 0) 162 | ) { 163 | const slideId = getSlides()[index + increment].id; 164 | updateLocationHash(slideId); 165 | } 166 | } 167 | 168 | /* 169 | Return the number of slides. 170 | */ 171 | 172 | function numberOfSlides() { 173 | return getSlides().length; 174 | } 175 | 176 | /* 177 | Get page index location hash. 178 | */ 179 | 180 | function getPageIndex() { 181 | return getSlides() 182 | .map(page => page.id) 183 | .findIndex(id => id === getSlideId()); 184 | } 185 | 186 | /* 187 | Return the list of slides. 188 | */ 189 | 190 | function getSlides() { 191 | return [...document.querySelectorAll('main > article')]; 192 | } 193 | 194 | /* 195 | Add slide numbers. 196 | */ 197 | 198 | function addSlideNumber(config) { 199 | const slides = getSlides(); 200 | slides.forEach((slide, index) => { 201 | const element = slide.querySelector('[data-onepunch="slide-number"]'); 202 | if (element) { 203 | if (config.slideNumber) { 204 | element.textContent = index + 1; 205 | } else { 206 | element.style.display = 'none'; 207 | } 208 | } 209 | }); 210 | } 211 | 212 | /* 213 | Add date. 214 | */ 215 | 216 | function addDate(config) { 217 | const slides = getSlides(); 218 | slides.forEach(slide => { 219 | const element = slide.querySelector('[data-onepunch="date"]'); 220 | if (element) { 221 | if (config.date) { 222 | element.textContent = config.date; 223 | } else { 224 | element.style.display = 'none'; 225 | } 226 | } 227 | }); 228 | } 229 | 230 | /* 231 | Add slide id 232 | */ 233 | 234 | function addSlideId() { 235 | const slides = getSlides(); 236 | slides.forEach((slide, index) => { 237 | slide.id = slide.id || `${index + 1}`; 238 | }); 239 | } 240 | 241 | /* 242 | Add copy-to-clipboard button 243 | */ 244 | function addCopyToClipBoardButton(config) { 245 | appendCopyToClipBoardButton(); 246 | setCopyToClipBoardButtonPosition(config); 247 | } 248 | 249 | function copyToClipBoard() { 250 | fetch(window.location.href) 251 | .then(response => response.text()) 252 | .then(text => { 253 | const element = document.createElement('html'); 254 | element.innerHTML = text; 255 | const articles = element.querySelectorAll('main > article'); 256 | const htmlSnippet = articles[getPageIndex()].outerHTML; 257 | navigator.clipboard.writeText(htmlSnippet).then(() => { 258 | // Console.log('Copied to clipboard.'); 259 | }, () => { 260 | // Console.log('Error, not copied.'); 261 | }); 262 | }); 263 | } 264 | 265 | function appendCopyToClipBoardButton() { 266 | const button = document.createElement('button'); 267 | button.className = 'copy-to-clipboard'; 268 | button.textContent = 'copy to clipboard'; 269 | button.addEventListener('click', copyToClipBoard); 270 | document.body.append(button); 271 | } 272 | 273 | function setCopyToClipBoardButtonPosition(config) { 274 | const button = document.querySelectorAll('.copy-to-clipboard')[0]; 275 | const bottom = Math.max(0, (window.innerHeight - config.height) / 2); 276 | const right = Math.max(0, (window.innerWidth - config.width) / 2); 277 | button.style.bottom = `calc(${bottom}px - 2.5vw)`; 278 | button.style.right = `${right}px`; 279 | } 280 | })(); 281 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const path = require('path'); 4 | const fs = require('fs-extra'); 5 | const meow = require('meow'); 6 | const puppeteer = require('puppeteer'); 7 | const liveServer = require('live-server'); 8 | const chalk = require('chalk'); 9 | const ora = require('ora'); 10 | 11 | const cli = meow({ 12 | description: false, 13 | help: ` 14 | ${chalk.bold('NAME')} 15 | 16 | ${chalk.bold('onepunch')} -- create presentations with web technology 17 | 18 | ${chalk.bold('DESCRIPTION')} 19 | 20 | ${chalk.bold('onepunch')} is a command-line interface useful to create presentations with web technology. 21 | Moreover, thanks to puppeteer, ${chalk.bold('onepunch')} can print the presentation in a PDF file. 22 | 23 | ${chalk.bold('onepunch')} is designed for designers, it does not provide any default style. 24 | Designers can define custom styles by writing CSS files and linking them in the ${chalk.underline('index.html')}. 25 | 26 | ${chalk.bold('onepunch')} is open source software licensed under the MIT License, 27 | please visit https://github.com/nextbitlabs/onepunch for further details. 28 | 29 | ${chalk.bold('SYNOPSIS')} 30 | 31 | ${chalk.bold('onepunch init')} [${chalk.bold('-n')} ${chalk.italic('directory_name')}] 32 | Initialize a presentation. 33 | 34 | ${chalk.bold('onepunch serve')} [${chalk.bold('-i')} ${chalk.italic('htmlfile')}] 35 | Open the presentation in the browser. 36 | 37 | ${chalk.bold('onepunch print')} [${chalk.bold('-i')} ${chalk.italic('htmlfile')}] [${chalk.bold('-o')} ${chalk.italic('pdffile')}] 38 | Print the presentation in a PDF file. 39 | 40 | ${chalk.bold('onepunch update')} 41 | Update files in the ${chalk.underline('src')} directory according to the latest release. 42 | Please note that any custom change inside directory ${chalk.underline('src')} will be overwritten. 43 | 44 | ${chalk.bold('OPTIONS')} 45 | 46 | ${chalk.bold('-n')} or ${chalk.bold('--name')} ${chalk.italic('directory_name')} 47 | Specify the name of the directory where the project is initialized. 48 | Defaults to ${chalk.underline('onepunch-presentation')}. 49 | 50 | ${chalk.bold('-i')} or ${chalk.bold('--input')} ${chalk.italic('htmlfile')} 51 | Specify the HTML file to serve or print, defaults to ${chalk.underline('index.html')}. 52 | 53 | ${chalk.bold('-o')} or ${chalk.bold('--output')} ${chalk.italic('pdffile')} 54 | Specify the name of the PDF file in output, defaults to ${chalk.underline('index.pdf')}. 55 | 56 | ${chalk.bold('--version')} 57 | Display the version number. 58 | 59 | ${chalk.bold('--help')} 60 | Display the documentation. 61 | 62 | ${chalk.bold('FILES')} 63 | 64 | File ${chalk.underline('onepunch.json')} defines config settings as key-value pairs in JSON format. 65 | The following keys are provided: 66 | 67 | ${chalk.bold('width')}: 68 | Describe the slide width in pixels, defaults to 960. 69 | 70 | ${chalk.bold('height')}: 71 | Describe the slide height in pixels, defaults to 600. 72 | 73 | ${chalk.bold('progress')}: 74 | Describe the presentation progress. At the moment only values "line" 75 | and "none" are supported. 76 | 77 | ${chalk.bold('date')}: 78 | Define the text content of HTML elements with data attribute 79 | data-onepunch="date", such as . 80 | 81 | ${chalk.bold('slideNumber')}: 82 | If true, show the slide number in HTML elements with data attribute 83 | data-onepunch="slide-number", such as . 84 | 85 | ${chalk.bold('LICENSE')} 86 | 87 | Copyright 2020 Nextbit (https://nextbit.it/) 88 | 89 | Permission is hereby granted, free of charge, to any person obtaining a copy 90 | of this software and associated documentation files (the "Software"), to deal 91 | in the Software without restriction, including without limitation the rights 92 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 93 | copies of the Software, and to permit persons to whom the Software is 94 | furnished to do so, subject to the following conditions: 95 | 96 | The above copyright notice and this permission notice shall be included in 97 | all copies or substantial portions of the Software. 98 | 99 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 100 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 101 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 102 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 103 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 104 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 105 | THE SOFTWARE. 106 | `, 107 | flags: { 108 | name: { 109 | type: 'string', 110 | default: 'onepunch-presentation', 111 | alias: 'n', 112 | }, 113 | input: { 114 | type: 'string', 115 | default: 'index.html', 116 | alias: 'i', 117 | }, 118 | output: { 119 | type: 'string', 120 | default: 'index.pdf', 121 | alias: 'o', 122 | }, 123 | }, 124 | }); 125 | 126 | switch (cli.input[0]) { 127 | case 'init': 128 | init(cli.flags); 129 | break; 130 | case 'serve': 131 | serve(cli.flags); 132 | break; 133 | case 'print': 134 | print(cli.flags); 135 | break; 136 | case 'update': 137 | update(); 138 | break; 139 | default: 140 | cli.showHelp(); 141 | break; 142 | } 143 | 144 | function serve(flags) { 145 | liveServer.start({ 146 | port: 8180, 147 | root: process.cwd(), 148 | open: `/${flags.input}`, 149 | logLevel: 1, 150 | }); 151 | } 152 | 153 | function init(flags) { 154 | const spinner = ora('Initializing ...').start(); 155 | const {name} = flags; 156 | const presentationPath = path.resolve(name); 157 | 158 | if (fs.existsSync(presentationPath)) { 159 | abort(`The directory ${chalk.underline(name)} is already existing.`, spinner); 160 | } 161 | 162 | fs.mkdirSync(presentationPath); 163 | fs.copySync(path.resolve(__dirname, 'template'), presentationPath); 164 | spinner.succeed(`Directory ${chalk.underline(name)} has been initialized.`); 165 | } 166 | 167 | function update() { 168 | const spinner = ora('Updating ...').start(); 169 | const file = 'onepunch.json'; 170 | 171 | if (!fs.existsSync(file)) { 172 | abort(`File ${chalk.underline('onepunch.json')} is not present. Are you sure this is the right directory?`, spinner); 173 | } 174 | 175 | fs.copySync( 176 | path.resolve(__dirname, 'template/src'), 177 | 'src', 178 | {overwrite: true}, 179 | ); 180 | spinner.succeed(`Directory ${chalk.underline('src')} has been updated to release ${cli.pkg.version}.`); 181 | } 182 | 183 | function print(flags) { 184 | const spinner = ora('Printing ...').start(); 185 | const {input, output} = flags; 186 | 187 | liveServer.start({ 188 | port: 8181, 189 | root: process.cwd(), 190 | open: false, 191 | logLevel: 0, 192 | }); 193 | 194 | const config = JSON.parse(fs.readFileSync('onepunch.json')); 195 | const width = config.width || 960; 196 | const height = config.height || 600; 197 | 198 | (async () => { 199 | const browser = await puppeteer.launch(); 200 | const page = await browser.newPage(); 201 | await page.goto( 202 | `http://127.0.0.1:8181/${input}`, 203 | {waitUntil: 'networkidle0'}, 204 | ); 205 | await page.pdf({ 206 | path: output, 207 | width, 208 | height, 209 | printBackground: true, 210 | }); 211 | await browser.close(); 212 | liveServer.shutdown(); 213 | spinner.succeed(`File ${chalk.underline(output)} has been successfully created.`); 214 | })(); 215 | } 216 | 217 | function abort(message, spinner = null) { 218 | if (spinner) { 219 | spinner.fail(message); 220 | } else { 221 | console.error(message); 222 | } 223 | 224 | console.error('Aborting.'); 225 | process.exit(1); 226 | } 227 | --------------------------------------------------------------------------------