├── .gitignore ├── assets ├── dark.css ├── light.css ├── main.js ├── paper.css ├── prism-coy.css ├── prism-dark.css ├── prism-funky.css ├── prism-okaidia.css ├── prism-solarizedlight.css ├── prism-tomorrow.css └── prism-twilight.css ├── demo ├── bullshit.png ├── demo.md ├── index.html ├── light.html └── paper.html ├── package-lock.json ├── package.json ├── readme.md ├── rollup.config.js ├── scripts ├── demo.sh ├── minifyPrismCss.ts └── updatePrismLangs.ts ├── src ├── bullshit │ ├── index.ts │ └── terms.ts ├── index.ts ├── render │ ├── createElement.ts │ ├── createSlide.ts │ ├── highlight.ts │ └── index.ts ├── test │ ├── bullshit.spec.ts │ ├── parseDsv.spec.ts │ └── parseMd.spec.ts ├── types.d.ts └── utils │ ├── files.ts │ ├── parseCmd.ts │ ├── parseDsv.ts │ └── parseMd.ts ├── src_client ├── index.ts ├── page.ts └── throttle.ts ├── src_style ├── dark.scss ├── light.scss ├── main.scss └── paper.scss └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | dist 4 | try_stuff 5 | assets/images 6 | .test.html -------------------------------------------------------------------------------- /assets/dark.css: -------------------------------------------------------------------------------- 1 | *{box-sizing:border-box;scrollbar-width:none}html,body{margin:0;padding:0}html,body,.presentation,.slide{width:100vw;height:100vh}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:2.5vw;background-color:#191919;color:#fff}a{color:inherit}.presentation{scroll-snap-type:mandatory;scroll-snap-points-y:repeat(100vh);scroll-snap-type:y mandatory}.slide,.slide-container{scroll-snap-align:start;display:flex;justify-content:center;align-items:center;text-align:justify;overflow:hidden}.slide{padding:5%}.slide img{max-width:100vw;max-height:100vh}.slide-with-image{display:flex;flex-direction:column;height:100%;width:100%;text-align:center}.slide-container,.slide-with-code{width:100%}.slide-with-image .image,.slide-with-image .mermaid,.slide-container .codeblock{flex-grow:1;width:100%;background-size:contain;background-repeat:no-repeat;background-position:center center}.codeblock{overflow:scroll}.text strong{font-weight:inherit;color:#c25959}.text em{font-weight:bolder}.text blockquote{font-style:italic;color:#cfcfcf}pre{font-size:0.8em;width:100%;padding-left:0.5em;border-radius:0.1em;text-align:left;background-color:#1a1d25}.codeblock-small pre{font-size:1em}.codeblock-big pre{font-size:0.5em}table{border-collapse:collapse}table.codeblock{font-size:0.8em}table.codeblock-big{font-size:0.6em}tbody tr:nth-child(odd){background-color:#1a1d25}tbody tr:hover{background-color:#c25959;color:#191919}table td,table th{border:1px solid #fff}td,th{padding:0.3em} 2 | -------------------------------------------------------------------------------- /assets/light.css: -------------------------------------------------------------------------------- 1 | *{box-sizing:border-box;scrollbar-width:none}html,body{margin:0;padding:0}html,body,.presentation,.slide{width:100vw;height:100vh}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:2.5vw;background-color:#fff;color:#191919}a{color:inherit}.presentation{scroll-snap-type:mandatory;scroll-snap-points-y:repeat(100vh);scroll-snap-type:y mandatory}.slide,.slide-container{scroll-snap-align:start;display:flex;justify-content:center;align-items:center;text-align:justify;overflow:hidden}.slide{padding:5%}.slide img{max-width:100vw;max-height:100vh}.slide-with-image{display:flex;flex-direction:column;height:100%;width:100%;text-align:center}.slide-container,.slide-with-code{width:100%}.slide-with-image .image,.slide-with-image .mermaid,.slide-container .codeblock{flex-grow:1;width:100%;background-size:contain;background-repeat:no-repeat;background-position:center center}.codeblock{overflow:scroll}.text strong{font-weight:inherit;color:#c22121}.text em{font-weight:bolder}.text blockquote{font-style:italic;color:#3a3a3a}pre{font-size:0.8em;width:100%;padding-left:0.5em;border-radius:0.1em;text-align:left;background-color:#f8f6e6}.codeblock-small pre{font-size:1em}.codeblock-big pre{font-size:0.5em}table{border-collapse:collapse}table.codeblock{font-size:0.8em}table.codeblock-big{font-size:0.6em}tbody tr:nth-child(odd){background-color:#f8f6e6}tbody tr:hover{background-color:#c22121;color:#fff}table td,table th{border:1px solid #191919}td,th{padding:0.3em} 2 | -------------------------------------------------------------------------------- /assets/main.js: -------------------------------------------------------------------------------- 1 | var initPaging=function(e){function t(n){return e[n].scrollIntoView({behavior:"smooth"})}var r=0,n=e.length;return{next:function(){t(r=r===n-1?0:r+1)},prev:function(){r=0===r?r=n-1:r-1,t(r)},goToPage:function(n){t(r=n)},getPage:function(){return r}}},throttle=function(e,n){void 0===e&&(e=200);var t,r=0,o=n;return{onChange:function(n){n!==r&&(r=n,t&&clearTimeout(t),t=setTimeout(function(){return o(Math.round(n))},e))}}},onKeydown=function(e,t){return function(n){switch(n.key){case"ArrowDown":return n.preventDefault(),t();case"ArrowLeft":return n.preventDefault(),e();case"ArrowRight":return n.preventDefault(),t();case"ArrowUp":return n.preventDefault(),e();default:return null}}},onLoad=function(){var n=Array.from(document.getElementsByClassName("slide")),e=initPaging(n),t=e.next,r=e.prev,o=e.goToPage,i=e.getPage;window.addEventListener("keydown",onKeydown(r,t));var u=throttle(100,o);window.addEventListener("scroll",function(){return u.onChange(window.pageYOffset/window.innerHeight)}),window.addEventListener("resize",function(){return o(i())})};window.onload=onLoad; 2 | -------------------------------------------------------------------------------- /assets/paper.css: -------------------------------------------------------------------------------- 1 | *{box-sizing:border-box;scrollbar-width:none}html,body{margin:0;padding:0}html,body,.presentation,.slide{width:100vw;height:100vh}body{font-family:"Source Serif Pro",Iowan Old Style,Apple Garamond,Palatino Linotype,Times New Roman,"Droid Serif",Times,serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:2.5vw;background-color:#f5f4e6;color:#332020}a{color:inherit}.presentation{scroll-snap-type:mandatory;scroll-snap-points-y:repeat(100vh);scroll-snap-type:y mandatory}.slide,.slide-container{scroll-snap-align:start;display:flex;justify-content:center;align-items:center;text-align:justify;overflow:hidden}.slide{padding:5%}.slide img{max-width:100vw;max-height:100vh}.slide-with-image{display:flex;flex-direction:column;height:100%;width:100%;text-align:center}.slide-container,.slide-with-code{width:100%}.slide-with-image .image,.slide-with-image .mermaid,.slide-container .codeblock{flex-grow:1;width:100%;background-size:contain;background-repeat:no-repeat;background-position:center center}.codeblock{overflow:scroll}.text strong{font-weight:inherit;color:#802626}.text em{font-weight:bolder}.text blockquote{font-style:italic;color:#3a3a3a}pre{font-size:0.8em;width:100%;padding-left:0.5em;border-radius:0.1em;text-align:left;background-color:#f8f8f2}.codeblock-small pre{font-size:1em}.codeblock-big pre{font-size:0.5em}table{border-collapse:collapse}table.codeblock{font-size:0.8em}table.codeblock-big{font-size:0.6em}tbody tr:nth-child(odd){background-color:#f8f8f2}tbody tr:hover{background-color:#802626;color:#f5f4e6}table td,table th{border:1px solid #332020}td,th{padding:0.3em} 2 | -------------------------------------------------------------------------------- /assets/prism-coy.css: -------------------------------------------------------------------------------- 1 | code[class*="language-"],pre[class*="language-"]{color:black;background:0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*="language-"]{position:relative;margin:.5em 0;overflow:visible;padding:0}pre[class*="language-"]>code{position:relative;border-left:10px solid #358ccb;box-shadow:-1px 0 0 0 #358ccb,0 0 0 1px #dfdfdf;background-color:#fdfdfd;background-image:linear-gradient(transparent 50%,rgba(69,142,209,0.04) 50%);background-size:3em 3em;background-origin:content-box;background-attachment:local}code[class*="language"]{max-height:inherit;height:inherit;padding:0 1em;display:block;overflow:auto}:not(pre)>code[class*="language-"],pre[class*="language-"]{background-color:#fdfdfd;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin-bottom:1em}:not(pre)>code[class*="language-"]{position:relative;padding:.2em;border-radius:.3em;color:#c92c2c;border:1px solid rgba(0,0,0,0.1);display:inline;white-space:normal}pre[class*="language-"]:before,pre[class*="language-"]:after{content:'';z-index:-2;display:block;position:absolute;bottom:.75em;left:.18em;width:40%;height:20%;max-height:13em;box-shadow:0 13px 8px #979797;-webkit-transform:rotate(-2deg);-moz-transform:rotate(-2deg);-ms-transform:rotate(-2deg);-o-transform:rotate(-2deg);transform:rotate(-2deg)}:not(pre)>code[class*="language-"]:after,pre[class*="language-"]:after{right:.75em;left:auto;-webkit-transform:rotate(2deg);-moz-transform:rotate(2deg);-ms-transform:rotate(2deg);-o-transform:rotate(2deg);transform:rotate(2deg)}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#7d8b99}.token.punctuation{color:#5f6364}.token.property,.token.tag,.token.boolean,.token.number,.token.function-name,.token.constant,.token.symbol,.token.deleted{color:#c92c2c}.token.selector,.token.attr-name,.token.string,.token.char,.token.function,.token.builtin,.token.inserted{color:#2f9c0a}.token.operator,.token.entity,.token.url,.token.variable{color:#a67f59;background:rgba(255,255,255,0.5)}.token.atrule,.token.attr-value,.token.keyword,.token.class-name{color:#1990b8}.token.regex,.token.important{color:#e90}.language-css .token.string,.style .token.string{color:#a67f59;background:rgba(255,255,255,0.5)}.token.important{font-weight:normal}.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.token.namespace{opacity:.7}@media screen and (max-width:767px){pre[class*="language-"]:before,pre[class*="language-"]:after{bottom:14px;box-shadow:none}}.token.tab:not(:empty):before,.token.cr:before,.token.lf:before{color:#e0d7d1}pre[class*="language-"].line-numbers.line-numbers{padding-left:0}pre[class*="language-"].line-numbers.line-numbers code{padding-left:3.8em}pre[class*="language-"].line-numbers.line-numbers .line-numbers-rows{left:0}pre[class*="language-"][data-line]{padding-top:0;padding-bottom:0;padding-left:0}pre[data-line] code{position:relative;padding-left:4em}pre .line-highlight{margin-top:0} 2 | -------------------------------------------------------------------------------- /assets/prism-dark.css: -------------------------------------------------------------------------------- 1 | code[class*="language-"],pre[class*="language-"]{color:white;background:0;text-shadow:0 -.1em .2em black;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}@media print{code[class*="language-"],pre[class*="language-"]{text-shadow:none}}pre[class*="language-"],:not(pre)>code[class*="language-"]{background:hsl(30,20%,25%)}pre[class*="language-"]{padding:1em;margin:.5em 0;overflow:auto;border:.3em solid hsl(30,20%,40%);border-radius:.5em;box-shadow:1px 1px .5em black inset}:not(pre)>code[class*="language-"]{padding:.15em .2em .05em;border-radius:.3em;border:.13em solid hsl(30,20%,40%);box-shadow:1px 1px .3em -.1em black inset;white-space:normal}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:hsl(30,20%,50%)}.token.punctuation{opacity:.7}.token.namespace{opacity:.7}.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol{color:hsl(350,40%,70%)}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted{color:hsl(75,70%,60%)}.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string,.token.variable{color:hsl(40,90%,60%)}.token.atrule,.token.attr-value,.token.keyword{color:hsl(350,40%,70%)}.token.regex,.token.important{color:#e90}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.token.deleted{color:red} 2 | -------------------------------------------------------------------------------- /assets/prism-funky.css: -------------------------------------------------------------------------------- 1 | code[class*="language-"],pre[class*="language-"]{font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*="language-"]{padding:.4em .8em;margin:.5em 0;overflow:auto;background:url('data:image/svg+xml;charset=utf-8,%0D%0A%0D%0A%0D%0A<%2Fsvg>');background-size:1em 1em}code[class*="language-"]{background:black;color:white;box-shadow:-.3em 0 0 .3em black,.3em 0 0 .3em black}:not(pre)>code[class*="language-"]{padding:.2em;border-radius:.3em;box-shadow:none;white-space:normal}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:#aaa}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol{color:#0cf}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin{color:yellow}.token.operator,.token.entity,.token.url,.language-css .token.string,.token.variable,.token.inserted{color:yellowgreen}.token.atrule,.token.attr-value,.token.keyword{color:deeppink}.token.regex,.token.important{color:orange}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.token.deleted{color:red}pre.diff-highlight.diff-highlight>code .token.deleted:not(.prefix),pre>code.diff-highlight.diff-highlight .token.deleted:not(.prefix){background-color:rgba(255,0,0,.3);display:inline}pre.diff-highlight.diff-highlight>code .token.inserted:not(.prefix),pre>code.diff-highlight.diff-highlight .token.inserted:not(.prefix){background-color:rgba(0,255,128,.3);display:inline} 2 | -------------------------------------------------------------------------------- /assets/prism-okaidia.css: -------------------------------------------------------------------------------- 1 | code[class*="language-"],pre[class*="language-"]{color:#f8f8f2;background:0;text-shadow:0 1px rgba(0,0,0,0.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*="language-"]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*="language-"],pre[class*="language-"]{background:#272822}:not(pre)>code[class*="language-"]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:slategray}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.property,.token.tag,.token.constant,.token.symbol,.token.deleted{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted{color:#a6e22e}.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.function,.token.class-name{color:#e6db74}.token.keyword{color:#66d9ef}.token.regex,.token.important{color:#fd971f}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help} 2 | -------------------------------------------------------------------------------- /assets/prism-solarizedlight.css: -------------------------------------------------------------------------------- 1 | code[class*="language-"],pre[class*="language-"]{color:#657b83;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*="language-"]::-moz-selection,pre[class*="language-"] ::-moz-selection,code[class*="language-"]::-moz-selection,code[class*="language-"] ::-moz-selection{background:#073642}pre[class*="language-"]::selection,pre[class*="language-"] ::selection,code[class*="language-"]::selection,code[class*="language-"] ::selection{background:#073642}pre[class*="language-"]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*="language-"],pre[class*="language-"]{background-color:#fdf6e3}:not(pre)>code[class*="language-"]{padding:.1em;border-radius:.3em}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:#93a1a1}.token.punctuation{color:#586e75}.token.namespace{opacity:.7}.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol,.token.deleted{color:#268bd2}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.url,.token.inserted{color:#2aa198}.token.entity{color:#657b83;background:#eee8d5}.token.atrule,.token.attr-value,.token.keyword{color:#859900}.token.function,.token.class-name{color:#b58900}.token.regex,.token.important,.token.variable{color:#cb4b16}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help} 2 | -------------------------------------------------------------------------------- /assets/prism-tomorrow.css: -------------------------------------------------------------------------------- 1 | code[class*="language-"],pre[class*="language-"]{color:#ccc;background:0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*="language-"]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*="language-"],pre[class*="language-"]{background:#2d2d2d}:not(pre)>code[class*="language-"]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#999}.token.punctuation{color:#ccc}.token.tag,.token.attr-name,.token.namespace,.token.deleted{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green} 2 | -------------------------------------------------------------------------------- /assets/prism-twilight.css: -------------------------------------------------------------------------------- 1 | code[class*="language-"],pre[class*="language-"]{color:white;background:0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;text-shadow:0 -.1em .2em black;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*="language-"],:not(pre)>code[class*="language-"]{background:hsl(0,0%,8%)}pre[class*="language-"]{border-radius:.5em;border:.3em solid hsl(0,0%,33%);box-shadow:1px 1px .5em black inset;margin:.5em 0;overflow:auto;padding:1em}pre[class*="language-"]::-moz-selection{background:hsl(200,4%,16%)}pre[class*="language-"]::selection{background:hsl(200,4%,16%)}pre[class*="language-"]::-moz-selection,pre[class*="language-"] ::-moz-selection,code[class*="language-"]::-moz-selection,code[class*="language-"] ::-moz-selection{text-shadow:none;background:hsla(0,0%,93%,0.15)}pre[class*="language-"]::selection,pre[class*="language-"] ::selection,code[class*="language-"]::selection,code[class*="language-"] ::selection{text-shadow:none;background:hsla(0,0%,93%,0.15)}:not(pre)>code[class*="language-"]{border-radius:.3em;border:.13em solid hsl(0,0%,33%);box-shadow:1px 1px .3em -.1em black inset;padding:.15em .2em .05em;white-space:normal}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:hsl(0,0%,47%)}.token.punctuation{opacity:.7}.token.namespace{opacity:.7}.token.tag,.token.boolean,.token.number,.token.deleted{color:hsl(14,58%,55%)}.token.keyword,.token.property,.token.selector,.token.constant,.token.symbol,.token.builtin{color:hsl(53,89%,79%)}.token.attr-name,.token.attr-value,.token.string,.token.char,.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string,.token.variable,.token.inserted{color:hsl(76,21%,52%)}.token.atrule{color:hsl(218,22%,55%)}.token.regex,.token.important{color:hsl(42,75%,65%)}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}pre[data-line]{padding:1em 0 1em 3em;position:relative}.language-markup .token.tag,.language-markup .token.attr-name,.language-markup .token.punctuation{color:hsl(33,33%,52%)}.token{position:relative;z-index:1}.line-highlight{background:hsla(0,0%,33%,0.25);background:linear-gradient(to right,hsla(0,0%,33%,.1) 70%,hsla(0,0%,33%,0));border-bottom:1px dashed hsl(0,0%,33%);border-top:1px dashed hsl(0,0%,33%);left:0;line-height:inherit;margin-top:.75em;padding:inherit 0;pointer-events:none;position:absolute;right:0;white-space:pre;z-index:0}.line-highlight:before,.line-highlight[data-end]:after{background-color:hsl(215,15%,59%);border-radius:999px;box-shadow:0 1px white;color:hsl(24,20%,95%);content:attr(data-start);font:bold 65%/1.5 sans-serif;left:.6em;min-width:1em;padding:0 .5em;position:absolute;text-align:center;text-shadow:none;top:.4em;vertical-align:.3em}.line-highlight[data-end]:after{bottom:.4em;content:attr(data-end);top:auto} 2 | -------------------------------------------------------------------------------- /demo/bullshit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idris-maps/middle-manager/faf426890fad55b30836c81a0a49e5c105809452/demo/bullshit.png -------------------------------------------------------------------------------- /demo/demo.md: -------------------------------------------------------------------------------- 1 | # middle-manager 2 | 3 | *The no bullshit presentation tool* 4 | 5 | --- 6 | 7 | ## Write your slides in **markdown** 8 | 9 | ### *What is markdown?* 10 | 11 | A simple markup language. Checkout this [cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) and [the source of this presentation](https://raw.githubusercontent.com/idris-maps/middle-manager/master/demo/demo.md). 12 | 13 | ### *How are the slides separated?* 14 | 15 | With three dashes. 16 | 17 | --- 18 | 19 | ## Cut the bullshit 20 | 21 | We help you be clear, concise and avoid unnecessary fluff. `middle-manager` always shows you a *bullshit score*™, ranking the amount of bullshit in your presentation between 0 and 1000. 22 | 23 | To see which words are nonsense, add the `-bs` flag to your command. 24 | 25 | ```bash 26 | middle-manager -md my-presentation.md -o my-presentation.html -bs 27 | ``` 28 | 29 | They will be replaced by the literal word *bullshit*. 30 | 31 | --- 32 | 33 | ### Disrupt your processes 34 | 35 | This amazing presentation tool helps you increase your productivity, communicate with all stakeholders and become the forward-thinking thought-leader you always dreamt of being. 36 | 37 | ### Bullshit your bullshits 38 | 39 | This bullshitting presentation tool helps you increase your bullshit, bullshit with all bullshitters and become the bullshitting bullshitter you always dreamt of being. 40 | 41 | --- 42 | 43 | ## Markdown tables are a pain 44 | 45 | Use `csv`,`tsv` or `dsv` code blocks instead. 46 | 47 | ```csv 48 | Index,Item,Cost,Tax,Total 49 | 1,"Fruit of the Loom Girl's Socks",7.97,0.60,8.57 50 | 2,"Rawlings Little League Baseball",2.97,0.22,3.19 51 | 3,"Secret Antiperspirant",1.29,0.10,1.39 52 | 4,"Deadpool DVD",14.96,1.12,16.08 53 | 5,"Maxwell House Coffee 28 oz",7.28,0.55,7.83 54 | 6,"Banana Boat Sunscreen, 8 oz",6.68,0.50,7.18 55 | 7,"WrenchSet, 18 pieces",10.00,0.75,10.75 56 | 8,"Mand M, 42 oz",8.98,0.67,9.65 57 | 9,"Bertoli Alfredo Sauce",2.12,0.16,2.28 58 | 10,"Large Paperclips, 10 boxes",6.19,0.46,6.65 59 | ``` 60 | 61 | --- 62 | 63 | ## Other code blocks are highlighted 64 | 65 | ```ts 66 | const onLoad = () => { 67 | const slides = Array.from(document.getElementsByClassName('slide')) 68 | const { next, prev, goToPage, getPage } = initPaging(slides) 69 | window.addEventListener('keydown', onKeydown(prev, next)) 70 | const scroll = throttle(100, goToPage) 71 | window.addEventListener('scroll', () => scroll.onChange(window.pageYOffset / window.innerHeight)) 72 | window.addEventListener('resize', () => goToPage(getPage())) 73 | } 74 | ``` 75 | 76 | --- 77 | 78 | ## 🎉 You can use emojis 🦄 79 | 80 | But really, you should not. 81 | 82 | ### [Try `middle-manager` now!](https://github.com/idris-maps/middle-manager) 83 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | middlemanagerThenobullshitpresentationtool

middle-manager

5 |

The no bullshit presentation tool

6 |

Write your slides in markdown

7 |

What is markdown?

8 |

A simple markup language. Checkout this cheatsheet and the source of this presentation.

9 |

How are the slides separated?

10 |

With three dashes.

11 |

Cut the bullshit

12 |

We help you be clear, concise and avoid unnecessary fluff. middle-manager always shows you a bullshit score™, ranking the amount of bullshit in your presentation between 0 and 1000.

13 |

To see which words are nonsense, add the -bs flag to your command.

14 |
middle-manager -md my-presentation.md -o my-presentation.html -bs

They will be replaced by the literal word bullshit.

15 |

Disrupt your processes

16 |

This amazing presentation tool helps you increase your productivity, communicate with all stakeholders and become the forward-thinking thought-leader you always dreamt of being.

17 |

Bullshit your bullshits

18 |

This bullshitting presentation tool helps you increase your bullshit, bullshit with all bullshitters and become the bullshitting bullshitter you always dreamt of being.

19 |

Markdown tables are a pain

20 |

Use csv,tsv or dsv code blocks instead.

21 |
IndexItemCostTaxTotal
1Fruit of the Loom Girl's Socks7.970.608.57
2Rawlings Little League Baseball2.970.223.19
3Secret Antiperspirant1.290.101.39
4Deadpool DVD14.961.1216.08
5Maxwell House Coffee 28 oz7.280.557.83
6Banana Boat Sunscreen, 8 oz6.680.507.18
7WrenchSet, 18 pieces10.000.7510.75
8Mand M, 42 oz8.980.679.65
9Bertoli Alfredo Sauce2.120.162.28
10Large Paperclips, 10 boxes6.190.466.65

Other code blocks are highlighted

22 |
const onLoad = () => {
23 |   const slides = Array.from(document.getElementsByClassName('slide'))
24 |   const { next, prev, goToPage, getPage } = initPaging(slides)
25 |   window.addEventListener('keydown', onKeydown(prev, next))
26 |   const scroll = throttle(100, goToPage)
27 |   window.addEventListener('scroll', () => scroll.onChange(window.pageYOffset / window.innerHeight))
28 |   window.addEventListener('resize', () => goToPage(getPage()))
29 | }

🎉 You can use emojis 🦄

30 |

But really, you should not.

31 |

Try middle-manager now!

32 |
34 | -------------------------------------------------------------------------------- /demo/light.html: -------------------------------------------------------------------------------- 1 | 2 | middlemanagerThenobullshitpresentationtool

middle-manager

5 |

The no bullshit presentation tool

6 |

Write your slides in markdown

7 |

What is markdown?

8 |

A simple markup language. Checkout this cheatsheet and the source of this presentation.

9 |

How are the slides separated?

10 |

With three dashes.

11 |

Cut the bullshit

12 |

We help you be clear, concise and avoid unnecessary fluff. middle-manager always shows you a bullshit score™, ranking the amount of bullshit in your presentation between 0 and 1000.

13 |

To see which words are nonsense, add the -bs flag to your command.

14 |
middle-manager -md my-presentation.md -o my-presentation.html -bs

They will be replaced by the literal word bullshit.

15 |

Disrupt your processes

16 |

This amazing presentation tool helps you increase your productivity, communicate with all stakeholders and become the forward-thinking thought-leader you always dreamt of being.

17 |

Bullshit your bullshits

18 |

This bullshitting presentation tool helps you increase your bullshit, bullshit with all bullshitters and become the bullshitting bullshitter you always dreamt of being.

19 |

Markdown tables are a pain

20 |

Use csv,tsv or dsv code blocks instead.

21 |
IndexItemCostTaxTotal
1Fruit of the Loom Girl's Socks7.970.608.57
2Rawlings Little League Baseball2.970.223.19
3Secret Antiperspirant1.290.101.39
4Deadpool DVD14.961.1216.08
5Maxwell House Coffee 28 oz7.280.557.83
6Banana Boat Sunscreen, 8 oz6.680.507.18
7WrenchSet, 18 pieces10.000.7510.75
8Mand M, 42 oz8.980.679.65
9Bertoli Alfredo Sauce2.120.162.28
10Large Paperclips, 10 boxes6.190.466.65

Other code blocks are highlighted

22 |
const onLoad = () => {
23 |   const slides = Array.from(document.getElementsByClassName('slide'))
24 |   const { next, prev, goToPage, getPage } = initPaging(slides)
25 |   window.addEventListener('keydown', onKeydown(prev, next))
26 |   const scroll = throttle(100, goToPage)
27 |   window.addEventListener('scroll', () => scroll.onChange(window.pageYOffset / window.innerHeight))
28 |   window.addEventListener('resize', () => goToPage(getPage()))
29 | }

🎉 You can use emojis 🦄

30 |

But really, you should not.

31 |

Try middle-manager now!

32 |
34 | -------------------------------------------------------------------------------- /demo/paper.html: -------------------------------------------------------------------------------- 1 | 2 | middlemanagerThenobullshitpresentationtool

middle-manager

5 |

The no bullshit presentation tool

6 |

Write your slides in markdown

7 |

What is markdown?

8 |

A simple markup language. Checkout this cheatsheet and the source of this presentation.

9 |

How are the slides separated?

10 |

With three dashes.

11 |

Cut the bullshit

12 |

We help you be clear, concise and avoid unnecessary fluff. middle-manager always shows you a bullshit score™, ranking the amount of bullshit in your presentation between 0 and 1000.

13 |

To see which words are nonsense, add the -bs flag to your command.

14 |
middle-manager -md my-presentation.md -o my-presentation.html -bs

They will be replaced by the literal word bullshit.

15 |

Disrupt your processes

16 |

This amazing presentation tool helps you increase your productivity, communicate with all stakeholders and become the forward-thinking thought-leader you always dreamt of being.

17 |

Bullshit your bullshits

18 |

This bullshitting presentation tool helps you increase your bullshit, bullshit with all bullshitters and become the bullshitting bullshitter you always dreamt of being.

19 |

Markdown tables are a pain

20 |

Use csv,tsv or dsv code blocks instead.

21 |
IndexItemCostTaxTotal
1Fruit of the Loom Girl's Socks7.970.608.57
2Rawlings Little League Baseball2.970.223.19
3Secret Antiperspirant1.290.101.39
4Deadpool DVD14.961.1216.08
5Maxwell House Coffee 28 oz7.280.557.83
6Banana Boat Sunscreen, 8 oz6.680.507.18
7WrenchSet, 18 pieces10.000.7510.75
8Mand M, 42 oz8.980.679.65
9Bertoli Alfredo Sauce2.120.162.28
10Large Paperclips, 10 boxes6.190.466.65

Other code blocks are highlighted

22 |
const onLoad = () => {
23 |   const slides = Array.from(document.getElementsByClassName('slide'))
24 |   const { next, prev, goToPage, getPage } = initPaging(slides)
25 |   window.addEventListener('keydown', onKeydown(prev, next))
26 |   const scroll = throttle(100, goToPage)
27 |   window.addEventListener('scroll', () => scroll.onChange(window.pageYOffset / window.innerHeight))
28 |   window.addEventListener('resize', () => goToPage(getPage()))
29 | }

🎉 You can use emojis 🦄

30 |

But really, you should not.

31 |

Try middle-manager now!

32 |
34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "middle-manager", 3 | "version": "1.0.8", 4 | "main": "dist/index.js", 5 | "keywords": [ 6 | "presentation", 7 | "deck", 8 | "markdown" 9 | ], 10 | "description": "The no bullshit presentation tool", 11 | "bin": { 12 | "middle-manager": "./dist/index.js" 13 | }, 14 | "scripts": { 15 | "test": "ava --verbose", 16 | "update-bullshit-terms": "curl https://raw.githubusercontent.com/mourner/bullshit.js/master/src/terms.js > src/bullshit/terms.ts", 17 | "update-prism-langs": "ts-node scripts/updatePrismLangs", 18 | "build:client": "rollup --config rollup.config.js", 19 | "dev:client": "parcel src_client/index.html", 20 | "scss:dark": "node-sass src_style/dark.scss -o assets --output-style compressed", 21 | "scss:light": "node-sass src_style/light.scss -o assets --output-style compressed", 22 | "scss:paper": "node-sass src_style/paper.scss -o assets --output-style compressed", 23 | "scss": "npm run scss:dark && npm run scss:light && npm run scss:paper", 24 | "build": "tsc && sed -i '1i #!/usr/bin/env node' dist/index.js && chmod -x dist/index.js", 25 | "prepublish": "npm run build", 26 | "demo": "./scripts/demo.sh" 27 | }, 28 | "author": "idris-maps", 29 | "license": "GPL-3.0-or-later", 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/idris-maps/middle-manager.git" 33 | }, 34 | "devDependencies": { 35 | "@types/argparse": "^1.0.38", 36 | "@types/marked": "^0.7.2", 37 | "@types/node": "^13.5.3", 38 | "@types/papaparse": "^5.0.3", 39 | "@types/prismjs": "^1.16.0", 40 | "ava": "^3.5.2", 41 | "node-sass": "^7.0.0", 42 | "rollup": "^1.31.0", 43 | "rollup-plugin-typescript": "^1.0.1", 44 | "rollup-plugin-uglify": "^6.0.4", 45 | "ts-node": "^8.6.2", 46 | "tslib": "^1.10.0", 47 | "typescript": "^3.7.5", 48 | "uglifycss": "0.0.29" 49 | }, 50 | "dependencies": { 51 | "argparse": "^1.0.10", 52 | "marked": "^0.8.0", 53 | "papaparse": "^5.2.0", 54 | "prismjs": "^1.19.0", 55 | "xml-string": "^2.0.2" 56 | }, 57 | "ava": { 58 | "extensions": [ 59 | "ts" 60 | ], 61 | "require": [ 62 | "ts-node/register" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | > "When we understand that slide, we’ll have won the war." - Gen. Stanley A. McChrystal 2 | 3 | > "PowerPoint makes us stupid" - Gen. James N. Mattis 4 | 5 | > "It’s dangerous because it can create the illusion of understanding and the illusion of control. Some problems in the world are not bullet-izable." - Brig. Gen. H. R. McMaster 6 | 7 | [We Have Met the Enemy and He Is PowerPoint](https://www.nytimes.com/2010/04/27/world/27powerpoint.html) 8 | 9 | If you have a meaningful message, you should be able to say it without support. The time you spend preparing slides is time you do not spend solving actual problems. Do you really need to make a presentation? Think again. 10 | 11 | Ok, ok, I get it. It helps you remember what to say. And doing presentations is kind of your job anyway. Then this is the tool for you. 12 | 13 | # middle-manager 14 | 15 | *The no bullshit presentation tool* 16 | 17 | Write your presentation in [markdown](https://www.markdownguide.org/getting-started/), separate your slides with three dashes `---` and run: 18 | 19 | ``` 20 | npx middle-manager -md my-presentation.md -o my-presentation.html 21 | ``` 22 | 23 | or install the package globally: 24 | 25 | ``` 26 | npm install middle-manager -g 27 | ``` 28 | 29 | Checkout the [demo](https://middle-manager.surge.sh). 30 | 31 | ## Cut the bullshit 32 | 33 | We help you be clear, concise and avoid unnecessary fluff. `middle-manager` always shows you a *bullshit score*™, ranking the amount of bullshit in your presentation between 0 and 1000. 34 | 35 | To see which words are nonsense, add the `-bs` flag to your command. 36 | 37 | ```bash 38 | middle-manager -md my-presentation.md -o my-presentation.html -bs 39 | ``` 40 | 41 | They will be replaced by the literal word *bullshit*. 42 | 43 | ![Replace bullshit demo](https://raw.githubusercontent.com/idris-maps/middle-manager/master/demo/bullshit.png) 44 | 45 | We would love to pretend the *bullshit detector*™ is based on a proprietary AI. But it did not pass said detector. It uses the, nonetheless excellent, [bullshit.js](https://mourner.github.io/bullshit.js/) under the hood. 46 | 47 | ## Tables 48 | 49 | Markdown tables are a pain. With `middle-manager`, you can use `csv`, `tsv` or `dsv` code blocks instead. `csv` and `tsv` are respectively for comma and tab separated values. If you paste from a spreadsheet, use `tsv`. 50 | 51 | `dsv` is for any other delimiters. You need to define the delimiter like this `dsv;`, for semicolon separated values for example. 52 | 53 | ## Code highlighting 54 | 55 | Any other code blocks will be highlighted with [`prism`](https://prismjs.com/). 56 | 57 | ## Images 58 | 59 | The html page produced by `middle-manager`, contains all slides, css and script necessary. However images are not included. If you use local images on your machine, make sure you ship them with the page. 60 | 61 | ## Themes 62 | 63 | At the moment there are three themes: 64 | 65 | * [`dark`](https://middle-manager.surge.sh) 66 | * [`light`](https://middle-manager.surge.sh/light.html) 67 | * [`paper`](https://middle-manager.surge.sh/paper.html) 68 | 69 | `dark` is the default. Set another theme with the `-t` flag. 70 | 71 | ``` 72 | middle-manager -md my-presentation.md -o my-presentation.html -t light 73 | ``` 74 | 75 | If you feel like creating a new theme, have a look at the [`src_style`](https://github.com/idris-maps/middle-manager/tree/master/src_style) folder. PRs are welcome. 76 | 77 | ## Why `middle-manager`? 78 | 79 | There are many presentation tools, how does `middle-manager` compare with the competition? 80 | 81 | Desktop tools such as PowerPoint are very powerful. But that is their curse. You will spend a lot of time fiddling with alignment and styling. Using just markdown lets you focus on your message. 82 | 83 | Tools such as [`impress`](https://impress.js.org), [`reveal`](https://revealjs.com) and [`MDX deck`](https://github.com/jxnblk/mdx-deck) are much fancier but come with the javascript creep the modern internet has gotten us used to. `middle-manager`, being a command line tool, produces lightweight presentation decks with minimal overhead. The [demo deck](https://middle-manager.surge.sh/) weighs 10kb. -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript' 2 | import { uglify } from 'rollup-plugin-uglify' 3 | 4 | const plugins = [ 5 | typescript({ target: 'es5' }), 6 | uglify(), 7 | ] 8 | 9 | export default [ 10 | { 11 | input: './src_client/index.ts', 12 | output: { file: 'assets/main.js' }, 13 | plugins, 14 | }, 15 | ] -------------------------------------------------------------------------------- /scripts/demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npx ts-node src/index -md demo/demo.md -o demo/index.html 4 | 5 | npx ts-node src/index -md demo/demo.md -o demo/light.html -t light 6 | 7 | npx ts-node src/index -md demo/demo.md -o demo/paper.html -t paper 8 | 9 | surge demo --domain middle-manager.surge.sh -------------------------------------------------------------------------------- /scripts/minifyPrismCss.ts: -------------------------------------------------------------------------------- 1 | import { readdir } from 'fs' 2 | import { promisify } from 'util' 3 | import { resolve } from 'path' 4 | import { exec } from 'child_process' 5 | 6 | const bash = (cmd: string) => new Promise((resolve, reject) => 7 | exec(cmd, (err, stdout, stderr) => { 8 | if (err) { return reject(err) } 9 | if (stderr) { return reject(stderr) } 10 | resolve() 11 | }) 12 | ) 13 | const pathToSrc = resolve(__dirname, '..', 'node_modules', 'prismjs', 'themes') 14 | const pathToAssets = resolve(__dirname, '..', 'assets') 15 | 16 | const getFiles = () => 17 | promisify(readdir)(pathToSrc) 18 | 19 | const minify = (file: string) => 20 | bash(`npx uglifycss ${resolve(pathToSrc, file)} > ${resolve(pathToAssets, file)}`) 21 | 22 | const run = async () => { 23 | const files = await getFiles() 24 | await Promise.all(files.map(minify)) 25 | console.log('Done minifying prism.css') 26 | } 27 | 28 | run() -------------------------------------------------------------------------------- /scripts/updatePrismLangs.ts: -------------------------------------------------------------------------------- 1 | import { readdir, writeFile, readFile } from 'fs' 2 | import { promisify } from 'util' 3 | import { resolve } from 'path' 4 | 5 | const file = (langs: string[]) => `import prism from 'prismjs' 6 | ${langs.map(d => `import 'prismjs/components/prism-${d}'`).join('\n')} 7 | 8 | export default (code: string, lang: string) => { 9 | try { 10 | return \`
\${prism.highlight(code, prism.languages[String(lang)], String(lang))}
\` 11 | } catch (e) { 12 | console.log(\`Could not highlight code block with language: \${lang}\`, e) 13 | return \`
\${code}
\` 14 | } 15 | } 16 | ` 17 | 18 | const run = async () => { 19 | const files = await promisify(readdir)(resolve(__dirname, '../node_modules/prismjs/components')) 20 | const langs = files.filter(d => d !== 'index.js' && !d.endsWith('.min.js')).map(d => d.split('prism-')[1].split('.js')[0]) 21 | const pathToFile = resolve(__dirname, '../src/render/highlight.ts') 22 | await promisify(writeFile)(pathToFile, file(langs), 'utf-8') 23 | console.log(`Wrote ${pathToFile}`) 24 | const pathToCss = resolve(__dirname, '../assets/prism.css') 25 | await promisify(writeFile)(pathToCss, await promisify(readFile)(resolve(__dirname, '../node_modules/prismjs/themes/prism.css'), 'utf-8'), 'utf-8') 26 | // TODO get rid of google fonts 27 | console.log(`Wrote ${pathToCss}`) 28 | } 29 | 30 | run() -------------------------------------------------------------------------------- /src/bullshit/index.ts: -------------------------------------------------------------------------------- 1 | import terms from './terms' 2 | 3 | const bullshitRe = new RegExp(`\\b(${terms.join('|')})\\b`, 'gi') 4 | 5 | const revealBullshit = (text: string) => { 6 | const c = text.charAt(0) 7 | const last = text.length - 1 8 | const bullshit = `${c === c.toUpperCase() ? 'B' : 'b'}ullshit` 9 | 10 | if ( 11 | (text.charAt(last - 3) !== 'o' || text.charAt(last - 3) !=='e') 12 | && (text.substr(last - 2) === 'ors' || text.substr(last - 2) === 'ers') 13 | ) { 14 | return `${bullshit}ters` 15 | } 16 | if ( 17 | (text.charAt(last - 2) !== 'o' || text.charAt(last - 2) !=='e') 18 | && (text.substr(last - 1) === 'or' || text.substr(last - 1) === 'er') 19 | ) { 20 | return `${bullshit}ter` 21 | } 22 | if (text.substr(last - 2) === 'ing') { 23 | return `${bullshit}ting` 24 | } 25 | if (text.charAt(last - 1) !== 's' && text.charAt(last) === 's') { 26 | return `${bullshit}s` 27 | } 28 | if (text.charAt(last - 2) !== 'e' && text.substr(last - 1) === 'ed') { 29 | return `${bullshit}ted` 30 | } 31 | return bullshit 32 | } 33 | 34 | export const replaceBullshit = (text: string): string => 35 | text.replace(bullshitRe, revealBullshit) 36 | 37 | const countWords = (text: string) => 38 | text 39 | .replace(/(^\s*)|(\s*$)/gi,'') //exclude start and end white-space 40 | .replace(/[ ]{2,}/gi,' ') // 2 or more space to 1 41 | .replace(/\n /,'\n') // exclude newline with a start spacing 42 | .split(' ') 43 | .filter(d => d !== '').length 44 | 45 | export const countBullshit = (text: string) => { 46 | const fixed = replaceBullshit(text) 47 | const words = countWords(fixed) 48 | const bullshit = fixed.toLowerCase().split('bullshit').length - 1 49 | return { 50 | words, 51 | bullshit, 52 | score: Math.round(bullshit / words * 1000), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/bullshit/terms.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | '(business|client|community|culture|customer|data|goal|intelligence|market' + 3 | '|process|quality|results|role|sales|subject|service|user)' + 4 | '.(centric(ity)?|facing|oriented|driven|focused|assessment|service|process|align(ed|ment|ing))', 5 | '10x', 6 | '24/7', 7 | 'ai', 8 | 'agile', 9 | 'ambassador', 10 | 'artificial intelligence', 11 | 'at (your|their) fingertips?', 12 | 'autonomous', 13 | 'a[-/]b testing', 14 | 'acquisition', 15 | 'action items?', 16 | 'act in time', 17 | 'advantages?', 18 | 'agents?', 19 | 'aggregat(e|ion)', 20 | 'accelerate', 21 | 'all.in.one', 22 | 'all.new', 23 | 'amazing', 24 | 'analytics?', 25 | 'application service providers?', 26 | 'as a service', 27 | 'assets?', 28 | 'astonishing', 29 | 'authoritative', 30 | 'automated', 31 | 'augmented', 32 | 'extended reality', 33 | 'award.winning', 34 | 'b2(b|c)', 35 | 'back to the drawing board', 36 | 'ball.?park', 37 | 'band.aid', 38 | 'bandwidth', 39 | '(benefit|gap) analysis', 40 | 'bespoke', 41 | 'best.in.class', 42 | 'best.of.breed', 43 | 'best.practice', 44 | 'big.data', 45 | 'big picture', 46 | 'big thinkers?', 47 | 'block.?chain', 48 | 'blazing(ly)? fast', 49 | 'boil the ocean', 50 | 'bottom.line', 51 | 'bottom.up', 52 | 'boost(s|ing)?', 53 | 'boundless', 54 | 'brain.?storm(ing)?', 55 | 'brain.?dump', 56 | 'brand(s?|ing|ed)', 57 | 'bright', 58 | 'building.trust', 59 | 'bulletproof', 60 | 'burn.rates?', 61 | 'business( cases| plans)', 62 | 'buzz', 63 | 'call to action', 64 | 'capacity', 65 | 'capabilit(y|ies)', 66 | 'capitali(s|z)e', 67 | 'centers? of excellence', 68 | 'challenges?', 69 | 'change agents?', 70 | 'circle the wagons', 71 | 'client-centered', 72 | 'client-centric', 73 | 'client-focused', 74 | 'cloud', 75 | 'cloud native', 76 | 'cloudif(y|ication)', 77 | 'collaborat(e|ion|ive)s?', 78 | 'comfort( zone)?', 79 | 'committee', 80 | 'communicat(e|ion)s?', 81 | 'company-employee.fit', 82 | 'compelling', 83 | 'competitive( advantage)?', 84 | 'connected systems?', 85 | 'complex(ity)?', 86 | 'comprehensive', 87 | 'connect the dots', 88 | 'container orchestration', 89 | 'containerizat(e|ion|ing)', 90 | 'content management', 91 | 'contextual(ly)?', 92 | 'contingency plans?', 93 | 'control groups?', 94 | 'control plane', 95 | 'convergence', 96 | 'convergent', 97 | 'conversions?', 98 | 'core business', 99 | 'core competenc(y|ies)', 100 | 'core.to.edge', 101 | 'cosmic', 102 | 'cost-effective', 103 | 'cost/benefit', 104 | 'cost control', 105 | 'craftsmanship', 106 | 'critical path', 107 | 'crypto.currency', 108 | 'crypto(?!graphy).\\w+', 109 | 'crm', 110 | 'cross.sell', 111 | 'crowd.?(fund(s?|ed|ing)|sourc(ed|e|ing))', 112 | 'customer obsession', 113 | 'cutting.edge', 114 | 'cyber', 115 | 'dashboards?', 116 | 'dashboarding', 117 | 'data mining', 118 | 'decentrali(s|z)ed', 119 | 'de-?dupe', 120 | 'deep dive', 121 | 'deep learning', 122 | 'delight', 123 | 'deliverables?', 124 | 'demographic', 125 | 'demystify', 126 | 'demystifying', 127 | 'deployless', 128 | 'devops', 129 | 'digital transformation', 130 | 'differentiation', 131 | 'discovery', 132 | 'distributed ledgers?', 133 | 'disrupt(ive|tor|ion|er)?', 134 | 'dollar.productive', 135 | 'downsi(s|z)e', 136 | 'drill down', 137 | 'drink the kool-aid', 138 | 'drop.?in', 139 | 'drop the ball', 140 | 'due dilligence', 141 | 'dynamic(s|ally)?', 142 | 'e-?(business|commerce|tailers)', 143 | 'early.stage', 144 | 'eas(y|ily)', 145 | 'ecosystem(s)?', 146 | 'efficien(t|cy)', 147 | 'effortless(ly)?', 148 | 'elastic', 149 | 'elaboration', 150 | 'elephant in the room', 151 | 'elevator pitch(ing)?', 152 | 'enabl(e|ing)', 153 | 'emerging markets?', 154 | 'empower(ing|ment|s)?', 155 | 'enablement', 156 | 'end of the day', 157 | 'end.to.end', 158 | 'engulf', 159 | 'engag(e(d)|ing|ment)', 160 | 'enhanced?', 161 | 'enterprise', 162 | 'erp', 163 | 'estimate', 164 | 'eta', 165 | 'etched in stone', 166 | 'evolution', 167 | 'exceed expectations', 168 | 'excellent', 169 | 'exceptional', 170 | 'exclusive(ly)?', 171 | 'exhaustive', 172 | 'expedite', 173 | 'experiences', 174 | 'experts?', 175 | 'expertise', 176 | 'exposure', 177 | 'extraordinary', 178 | 'facilitat(e|or)', 179 | 'fast track', 180 | 'fault.tolerant', 181 | 'feeling excited', 182 | 'first.rate', 183 | 'first.to.market', 184 | 'flexibility', 185 | 'flux', 186 | 'foot view', 187 | 'forward-thinking', 188 | 'four pillars', 189 | 'frictionless', 190 | 'front lines', 191 | 'frustration[- ]free', 192 | 'functional', 193 | 'futurist', 194 | 'full benefit', 195 | 'future.proof', 196 | 'game changer', 197 | 'game plan', 198 | 'behavioral', 199 | 'global(ly)?', 200 | 'go public', 201 | 'go.to.market', 202 | 'goals?', 203 | 'god-speed', 204 | 'going forward', 205 | 'granular', 206 | 'ground.?breaking', 207 | 'growth', 208 | 'grow', 209 | 'guidance', 210 | 'guru', 211 | 'hackathon', 212 | 'hacker( mindset)?', 213 | 'happiness manage(ment|rs?)', 214 | 'hardball', 215 | 'heavy.lifting', 216 | 'herding cats', 217 | 'hidden.gem', 218 | 'hidden.meaning', 219 | 'high.level', 220 | '(high|mass).impact', 221 | 'highly.scalable', 222 | 'hive ?mind', 223 | 'hybrid environments?', 224 | 'hyper.personalization', 225 | 'hyperconverged', 226 | 'hyperscal(e|ed|ing)', 227 | 'holistic', 228 | 'ideathon', 229 | 'impactful', 230 | 'impeccable', 231 | 'in( |-)a( |-)nutshell', 232 | 'incent', 233 | 'incentivi(s|z)e', 234 | 'increase the odds', 235 | 'incredibl(e|y)', 236 | '(inflat|redeem)able value', 237 | 'influencers?', 238 | 'influx', 239 | 'innovat(e|ed|ion|ive|ing|or)s?', 240 | 'inspir(e|ing|ation)', 241 | 'insights?', 242 | 'integrat(e|ed|ion)s?', 243 | 'internet[- ]of[- ]things', 244 | 'intellectual property', 245 | 'intuitive', 246 | 'iot', 247 | 'key results?', 248 | 'kickstart(er|ed)?s?', 249 | 'killjoy', 250 | 'knowledge.(base|transfer|sharing)', 251 | 'kpis?', 252 | 'landing pages?', 253 | 'lead the field', 254 | 'leading', 255 | 'leaders?', 256 | 'leadership', 257 | 'learnings', 258 | 'legacy', 259 | 'lessons learned', 260 | 'level (the )? playing field', 261 | 'level set', 262 | 'leverag(e|ing)', 263 | 'lifecycle', 264 | 'low.hanging fruit', 265 | 'look.(&|and).feel', 266 | 'm2m', 267 | 'machine learning', 268 | 'made simple', 269 | 'magical', 270 | 'market (chang(er|ing)|leader|window|simplified|fit)', 271 | 'market.ready', 272 | 'marketing collateral', 273 | 'maximi(s|z)e', 274 | 'meaningful( client | )interactions?', 275 | 'measurements?', 276 | 'methodolog(y|ies)', 277 | 'metrics', 278 | 'middleware', 279 | 'milestone', 280 | 'military.grade encryption', 281 | 'mind ?share', 282 | 'mind ?shower', 283 | 'mind-boggling', 284 | 'mindset', 285 | '(mission|time).critical', 286 | 'ml', 287 | 'moneti(s|z)e', 288 | 'mov(e|ing) (fast|forward)', 289 | 'multitask(ing?)', 290 | 'multifaceted', 291 | 'multi-?tenant(ed)?', 292 | 'mvp', 293 | 'negotiated', 294 | 'networking', 295 | 'new.economy', 296 | 'new.breed', 297 | '(new|next|second).(level|gen|generation)', 298 | 'nexus', 299 | 'niches?', 300 | 'ninja', 301 | 'no-brainer', 302 | 'non-traditional management', 303 | 'objectives', 304 | 'occupy the field', 305 | 'off.site', 306 | 'off.the.(radar|shelf)', 307 | 'on board', 308 | 'on.premises?', 309 | 'on the (back end|radar screen|same page|house)', 310 | 'one to one', 311 | 'open the kimono', 312 | 'opportunit(y|ies)', 313 | 'outperform', 314 | 'optimal', 315 | 'orthogonal', 316 | 'outcome(s)', 317 | 'outstanding', 318 | 'out(side)?.(of)?.the.(box|loop)', 319 | 'outsourc(e|ed|ing)', 320 | '(total cost of )?ownership', 321 | 'paradigms?( shift)?', 322 | 'partner(ships?)?', 323 | 'patents?', 324 | 'people.focus(ed|sed)', 325 | 'performance indicators', 326 | 'personaliz(e|ed|ation)', 327 | 'perspective', 328 | 'phases?', 329 | 'phased approach', 330 | 'pipeline', 331 | 'pioneers', 332 | 'pivot', 333 | 'planning horizon', 334 | 'platforms?', 335 | 'plethora', 336 | 'plug.?in', 337 | 'potentials?', 338 | 'powerful', 339 | 'prioriti(s|z)ed?', 340 | 'proactive', 341 | 'problem space', 342 | 'processes', 343 | 'profit(ability)?', 344 | 'promotion', 345 | 'promotional collateral', 346 | 'prominent', 347 | 'proprietary', 348 | 'proof.of.concept', 349 | 'purpose.built', 350 | 'push the envelope', 351 | 'push.back', 352 | 'production.ready', 353 | 'productivity', 354 | 'pushing on open doors', 355 | 'quick wins?', 356 | 'rais(e|ing) the bar', 357 | 'ramp.up', 358 | 'ravishing', 359 | '(reaping )(tangible )rewards', 360 | 'relationship management', 361 | 'responsive', 362 | 'engage(ments?)?', 363 | 'reach out', 364 | 'reactivation', 365 | 'real.time', 366 | 'real.world', 367 | 'reconfigure', 368 | 'redefin(e|ed|ing)', 369 | 'red flags?', 370 | 'reengineering', 371 | 'reimagin(e|ed|ing)', 372 | 'reinvent(ing)? the(.square)? wheel', 373 | 'reinvigorate', 374 | 'relevance', 375 | 'repurpose', 376 | 'resilien(ce|cy|t)', 377 | 'resource allocation', 378 | 'restructuring', 379 | 'retention', 380 | 'return on investment', 381 | 'results', 382 | 'reus(e|ability)', 383 | 'revenue', 384 | 'reverse.engineer', 385 | 'revisit', 386 | 'revolution', 387 | 'reward(ing)?(.experience)?', 388 | 'rich', 389 | 'road ?map', 390 | 'robust', 391 | 'rock.?star', 392 | 'roi', 393 | 'run the numbers', 394 | '(s|p)aas', 395 | 'sacrific(e|es|ing)', 396 | 'scal(e|es|ing|ability)', 397 | 'high availability', 398 | 'scenarios?', 399 | 'scope', 400 | 'scrum( master)?', 401 | 'seamless', 402 | 'secret sauce', 403 | 'search engine optimization', 404 | 'segments?', 405 | 'self-managed team', 406 | 'seo', 407 | '(serial )?entrepreneurs?', 408 | 'serverless', 409 | 'service mesh', 410 | 'shareholder value', 411 | 'significant(ly)?', 412 | 'single-source responsibility', 413 | 'skill ?sets?', 414 | 'smart(er)?', 415 | 'smoke (&|and) mirrors', 416 | 'social(.media|.gaming|.networks?)', 417 | 'solidality', 418 | 'solutions?', 419 | 'sophisticated', 420 | 'soup to nuts', 421 | 'sow', 422 | 'spatial.computing', 423 | 'stakeholders?', 424 | 'start.up?', 425 | 'statement of work', 426 | 'state.of.the.art', 427 | 'step.changes?', 428 | 'sticky-?ness', 429 | 'strateg(y|ic|ize|ise)', 430 | 'streamlin(ed|e|ing)', 431 | 'story points?', 432 | 'success(ful)?', 433 | 'super(critical|star|nova)', 434 | 'sustainab(le|ility)', 435 | 'synerg(y|ies|ized|i)', 436 | 'systems?', 437 | 'tailwinds?', 438 | 'talented', 439 | 'take offline', 440 | 'talking points', 441 | 'target (audience|group)', 442 | 'targeted', 443 | 'tasked', 444 | 'tco', 445 | 'team.building', 446 | 'team.player', 447 | 'teamwork', 448 | 'technolog(y|ies)', 449 | 'that being said', 450 | 'thought.leader', 451 | 'throughput', 452 | 'time.to.awesome', 453 | 'time.to.market', 454 | 'timelines?', 455 | 'top.down', 456 | 'top.of.the.game', 457 | 'total quality', 458 | 'touch.base', 459 | 'touchpoints?', 460 | 'traction', 461 | 'transform(ing|ative|ation(al))', 462 | 'trends?', 463 | 'true', 464 | 'truths?', 465 | 'turnkey', 466 | 'ultimate', 467 | 'up.to.speed', 468 | 'up-?sell', 469 | 'upside', 470 | 'user.friendly', 471 | 'user funnels?', 472 | 'user.experience', 473 | 'utili(s|z)(e|ation)', 474 | 'uncover', 475 | 'unicorn', 476 | 'unique approach', 477 | 'unlimited.holidays', 478 | 'values?', 479 | 'valueable', 480 | 'value.add(ed)?', 481 | 'venture', 482 | 'venturing', 483 | 'vertical market', 484 | 'viral', 485 | 'virtual(ization|isation)?', 486 | 'visibility', 487 | 'visio(n|nary)', 488 | 'walk the talk', 489 | 'wearable', 490 | 'web.enabled', 491 | 'win-win', 492 | 'wisdom of crowds', 493 | 'with due respect', 494 | 'with ease', 495 | 'wizards?', 496 | 'workflows?', 497 | 'workloads?', 498 | 'workplaceless', 499 | 'workspace', 500 | 'world.?class', 501 | 'world a better place', 502 | 'wow.factor', 503 | 'you\'ve never seen (a|an) \\w+', 504 | 'zeitgeist', 505 | 'zenith' 506 | ]; 507 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import parseCmd, { Config } from './utils/parseCmd' 2 | import parseMd from './utils/parseMd' 3 | import { read, write } from './utils/files' 4 | import renderHTML from './render' 5 | import { replaceBullshit, countBullshit } from './bullshit' 6 | 7 | const config = parseCmd() 8 | 9 | const bullShitScore = (md: string) => { 10 | const { words, bullshit, score } = countBullshit(md) 11 | return ` 12 | Your bullshit score is: ${score} 13 | 14 | ${bullshit} out of ${words} words are bullshit. 15 | ` 16 | } 17 | 18 | const run = async ({ theme, replaceBs, file, output }: Config) => { 19 | try { 20 | const md = await read(file) 21 | const slides = parseMd(replaceBs ? replaceBullshit(md) : md) 22 | const html = await renderHTML(slides, theme) 23 | await write(output, html) 24 | console.log(bullShitScore(md)) 25 | } catch (e) { 26 | console.log(e) 27 | } 28 | } 29 | 30 | if (config) { 31 | run(config) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/render/createElement.ts: -------------------------------------------------------------------------------- 1 | import marked from 'marked' 2 | import highlight from './highlight' 3 | import { SlideElement } from '../types' 4 | import Tag from 'xml-string/dist/Tag' 5 | import parseDsv from '../utils/parseDsv' 6 | 7 | const setCodeBlockSize = (linesOfCode: number) => { 8 | if (linesOfCode < 5) { return 'codeblock-small' } 9 | if (linesOfCode > 10) { return 'codeblock-big' } 10 | return '' 11 | } 12 | 13 | const createTable = ( 14 | value: string, 15 | linesOfCode: number, 16 | root: Tag 17 | ) => 18 | (separator: string) => { 19 | const [head, ...rows] = parseDsv(separator)(value) 20 | const table = root.child('table') 21 | .attr({ 'class': `codeblock ${setCodeBlockSize(linesOfCode)}` }) 22 | const thead = table.child('thead').child('tr') 23 | const tbody = table.child('tbody') 24 | head.forEach(cell => thead.child('th').data(cell)) 25 | rows.forEach(row => { 26 | const tr = tbody.child('tr') 27 | row.forEach(cell => tr.child('td').data(cell)) 28 | }) 29 | } 30 | 31 | export default (linesOfCode: number, root: Tag) => ({ type, lang, value }: SlideElement) => { 32 | if (type === 'image') { 33 | root.child('div').attr({ 34 | 'class': 'image', 35 | style: `background-image: url(${value.split('(')[1].split(')')[0]})`, 36 | 'aria-label': value.split(']')[0].split('[')[1], 37 | }) 38 | return 39 | } 40 | if (type === 'code' && lang === 'csv') { 41 | createTable(value, linesOfCode, root)(',') 42 | return 43 | } 44 | if (type === 'code' && lang === 'tsv') { 45 | createTable(value, linesOfCode, root)('\t') 46 | return 47 | } 48 | if (type === 'code' && lang?.startsWith('dsv')) { 49 | createTable(value, linesOfCode, root)(lang.split('dsv')[1]) 50 | return 51 | } 52 | if (type === 'code') { 53 | root.child('div') 54 | .attr({ 'class': `codeblock ${setCodeBlockSize(linesOfCode)}` }) 55 | .data(highlight(value, lang || '')) 56 | return 57 | } 58 | root.child('div').attr({ 'class': 'text' }) 59 | .data(marked(value)) 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/render/createSlide.ts: -------------------------------------------------------------------------------- 1 | import { Slide } from '../types' 2 | import Tag from 'xml-string/dist/Tag' 3 | import createElement from './createElement' 4 | 5 | const hasImage = (slide: Slide) => 6 | slide.map(d => d.type).includes('image') 7 | 8 | const hasCode = (slide: Slide) => 9 | slide.filter(d => d.type === 'code').length > 0 10 | 11 | const countCodeLines = (slide: Slide) => 12 | slide.filter(d => d.type === 'code') 13 | .map(d => d.value.trim().split('\n').length) 14 | .reduce((r, d) => r + d, 0) 15 | 16 | const getContainer = (slide: Slide, root: Tag): Tag => { 17 | if (hasImage(slide)) { 18 | return root 19 | .child('div').attr({ 'class': 'slide-with-image' }) 20 | } 21 | if (hasCode(slide)) { 22 | return root 23 | .child('div').attr({ 'class': 'slide-container' }) 24 | .child('div').attr({ 'class': 'slide-with-code' }) 25 | } 26 | return root 27 | .child('div').attr({ 'class': 'slide-container' }) 28 | } 29 | 30 | export default (body: Tag) => (slide: Slide) => { 31 | const linesOfCode = countCodeLines(slide) 32 | const root = body.child('div').attr({ 'class': 'slide' }) 33 | const container = getContainer(slide, root) 34 | slide.forEach(createElement(linesOfCode, container)) 35 | } 36 | -------------------------------------------------------------------------------- /src/render/highlight.ts: -------------------------------------------------------------------------------- 1 | import prism from 'prismjs' 2 | import 'prismjs/components/prism-autoit' 3 | import 'prismjs/components/prism-bash' 4 | import 'prismjs/components/prism-c' 5 | import 'prismjs/components/prism-cmake' 6 | import 'prismjs/components/prism-coffeescript' 7 | import 'prismjs/components/prism-csharp' 8 | import 'prismjs/components/prism-docker' 9 | import 'prismjs/components/prism-ejs' 10 | import 'prismjs/components/prism-elm' 11 | import 'prismjs/components/prism-fsharp' 12 | import 'prismjs/components/prism-go' 13 | import 'prismjs/components/prism-graphql' 14 | import 'prismjs/components/prism-handlebars' 15 | import 'prismjs/components/prism-haskell' 16 | import 'prismjs/components/prism-javascript' 17 | import 'prismjs/components/prism-json' 18 | import 'prismjs/components/prism-jsx' 19 | import 'prismjs/components/prism-latex' 20 | import 'prismjs/components/prism-lua' 21 | import 'prismjs/components/prism-markdown' 22 | import 'prismjs/components/prism-markup' 23 | import 'prismjs/components/prism-markup-templating' 24 | import 'prismjs/components/prism-matlab' 25 | import 'prismjs/components/prism-php' 26 | import 'prismjs/components/prism-processing' 27 | import 'prismjs/components/prism-pug' 28 | import 'prismjs/components/prism-python' 29 | import 'prismjs/components/prism-regex' 30 | import 'prismjs/components/prism-ruby' 31 | import 'prismjs/components/prism-rust' 32 | import 'prismjs/components/prism-sass' 33 | import 'prismjs/components/prism-scss' 34 | import 'prismjs/components/prism-sql' 35 | import 'prismjs/components/prism-swift' 36 | import 'prismjs/components/prism-tsx' 37 | import 'prismjs/components/prism-typescript' 38 | import 'prismjs/components/prism-wiki' 39 | import 'prismjs/components/prism-yaml' 40 | 41 | export default (code: string, lang: string) => { 42 | const language = prism.languages[lang] || prism.languages.autoit 43 | try { 44 | return `
${prism.highlight(code, language, lang)}
` 45 | } catch (e) { 46 | console.log(`Could not highlight code block with language: ${lang}`, e) 47 | return `
${code}
` 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/render/index.ts: -------------------------------------------------------------------------------- 1 | import xml from 'xml-string' 2 | import Tag from 'xml-string/dist/Tag' 3 | import { readFile } from 'fs' 4 | import { promisify } from 'util' 5 | import { resolve } from 'path' 6 | import { Slide } from '../types' 7 | import createSlide from './createSlide' 8 | 9 | const getFile = (file: string) => 10 | promisify(readFile)(resolve(__dirname, '..', '..', 'assets', file), 'utf-8') 11 | 12 | const getTitle = (slides: Slide[]) => 13 | slides && slides[0] && slides[0][0] && slides[0][0].value 14 | ? slides[0][0].value.replace(/\W/g, '') 15 | : 'Presentation' 16 | 17 | const isString = (d: any): d is string => 18 | typeof d === 'string' || d instanceof String 19 | 20 | const getAllCodeLangs = (slides: Slide[]) => 21 | slides 22 | .map(el => el.filter(d => d.type === 'code' && d.lang).map(d => d.lang)) 23 | .reduce((r, list) => { 24 | const toAdd = Array.from(new Set(list.filter(d => !r.includes(d)))) 25 | return [...r, ...toAdd] 26 | }, []) 27 | .filter(isString) 28 | 29 | const createHead = (html: Tag, title: string, cssFiles: string[]) => { 30 | const head = html.child('head') 31 | head.child('meta').attr({ charset: 'utf-8' }) 32 | head.child('title').data(title) 33 | cssFiles.forEach(css => head.child('style').data(css)) 34 | } 35 | 36 | const createBody = (html: Tag, slides: Slide[], jsFiles: string[]) => { 37 | const body = html.child('body') 38 | const presentation = body.child('div').attr({ 'class': 'presentation' }) 39 | slides.map(createSlide(presentation)) 40 | jsFiles.forEach(js => body.child('script').data(js)) 41 | } 42 | 43 | const needsPrism = (langs: string[]) => langs.length > 0 44 | 45 | const allowedThemes = [ 46 | 'dark', 47 | 'light', 48 | 'paper', 49 | ] 50 | 51 | const prismByTheme = { 52 | 'dark': 'prism-okaidia.css', 53 | 'light': 'prism-solarizedlight.css', 54 | 'paper': 'prism-solarizedlight.css', 55 | } 56 | 57 | const getThemeCss = (theme: string) => 58 | allowedThemes.includes(theme) 59 | ? `${theme}.css` 60 | : 'dark.css' 61 | 62 | const getPrismCss = (theme: string, langs: string[]): string | undefined => { 63 | if (!needsPrism(langs)) { return undefined } 64 | // @ts-ignore 65 | return prismByTheme[allowedThemes.includes(theme) ? theme : 'dark'] 66 | } 67 | 68 | export default async (slides: Slide[], theme: string) => { 69 | const langs = getAllCodeLangs(slides) 70 | const html = xml.create('html') 71 | 72 | const cssFileNames: string[] = [ 73 | getThemeCss(theme), 74 | getPrismCss(theme, langs), 75 | ].filter(isString) 76 | const cssFiles = await Promise.all(cssFileNames.map(getFile)) 77 | 78 | createHead(html, getTitle(slides), cssFiles) 79 | 80 | const jsFileNames: string[] = [ 81 | 'main.js', 82 | ] 83 | const jsFiles = await Promise.all(jsFileNames.map(getFile)) 84 | 85 | 86 | createBody(html, slides, jsFiles) 87 | 88 | return ` 89 | ${html.outer()} 90 | ` 91 | } 92 | -------------------------------------------------------------------------------- /src/test/bullshit.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { countBullshit, replaceBullshit } from '../bullshit' 3 | 4 | test('bullshit', t => { 5 | const text = 'branding and artificial intelligence ambassador' 6 | t.is( 7 | replaceBullshit(text), 8 | 'bullshitting and bullshit bullshitter' 9 | ) 10 | const { bullshit, words, score } = countBullshit(text) 11 | t.is(bullshit, 3) 12 | t.is(words, 4) 13 | t.is(score, 750) 14 | }) -------------------------------------------------------------------------------- /src/test/parseDsv.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import parseDsv from '../utils/parseDsv' 3 | 4 | test('parseDsv 1', t => { 5 | const csv = ` 6 | Year,Make,Model,Description,Price 7 | 1997,Ford,E350,"ac, abs, moon",3000.00 8 | 1999,Chevy,"Venture ""Extended Edition""","",4900.00 9 | 1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00 10 | 1996,Jeep,Grand Cherokee,"MUST SELL! 11 | air, moon roof, loaded",4799.00 12 | `.trim() 13 | const lines = parseDsv(',')(csv) 14 | const [ head, ...rest ] = lines 15 | t.true(!rest.map(line => line.length === head.length).includes(false)) 16 | }) 17 | 18 | test('parseDsv 2', t => { 19 | const csv = ` 20 | Index,Item,Cost,Tax,Total 21 | 1,"Fruit of the Loom Girl's Socks",7.97,0.60,8.57 22 | 2,"Rawlings Little League Baseball",2.97,0.22,3.19 23 | 3,"Secret Antiperspirant",1.29,0.10,1.39 24 | 4,"Deadpool DVD",14.96,1.12,16.08 25 | 5,"Maxwell House Coffee 28 oz",7.28,0.55,7.83 26 | 6,"Banana Boat Sunscreen, 8 oz",6.68,0.50,7.18 27 | 7,"WrenchSet, 18 pieces",10.00,0.75,10.75 28 | 8,"Mand M,42 oz",8.98,0.67,9.65 29 | 9,"Bertoli Alfredo Sauce",2.12,0.16,2.28 30 | 10,"Large Paperclips, 10boxes",6.19,0.46,6.65 31 | ` 32 | const lines = parseDsv(',')(csv) 33 | const [ head, ...rest ] = lines 34 | t.true(!rest.map(line => line.length === head.length).includes(false)) 35 | 36 | }) -------------------------------------------------------------------------------- /src/test/parseMd.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { parseSlide } from '../utils/parseMd' 3 | 4 | test('parseSlide', t => { 5 | const one = ` 6 | # Hello 7 | ` 8 | const oneP = parseSlide(one) 9 | t.is(oneP.length, 1) 10 | t.is(oneP[0].type, 'md') 11 | t.is(oneP[0].value, '# Hello') 12 | 13 | const two = ` 14 | Some text 15 | ![An image](http://somewhere.com/img.png) 16 | ` 17 | const twoP = parseSlide(two) 18 | t.is(twoP.length, 2) 19 | t.is(twoP[0].type, 'md') 20 | t.is(twoP[1].type, 'image') 21 | 22 | const three = ` 23 | \`\`\`js 24 | console.log('hello world') 25 | \`\`\` 26 | ` 27 | const threeP = parseSlide(three) 28 | t.is(threeP[0].type, 'code') 29 | t.is(threeP[0].lang, 'js') 30 | }) -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface SlideElement { 2 | type: 'md' | 'code' | 'image' | string 3 | lang?: string 4 | value: string 5 | } 6 | 7 | export type Slide = SlideElement[] -------------------------------------------------------------------------------- /src/utils/files.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util' 2 | import { readFile, writeFile } from 'fs' 3 | 4 | export const read = async (path: string) => { 5 | try { 6 | return await promisify(readFile)(path, 'utf-8') 7 | } catch (e) { 8 | throw new Error (`Could not find "${path}"`) 9 | } 10 | } 11 | 12 | export const write = async (path: string, content: string) => { 13 | try { 14 | return await promisify(writeFile)(path, content, 'utf-8') 15 | } catch (e) { 16 | throw new Error (`Could not find "${path}"`) 17 | } 18 | } -------------------------------------------------------------------------------- /src/utils/parseCmd.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentParser } from 'argparse' 2 | 3 | const parser = new ArgumentParser() 4 | 5 | parser.addArgument( 6 | ['-md', '--markdown-file'], 7 | { required: true, help: 'The markdown file to read' } 8 | ) 9 | 10 | parser.addArgument( 11 | ['-o', '--output-file'], 12 | { required: true, help: 'The name of the HTML file to create' } 13 | ) 14 | 15 | parser.addArgument( 16 | ['-bs', '--replace-bullshit'], 17 | { action: 'storeTrue', help: 'Replace bullshit words by "bullshit"' } 18 | ) 19 | 20 | parser.addArgument( 21 | ['-t', '--theme'], 22 | { defaultValue: 'dark', help: 'Defaults to "dark". Alternatives are "light" or "paper".' } 23 | ) 24 | 25 | 26 | export interface Config { 27 | file: string 28 | output: string 29 | replaceBs: boolean 30 | theme: string 31 | } 32 | 33 | export default (): Config => { 34 | const { 35 | markdown_file, 36 | output_file, 37 | replace_bullshit, 38 | theme, 39 | } = parser.parseArgs() 40 | 41 | return { 42 | file: markdown_file, 43 | output: output_file, 44 | replaceBs: Boolean(replace_bullshit), 45 | theme: theme || 'dark', 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/parseDsv.ts: -------------------------------------------------------------------------------- 1 | import { parse } from 'papaparse' 2 | 3 | export default (delimiter: string) => (dsv: string): string[][] => 4 | // @ts-ignore 5 | parse(dsv.trim(), { delimiter }).data//.data.map(line => line.map((d: string) => d.trim())) 6 | -------------------------------------------------------------------------------- /src/utils/parseMd.ts: -------------------------------------------------------------------------------- 1 | import { Slide, SlideElement } from '../types' 2 | 3 | const addCodeType = ({ type, value }: SlideElement) => { 4 | if (type === 'md') { 5 | return { type, value } 6 | } 7 | const [lang, ...rest] = value.split('\n') 8 | return { type, lang: lang.trim(), value: rest.join('\n').trim() } 9 | } 10 | 11 | const addType = (d: string, index: number): SlideElement => ({ type: index % 2 === 0 ? 'md': 'code', value: d }) 12 | 13 | const isImage = (line: string) => { 14 | const l = line.trim() 15 | return l.startsWith('![') && l.includes('](') && l.endsWith(')') 16 | } 17 | 18 | const splitMdIfHasImage = (value: string): SlideElement[] => { 19 | const lines = value.split('\n') 20 | if (!lines.map(isImage).includes(true)) { 21 | return [{ type: 'md', value }] 22 | } 23 | const { current, parts } = lines.reduce<{ current: string, parts: SlideElement[] }>(({current, parts}, line) => { 24 | if (isImage(line)) { 25 | const img: SlideElement = { type: 'image', value: line } 26 | if (current !== '') { 27 | const md: SlideElement = { type: 'md', value: current } 28 | return { current: '', parts: [...parts, md, img] } 29 | } 30 | return { current, parts: [...parts, img] } 31 | } 32 | return { 33 | current: current !== '' ? `${current}\n${line}` : line, 34 | parts 35 | } 36 | }, { current: '', parts: [] }) 37 | return current === '' 38 | ? parts 39 | : [...parts, { type: 'md', value: current }] 40 | } 41 | 42 | const separateImages = (slide: Slide): Slide => 43 | slide 44 | .map(d => d.type === 'md' ? splitMdIfHasImage(d.value) : [d]) 45 | .reduce((r, d) => ([...r, ...d]), []) 46 | 47 | export const parseSlide = (page: string): Slide => 48 | separateImages( 49 | page.split('```') 50 | .map(addType) 51 | .map(addCodeType) 52 | ) 53 | .map(d => ({ ...d, value: d.value.trim() })) 54 | .filter(d => d.value !== '') 55 | 56 | export default (file: string): Slide[] => { 57 | const slides = file.split('---').map(d => d.trim()).filter(d => d !== '') 58 | return slides.map(parseSlide) 59 | } -------------------------------------------------------------------------------- /src_client/index.ts: -------------------------------------------------------------------------------- 1 | import { initPaging } from './page' 2 | import throttle from './throttle' 3 | 4 | const onKeydown = (prev: () => void, next: () => void) => 5 | (e: KeyboardEvent) => { 6 | switch (e.key) { 7 | case 'ArrowDown': { e.preventDefault(); return next() } 8 | case 'ArrowLeft': { e.preventDefault(); return prev() } 9 | case 'ArrowRight': { e.preventDefault(); return next() } 10 | case 'ArrowUp': { e.preventDefault(); return prev() } 11 | default: return null 12 | } 13 | } 14 | 15 | const onLoad = () => { 16 | const slides = Array.from(document.getElementsByClassName('slide')) 17 | const { next, prev, goToPage, getPage } = initPaging(slides) 18 | window.addEventListener('keydown', onKeydown(prev, next)) 19 | const scroll = throttle(100, goToPage) 20 | window.addEventListener('scroll', () => scroll.onChange(window.pageYOffset / window.innerHeight)) 21 | window.addEventListener('resize', () => goToPage(getPage())) 22 | } 23 | 24 | window.onload = onLoad -------------------------------------------------------------------------------- /src_client/page.ts: -------------------------------------------------------------------------------- 1 | export const initPaging = (slides: Element[]) => { 2 | let page = 0 3 | const pages = slides.length 4 | const goToPage = (page: number) => 5 | slides[page].scrollIntoView({ behavior: 'smooth' }) 6 | return { 7 | next: () => { 8 | page = page === pages - 1 ? 0 : page + 1 9 | goToPage(page) 10 | }, 11 | prev: () => { 12 | page = page === 0 ? page = pages - 1 : page - 1 13 | goToPage(page) 14 | }, 15 | goToPage: (newPage: number) => { 16 | page = newPage 17 | goToPage(newPage) 18 | }, 19 | getPage: () => page 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src_client/throttle.ts: -------------------------------------------------------------------------------- 1 | type OnTimeout = (value: number) => void 2 | 3 | export default (time: number = 200, onTimeout: OnTimeout) => { 4 | let prev = 0 5 | let f: OnTimeout = onTimeout 6 | let timeout: any 7 | return { 8 | onChange: (value: number) => { 9 | if (value !== prev) { 10 | prev = value 11 | if (timeout) { 12 | clearTimeout(timeout) 13 | } 14 | timeout = setTimeout(() => f(Math.round(value)), time) 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src_style/dark.scss: -------------------------------------------------------------------------------- 1 | $font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 2 | $fontSize: 2.5vw; 3 | 4 | $bgColor: rgb(25, 25, 25); 5 | $textColor: white; 6 | $strongColor: rgb(194, 89, 89); 7 | $codeBgColor: rgb(26, 29, 37); 8 | 9 | $quoteFontStyle: italic; 10 | $quoteColor: rgb(207, 207, 207); 11 | 12 | @import './main.scss'; -------------------------------------------------------------------------------- /src_style/light.scss: -------------------------------------------------------------------------------- 1 | $font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 2 | $fontSize: 2.5vw; 3 | 4 | $bgColor: white; 5 | $textColor: rgb(25, 25, 25); 6 | $strongColor: rgb(194, 33, 33); 7 | $codeBgColor: rgb(248, 246, 230); 8 | 9 | $quoteFontStyle: italic; 10 | $quoteColor: rgb(58, 58, 58); 11 | 12 | @import './main.scss'; -------------------------------------------------------------------------------- /src_style/main.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | scrollbar-width: none; 4 | } 5 | html, body { 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | html, body, .presentation, .slide { 11 | width: 100vw; 12 | height: 100vh; 13 | } 14 | 15 | body { 16 | font-family: $font; 17 | font-size: $fontSize; 18 | background-color: $bgColor; 19 | color: $textColor; 20 | } 21 | 22 | a { color: inherit; } 23 | 24 | .presentation { 25 | scroll-snap-type: mandatory; 26 | scroll-snap-points-y: repeat(100vh); 27 | scroll-snap-type: y mandatory; 28 | } 29 | 30 | .slide, .slide-container { 31 | scroll-snap-align: start; 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | text-align: justify; 36 | overflow: hidden; 37 | } 38 | 39 | .slide { 40 | padding: 5%; 41 | } 42 | 43 | .slide img { 44 | max-width: 100vw; 45 | max-height: 100vh; 46 | } 47 | 48 | .slide-with-image { 49 | display: flex; 50 | flex-direction: column; 51 | height: 100%; 52 | width: 100%; 53 | text-align: center; 54 | } 55 | 56 | .slide-container, .slide-with-code { 57 | width: 100%; 58 | } 59 | 60 | .slide-with-image .image, .slide-with-image .mermaid, .slide-container .codeblock { 61 | flex-grow: 1; 62 | width: 100%; 63 | background-size: contain; 64 | background-repeat: no-repeat; 65 | background-position: center center; 66 | } 67 | 68 | .codeblock { 69 | overflow: scroll; 70 | } 71 | 72 | .text strong { 73 | font-weight: inherit; 74 | color: $strongColor; 75 | } 76 | 77 | .text em { 78 | font-weight: bolder; 79 | } 80 | 81 | .text blockquote { 82 | font-style: $quoteFontStyle; 83 | color: $quoteColor; 84 | } 85 | 86 | pre { 87 | font-size: 0.8em; 88 | width: 100%; 89 | padding-left: 0.5em; 90 | border-radius: 0.1em; 91 | text-align: left; 92 | background-color: $codeBgColor; 93 | } 94 | 95 | .codeblock-small pre { 96 | font-size: 1em; 97 | } 98 | 99 | .codeblock-big pre { 100 | font-size: 0.5em; 101 | } 102 | 103 | table { 104 | border-collapse: collapse; 105 | } 106 | 107 | table.codeblock { 108 | font-size: 0.8em; 109 | } 110 | 111 | table.codeblock-big { 112 | font-size: 0.6em; 113 | } 114 | 115 | tbody tr:nth-child(odd) { 116 | background-color: $codeBgColor; 117 | } 118 | 119 | tbody tr:hover { 120 | background-color: $strongColor; 121 | color: $bgColor; 122 | } 123 | 124 | table td, table th { 125 | border: 1px solid $textColor; 126 | } 127 | 128 | td, th { 129 | padding: 0.3em; 130 | } -------------------------------------------------------------------------------- /src_style/paper.scss: -------------------------------------------------------------------------------- 1 | $font: "Source Serif Pro",Iowan Old Style,Apple Garamond,Palatino Linotype,Times New Roman,"Droid Serif",Times,serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; 2 | $fontSize: 2.5vw; 3 | 4 | $bgColor: rgb(245, 244, 230); 5 | $textColor: rgb(51, 32, 32); 6 | $strongColor: rgb(128, 38, 38); 7 | $codeBgColor: rgb(248, 248, 242); 8 | 9 | $quoteFontStyle: italic; 10 | $quoteColor: rgb(58, 58, 58); 11 | 12 | @import './main.scss'; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist", /* Redirect output structure to the directory. */ 16 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "exclude": [ 67 | "assets/**/*", 68 | "node_modules/**/*", 69 | "scripts/**/*", 70 | "src/test/**/*", 71 | "src_client/**/*", 72 | "src_style/**/*" 73 | ] 74 | } 75 | --------------------------------------------------------------------------------