├── TODO.txt ├── asset ├── README.md ├── usage.png ├── matrix-meme.png ├── acknowledgement.png ├── dir.svg ├── file.svg ├── browser-download-symbolic.svg ├── browser-download-symbolic-dark.svg ├── favicon.svg ├── paste.svg └── no-wifi.svg ├── widgets └── theme-switch.html ├── new └── index.html ├── js ├── include-html.js ├── theme-switch.js ├── programming-txt-mime.js ├── my-marked-base-url.js ├── ipynp.notebook.min.js └── main.js ├── download ├── README.md ├── index.html ├── styles.css └── fetcher.js ├── css ├── ipynp.notebook.css ├── theme-switch.css ├── prism.css ├── main.css └── style.css ├── index.html └── README.md /TODO.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /asset/README.md: -------------------------------------------------------------------------------- 1 | all images are here 2 | 3 | -------------------------------------------------------------------------------- /asset/usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibrahemesam/repo-view/HEAD/asset/usage.png -------------------------------------------------------------------------------- /asset/matrix-meme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibrahemesam/repo-view/HEAD/asset/matrix-meme.png -------------------------------------------------------------------------------- /asset/acknowledgement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibrahemesam/repo-view/HEAD/asset/acknowledgement.png -------------------------------------------------------------------------------- /asset/dir.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /asset/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /widgets/theme-switch.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /new/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RepoView 7 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /js/include-html.js: -------------------------------------------------------------------------------- 1 | class IncludeHTML extends HTMLElement { 2 | constructor() { 3 | super(); 4 | } 5 | connectedCallback() { 6 | this.style.display = "none !important;"; 7 | var src = this.getAttribute("src"); 8 | if (src) { 9 | fetch(src).then((r) => { r.text().then( (t) => { 10 | setOuterHTML(this, t); 11 | })}); 12 | } 13 | } 14 | } 15 | customElements.define("include-html", IncludeHTML); 16 | 17 | async function setOuterHTML(elm, html) { 18 | var lastHidden = elm.hidden; 19 | elm.hidden = true; 20 | elm.innerHTML = html; 21 | Array.from(elm.querySelectorAll("script")).forEach(async(oldScript) => { 22 | const newScript = document.createElement("script"); 23 | Array.from(oldScript.attributes) 24 | .forEach(attr => newScript.setAttribute(attr.name, attr.value)); 25 | newScript.appendChild(document.createTextNode(oldScript.innerHTML)); 26 | newScript.async = false; 27 | 28 | oldScript.parentNode.replaceChild(newScript, oldScript); 29 | }); 30 | elm.outerHTML = html; 31 | elm.hidden = lastHidden; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /download/README.md: -------------------------------------------------------------------------------- 1 | # using "GitRepo Zip Fetcher" to download repo 2 | > Credits: [github-repo-downloader](https://github.com/chinxcode/github-repo-downloader) 3 |
4 | # 🚀 GitRepo Zip Fetcher 5 | Effortlessly download GitHub repositories and folders as ZIP files with just a few clicks! 🖱️ 6 | 7 | ## ✨ Features 8 | 9 | - 📦 Fetch entire GitHub repos or specific folders 10 | - 🗜️ Download repositories as ZIP files 11 | - 🔒 Support for both public and private repositories 12 | - 🔑 Optional GitHub Personal Access Token integration 13 | - 📊 Real-time progress tracking 14 | - 📱 Responsive design for all your devices 15 | 16 | ## 🚀 How to Use 17 | 18 | 1. 🌐 Open the app in your favorite web browser 19 | 2. 📝 Enter a GitHub repository URL 20 | 3. 🔑 (Optional) Click "Use Token" to enter your GitHub Personal Access Token 21 | 4. 🚀 Hit the "Fetch" button or press Enter 22 | 5. 📊 Watch the progress in the status output area 23 | 6. 💾 When it's done, your ZIP file will start downloading automatically! 24 | 25 | ## 🛠️ Dependencies 26 | 27 | - [JSZip](https://stuk.github.io/jszip/): ZIP file wizardry 🧙‍♂️ 28 | - [FileSaver.js](https://github.com/eligrey/FileSaver.js/): Download magic 🪄 29 | - [Octokit](https://github.com/octokit/octokit.js): GitHub API sorcery 🔮 30 | 31 | ## 📝 Note 32 | 33 | Your privacy is our top priority! 🛡️ This app runs entirely in your browser and doesn't collect or send any personal data. All operations happen on your device for maximum security. 🔒 34 | 35 | Enjoy fetching your GitHub repositories with ease! 🎉 36 | -------------------------------------------------------------------------------- /js/theme-switch.js: -------------------------------------------------------------------------------- 1 | const animate = gsap.timeline({ paused: true }); 2 | let toggle = true; 3 | animate 4 | .to(".toggle-button", 0.2, { scale: 0.7 }, 0) 5 | .set(".toggle", { backgroundColor: "#FFF" }) 6 | .set(".circle", { display: "none" }) 7 | .to(".moon-mask", 0.2, { translateY: 20, translateX: -10 }, 0.2) 8 | .to(".toggle-button", 0.2, { translateY: 49 }, 0.2) 9 | .to(".toggle-button", 0.2, { scale: 0.9 }) 10 | 11 | var switchBtn = document.querySelector("div.darkreader-switch") 12 | switchBtn.addEventListener("click", async () => { 13 | if(toggle){ 14 | animate.restart(); 15 | darkreader.enable(); 16 | localStorage.setItem('useDarkTheme', true) 17 | } else { 18 | animate.reverse(); 19 | darkreader.disable(); 20 | localStorage.setItem('useDarkTheme', false) 21 | } 22 | toggle = !toggle; 23 | }); 24 | 25 | 26 | 27 | 28 | if (window.useDarkTheme) { 29 | switchBtn.click(); 30 | delete window.useDarkTheme; 31 | } 32 | 33 | // switchBtn.addEventListener('load', 34 | (() => { 35 | var targetNode = document.querySelector('html'); 36 | (new MutationObserver(function(){ 37 | if(targetNode.hasAttribute('data-darkreader-scheme')){ 38 | // darkreader addon got activated 39 | darkreader.disable(); 40 | } else { 41 | // darkreader addon got un-activated 42 | if (toggle) { 43 | darkreader.disable(); 44 | } else { 45 | darkreader.enable(); 46 | } 47 | } 48 | })).observe(targetNode, { attributes: true }); 49 | })(); 50 | 51 | 52 | -------------------------------------------------------------------------------- /css/ipynp.notebook.css: -------------------------------------------------------------------------------- 1 | .nb-notebook { 2 | line-height: 1.5; 3 | } 4 | 5 | .nb-stdout, .nb-stderr { 6 | white-space: pre-wrap; 7 | margin: 1em 0; 8 | padding: 0.1em 0.5em; 9 | } 10 | 11 | .nb-stderr { 12 | background-color: #FAA; 13 | } 14 | 15 | .nb-cell + .nb-cell { 16 | margin-top: 0.5em; 17 | } 18 | 19 | .nb-output table { 20 | border: 1px solid #000; 21 | border-collapse: collapse; 22 | } 23 | 24 | .nb-output th { 25 | font-weight: bold; 26 | } 27 | 28 | .nb-output th, .nb-output td { 29 | border: 1px solid #000; 30 | padding: 0.25em; 31 | text-align: left; 32 | vertical-align: middle; 33 | border-collapse: collapse; 34 | } 35 | 36 | .nb-notebook blockquote { 37 | border-left: 5px solid #CCC; 38 | margin-left: 0; 39 | padding-left: 1em; 40 | } 41 | 42 | .nb-cell { 43 | position: relative; 44 | } 45 | 46 | .nb-raw-cell { 47 | white-space: pre-wrap; 48 | background-color: #f5f2f0; 49 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 50 | padding: 1em; 51 | margin: .5em 0; 52 | } 53 | 54 | .nb-output { 55 | min-height: 1em; 56 | width: 100%; 57 | overflow-x: scroll; 58 | border-right: 1px dotted #CCC; 59 | } 60 | 61 | .nb-output img { 62 | max-width: 100%; 63 | } 64 | 65 | .nb-output:before, .nb-input:before { 66 | position: absolute; 67 | font-family: monospace; 68 | color: #999; 69 | left: -7.5em; 70 | width: 7em; 71 | text-align: right; 72 | } 73 | 74 | .nb-input:before { 75 | content: "In [" attr(data-prompt-number) "]:"; 76 | } 77 | .nb-output:before { 78 | content: "Out [" attr(data-prompt-number) "]:"; 79 | } 80 | 81 | // Fix pandas dataframe formatting 82 | div[style="max-height:1000px;max-width:1500px;overflow:auto;"] { 83 | max-height: none !important; 84 | } 85 | 86 | -------------------------------------------------------------------------------- /asset/browser-download-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 43 | 47 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /asset/browser-download-symbolic-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 43 | 47 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /css/theme-switch.css: -------------------------------------------------------------------------------- 1 | html[data-darkreader-scheme] div.darkreader-switch, 2 | html[data-darkreader-mode] div.darkreader-switch 3 | { 4 | /* add darkreader switch only when default darkreader addon is not enabled */ 5 | display: none; 6 | } 7 | 8 | .switch { 9 | width: 60px; 10 | height: 110px; 11 | padding-top: 2px; 12 | background-color: #514e4b; 13 | border-radius: 40px; 14 | cursor: pointer; 15 | outline: none; 16 | overflow: hidden; 17 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); 18 | } 19 | .toggle-button { 20 | transform: scale(0.8); 21 | transform-origin: center center; 22 | } 23 | .toggle { 24 | position: relative; 25 | width: 35px; 26 | height: 35px; 27 | margin: 12px; 28 | margin-top: 12px; 29 | background-color: #fdb813; 30 | border-radius: 50%; 31 | } 32 | .moon-mask { 33 | position: absolute; 34 | width: 35px; 35 | height: 35px; 36 | margin: -74px 0 0 34px; 37 | background-color: #514e4b; 38 | border-radius: 50%; 39 | } 40 | .circles-wrapper .circle { 41 | position: absolute; 42 | width: 4px; 43 | height: 8px; 44 | background-color: #fff; 45 | border-radius: 10px; 46 | } 47 | .circles-wrapper .circle:first-child { 48 | margin: -57px 0 0 27px; 49 | } 50 | .circles-wrapper .circle:nth-child(2) { 51 | margin: -9px 0 0 28px; 52 | } 53 | .circles-wrapper .circle:nth-child(3) { 54 | margin: -32px 0 0 51px; 55 | transform: rotate(90deg); 56 | } 57 | .circles-wrapper .circle:nth-child(4) { 58 | margin: -33px 0 0 4px; 59 | transform: rotate(90deg); 60 | } 61 | .circles-wrapper .circle:nth-child(5) { 62 | margin: -14px 0 0 10px; 63 | transform: rotate(45deg); 64 | } 65 | .circles-wrapper .circle:nth-child(6) { 66 | margin: -14px 0 0 45px; 67 | transform: rotate(320deg); 68 | } 69 | .circles-wrapper .circle:nth-child(7) { 70 | margin: -50px 0 0 10px; 71 | transform: rotate(135deg); 72 | } 73 | .circles-wrapper .circle:nth-child(8) { 74 | margin: -50px 0 0 45px; 75 | transform: rotate(235deg); 76 | } 77 | 78 | -------------------------------------------------------------------------------- /asset/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /download/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | GitRepo Downloader 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |

21 | 22 | 23 |
24 |
25 | 39 | 42 | 46 |
47 | 51 |
52 | 53 | 54 | 55 | 56 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /download/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --accent-color: #64ffda; 3 | --bg-color: #0a192f; 4 | --surface-color: #112240; 5 | --text-color: #ccd6f6; 6 | --error-color: #ff5370; 7 | } 8 | 9 | * { 10 | margin: 0; 11 | padding: 0; 12 | box-sizing: border-box; 13 | } 14 | 15 | body { 16 | background: linear-gradient(135deg, #0a192f, #112240); 17 | font-family: "Poppins", sans-serif; 18 | color: var(--text-color); 19 | min-height: 100vh; 20 | } 21 | 22 | .container { 23 | display: flex; 24 | flex-direction: column; 25 | min-height: 100vh; 26 | } 27 | 28 | header { 29 | background-color: var(--surface-color); 30 | padding: 2rem; 31 | text-align: center; 32 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 33 | } 34 | 35 | h1 { 36 | font-weight: 500; 37 | margin-bottom: 0.5rem; 38 | color: var(--accent-color); 39 | font-size: 2.5rem; 40 | } 41 | 42 | main { 43 | flex-grow: 1; 44 | padding: 2rem; 45 | max-width: 800px; 46 | margin: 0 auto; 47 | width: 100%; 48 | } 49 | 50 | .input-wrapper { 51 | display: flex; 52 | flex-direction: column; 53 | gap: 1rem; 54 | margin-bottom: 1rem; 55 | } 56 | 57 | input { 58 | flex-grow: 1; 59 | padding: 0.75rem 1rem; 60 | border: 2px solid var(--accent-color); 61 | border-radius: 4px; 62 | background-color: var(--surface-color); 63 | color: var(--text-color); 64 | font-size: 1rem; 65 | font-family: "Poppins", sans-serif; 66 | } 67 | 68 | button { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | gap: 0.3rem; 73 | padding: 0.75rem 1.5rem; 74 | border: none; 75 | border-radius: 4px; 76 | background-color: var(--accent-color); 77 | color: var(--bg-color); 78 | font-size: 1rem; 79 | font-weight: 500; 80 | cursor: pointer; 81 | transition: background-color 0.3s ease, transform 0.1s ease; 82 | } 83 | 84 | button:hover { 85 | background-color: #45e0bc; 86 | transform: translateY(-2px); 87 | } 88 | 89 | .token-info { 90 | font-size: 0.9rem; 91 | color: var(--accent-color); 92 | margin-bottom: 1rem; 93 | } 94 | 95 | .toggle-token-btn { 96 | align-self: flex-start; 97 | padding: 0.5rem 1rem; 98 | font-size: 0.9rem; 99 | background-color: var(--surface-color); 100 | color: var(--accent-color); 101 | border: 1px solid var(--accent-color); 102 | cursor: pointer; 103 | transition: background-color 0.3s ease; 104 | } 105 | 106 | .toggle-token-btn:hover { 107 | background-color: rgba(100, 255, 218, 0.1); 108 | } 109 | 110 | .status-output { 111 | background-color: var(--surface-color); 112 | border-radius: 4px; 113 | padding: 1rem; 114 | margin-bottom: 2rem; 115 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 116 | } 117 | 118 | .status-log { 119 | font-family: "Courier New", monospace; 120 | white-space: pre-wrap; 121 | word-break: break-all; 122 | font-size: 0.9rem; 123 | line-height: 1.5; 124 | } 125 | 126 | .file-list { 127 | list-style-type: none; 128 | max-height: 250px; 129 | overflow-y: auto; 130 | } 131 | 132 | .file-list li { 133 | padding: 0.5rem 0; 134 | border-bottom: 1px solid rgba(255, 255, 255, 0.1); 135 | font-size: 0.9rem; 136 | } 137 | 138 | footer { 139 | background-color: var(--surface-color); 140 | padding: 1rem; 141 | text-align: center; 142 | font-size: 0.9rem; 143 | } 144 | 145 | footer a { 146 | color: var(--accent-color); 147 | text-decoration: none; 148 | font-weight: 500; 149 | } 150 | 151 | .note { 152 | font-size: 0.8rem; 153 | color: var(--error-color); 154 | margin-top: 0.5rem; 155 | } 156 | 157 | @media (max-width: 600px) { 158 | h1 { 159 | font-size: 1.8rem; 160 | } 161 | 162 | p { 163 | font-size: 0.9rem; 164 | } 165 | .input-wrapper { 166 | flex-direction: column; 167 | } 168 | input { 169 | font-size: 0.8rem; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /css/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.29.0 2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+armasm+arturo+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+awk+bash+basic+batch+bbcode+bbj+bicep+birb+bison+bnf+bqn+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+cilkc+cilkcpp+clojure+cmake+cobol+coffeescript+concurnas+csp+cooklang+coq+crystal+css-extras+csv+cue+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gettext+gherkin+git+glsl+gn+linker-script+go+go-module+gradle+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+mata+matlab+maxscript+mel+mermaid+metafont+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+odin+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plant-uml+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rescript+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+stata+iecst+stylus+supercollider+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uorazor+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wgsl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig&plugins=line-numbers */ 3 | code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;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}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} 4 | pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right} 5 | 6 | -------------------------------------------------------------------------------- /js/programming-txt-mime.js: -------------------------------------------------------------------------------- 1 | const types = { 2 | "application/python": ["py"], 3 | "application/ecmascript": ["es"], 4 | "application/json": ["json"], 5 | "application/xml": ["xml"], 6 | "application/x-httpd-php": ["php"], 7 | "application/x-perl": ["pl", "pm"], 8 | "application/x-ruby": ["rb"], 9 | "application/x-csharp": ["cs"], 10 | "application/x-objectivec": ["m"], 11 | "application/x-swift": ["swift"], 12 | "application/x-go": ["go"], 13 | "application/x-kotlin": ["kt", "kts"], 14 | "application/x-groovy": ["groovy"], 15 | "application/x-scala": ["scala"], 16 | "application/x-asp": ["aspx", "asmx", "ashx", "ascx", "config"], 17 | "application/x-haskell": ["hs"], 18 | "application/x-erlang": ["erl", "hrl"], 19 | "application/x-clojure": ["clj"], 20 | "application/x-ocaml": ["ml", "mli"], 21 | "application/x-elisp": ["el"], 22 | "application/x-lisp": ["lisp", "lsp"], 23 | "application/x-perl": ["pl", "pm"], 24 | "application/x-tcl": ["tcl"], 25 | "application/x-awk": ["awk"], 26 | "application/x-sed": ["sed"], 27 | "application/x-makefile": ["Makefile"], 28 | "application/x-cmake": ["cmake"], 29 | "application/x-dockerfile": ["Dockerfile"], 30 | "application/x-gitignore": ["gitignore"], 31 | "application/x-tex": ["tex"], 32 | "application/x-latex": ["latex"], 33 | "application/x-r-script": ["R"], 34 | "application/x-r-workspace": ["RData"], 35 | "application/x-r-history": ["RHistory"], 36 | "application/x-r-profile": ["Rprofile"], 37 | "application/x-r-project": ["Rproj"], 38 | "application/x-r-workspace": ["RData"], 39 | "application/x-r-session-info": ["RSessionInfo"], 40 | "application/x-r-console": ["RConsole"], 41 | "application/x-r-log": ["RLog"], 42 | "application/x-r-output": ["Rout"], 43 | "application/x-r-plot": ["RPlot"], 44 | "application/x-r-help": ["RHelp"], 45 | "application/x-r-data-frame": ["RDataFrame"], 46 | "application/x-r-factor": ["RFactor"], 47 | "application/x-r-matrix": ["RMatrix"], 48 | "application/x-r-list": ["RList"], 49 | "application/x-r-environment": ["REnvironment"], 50 | "application/x-r-closure": ["RClosure"], 51 | "application/x-r-expression": ["RExpression"], 52 | "application/x-r-symbol": ["RSymbol"], 53 | "application/x-r-character": ["RCharacter"], 54 | "application/x-r-raw": ["RRaw"], 55 | "application/x-r-complex": ["RComplex"], 56 | "application/x-r-numeric": ["RNumeric"], 57 | "application/x-r-integer": ["RInteger"], 58 | "application/x-r-double": ["RDouble"], 59 | "application/x-r-logical": ["RLogical"], 60 | "application/x-r-boolean": ["RBoolean"], 61 | "application/x-r-vector": ["RVector"], 62 | "application/x-r-array": ["RArray"], 63 | "application/x-r-data-table": ["RDataTable"], 64 | "application/x-r-factor": ["RFactor"], 65 | "application/x-r-ordered": ["ROrdered"], 66 | "application/x-r-grouped-data-frame": ["RGroupedDataFrame"], 67 | "application/x-r-tibble": ["RTibble"], 68 | "application/x-r-tidyverse": ["RTidyverse"], 69 | "application/x-r-ggplot2": ["RGGplot2"], 70 | "application/x-r-dplyr": ["RDplyr"], 71 | "application/x-r-tidyr": ["RTidyr"], 72 | "application/x-r-readr": ["RReadr"], 73 | "application/x-r-purrr": ["RPurrr"], 74 | "application/x-r-stringr": ["RStringr"], 75 | "application/x-r-lubridate": ["RLubridate"], 76 | "application/x-r-ggvis": ["RGGvis"], 77 | "application/x-r-shiny": ["RShiny"], 78 | "application/x-r-rmarkdown": ["RMarkdown"], 79 | "application/x-r-knitr": ["RKnitr"], 80 | "application/x-r-base": ["RBase"], 81 | "application/x-r-stats": ["RStats"], 82 | "application/x-r-utils": ["RUtils"], 83 | "application/x-r-grDevices": ["RGrDevices"], 84 | "application/x-r-graphics": ["RGraphics"], 85 | "application/x-r-grid": ["RGrid"], 86 | "application/x-r-lattice": ["RLattice"], 87 | "application/x-r-spatial": ["RSpatial"], 88 | "application/x-r-nlme": ["RNLME"], 89 | "application/x-r-survival": ["RSurvival"], 90 | "application/x-r-boot": ["RBoot"], 91 | "application/x-r-cluster": ["RCluster"], 92 | "application/x-r-foreign": ["RForeign"], 93 | "application/x-r-mass": ["RMass"], 94 | "application/x-r-Matrix": ["RMatrix"], 95 | "application/x-r-Bioconductor": ["RBioconductor"], 96 | "application/x-r-BiocManager": ["RBiocManager"], 97 | "application/x-r-Biostrings": ["RBiostrings"], 98 | "application/x-r-IRanges": ["RIRanges"], 99 | "application/x-r-GenomicRanges": ["RGenomicRanges"], 100 | "application/x-r-GenomeInfoDb": ["RGenomeInfoDb"], 101 | "application/x-r-AnnotationDbi": ["RAnnotationDbi"], 102 | "application/x-r-dbplyr": ["RDBplyr"], 103 | "application/x-r-RSQLite": ["RSQLite"], 104 | "application/x-r-RPostgres": ["RPostgres"], 105 | "application/x-r-RMySQL": ["RMySQL"], 106 | "application/x-r-RMariaDB": ["RMariaDB"], 107 | "application/x-r-RH2": ["RH2"], 108 | "application/x-r-jdbc": ["RJDBC"], 109 | "application/x-r-odbc": ["RODBC"], 110 | "application/x-r-dbi": ["RDBI"], 111 | "application/x-r-bigmemory": ["RBigMemory"], 112 | "application/x-r-fftw": ["RFFTW"], 113 | "application/x-r-rcpp": ["RCPP"], 114 | "application/x-r-inline": ["RInline"], 115 | "application/x-r-rinside": ["RInside"], 116 | "application/x-r-rserve": ["RServe"], 117 | "application/x-r-r6": ["R6"], 118 | "application/x-r-shiny": ["RShiny"], 119 | "application/x-r-rmarkdown": ["RMarkdown"], 120 | "application/x-r-knitr": ["RKnitr"], 121 | "application/x-r-pkgbuild": ["PKGBUILD"] 122 | }; 123 | Object.freeze(types); 124 | export default types; 125 | 126 | -------------------------------------------------------------------------------- /js/my-marked-base-url.js: -------------------------------------------------------------------------------- 1 | const urlAttributeRegex = 2 | /([\w|data-]+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?/g; 3 | // const urlAttributeRegex = /(\S+)\s*=\s*([']|["])([\W\w]*?)\2/g; 4 | const is_http_regix = /^https?:\/\//; 5 | const github_absolute_url_regix = 6 | /^https?:\/\/github.com\/(?[^/]*)\/(?[^/]*)(\/(?blob|tree)\/(?[^/]*)(\/(?.*))?)?/; 7 | // https://github.com/ibrahemesam/repo-view-demo/blob/main/directory/subdir/sample.c 8 | function __parseUrl(url) { 9 | /* 10 | parse url:- 11 | it maybe: 12 | - relative path => extract owner, repo, path 13 | - absolute path: 14 | - to this repo => extract owner, repo, path 15 | - to something else 16 | ===== 17 | if url starts with "http(s)?://“: 18 | if url starts with "http(s)?://github.com“: 19 | extract repo, owner & path (consider tree and branch) (internal=true) 20 | else: leave url as is 21 | else if starts with"/*" or "*": 22 | This is a relative path: 23 | repo and owner are same. And path is that relative path (internal=true) 24 | 25 | If external (ie: outside github): 26 | Leave as is 27 | Else if internal: 28 | if : fetch b64content 29 | Else if : make repo-view url 30 | */ 31 | // console.log(url); 32 | var internal = false, // inside github.com 33 | owner = window.owner, 34 | repo = window.repo, 35 | path = ""; 36 | if (is_http_regix.test(url)) { 37 | try { 38 | var github_url_groups = github_absolute_url_regix.exec(url); 39 | } catch (err) { 40 | console.log(url); 41 | throw err; 42 | } 43 | if (github_url_groups) { 44 | github_url_groups = github_url_groups.groups; 45 | internal = true; 46 | owner = github_url_groups.owner; 47 | repo = github_url_groups.repo; 48 | path = github_url_groups.path; 49 | } else { 50 | // pass 51 | } 52 | } else { 53 | internal = true; 54 | if (url.startsWith("/")) url = url.slice(1); 55 | path = url; 56 | } 57 | return { 58 | internal, 59 | owner, 60 | repo, 61 | path, 62 | }; 63 | } 64 | 65 | window.extensionFixUrls = () => { 66 | /* 67 | tested:- 68 | x a href relative 69 | x a href absolute 70 | x img src relative 71 | x img src absolute 72 | x md link relative 73 | x md link absolute 74 | x md image relative 75 | x md image absolute 76 | */ 77 | return { 78 | async: true, 79 | async walkTokens(token) { 80 | // console.log(token); 81 | switch (token.type) { 82 | case "html": 83 | token.text = token.text.replaceAll( 84 | urlAttributeRegex, 85 | (match, name, value) => { 86 | if (["src", "href"].includes(name)) { 87 | // parse url 88 | var { internal, owner, repo, path } = __parseUrl(value); 89 | switch (name) { 90 | case "src": 91 | try { 92 | var mime_is_image = mime 93 | .getType(path) 94 | .startsWith("image"); 95 | } catch (err) { 96 | var mime_is_image = true; 97 | } 98 | if (internal && mime_is_image) { 99 | /* 100 | if src ends with an image extension mime 101 | fetch image as base64 using github rest API 102 | set src to this base64 103 | else 104 | set src to base + relative-url 105 | */ 106 | name = "data-repoview-lazy-src"; 107 | value = JSON.stringify({ 108 | owner, 109 | repo, 110 | path, 111 | }); 112 | } 113 | break; 114 | case "href": 115 | // console.log(value); 116 | if ( 117 | !value.startsWith("#") && 118 | internal && 119 | owner === window.owner && 120 | repo === window.repo 121 | ) { 122 | name = "data-repoview-href"; 123 | value = path; 124 | } 125 | break; 126 | } 127 | } 128 | // console.log(`${name}="${value}"`); 129 | return `${name}='${value}'`; 130 | } 131 | ); 132 | // console.log(token.text); 133 | break; 134 | case "image": 135 | var { internal, owner, repo, path } = __parseUrl(token.href); 136 | try { 137 | var mime_is_image = mime.getType(path).startsWith("image"); 138 | } catch (err) { 139 | var mime_is_image = true; 140 | } 141 | if (internal && mime_is_image) { 142 | token.href = resopnse2imgSrc( 143 | await octokit.request( 144 | "GET /repos/{owner}/{repo}/contents/{path}", 145 | { 146 | owner, 147 | repo, 148 | path, 149 | headers: DEFAULT_API_HEADERS.headers, 150 | } 151 | ) 152 | ); 153 | } 154 | break; 155 | case "link": 156 | var { internal, owner, repo, path } = __parseUrl(token.href); 157 | if (token.href.startsWith("#")) { 158 | // the URL is a local reference 159 | break; 160 | } else if ( 161 | internal && 162 | owner === window.owner && 163 | repo === window.repo 164 | ) { 165 | // console.log(token); 166 | if (token.title === null) token.title = path; 167 | token.href = `data-repoview-href=${path}`; 168 | } 169 | break; 170 | default: 171 | return; 172 | } 173 | return; 174 | }, 175 | }; 176 | }; 177 | -------------------------------------------------------------------------------- /download/fetcher.js: -------------------------------------------------------------------------------- 1 | import { Octokit } from "https://cdn.skypack.dev/@octokit/rest"; 2 | 3 | document.addEventListener("DOMContentLoaded", () => { 4 | // const repoInput = document.getElementById("repoInput"); 5 | // const tokenInput = document.getElementById("tokenInput"); 6 | // const fetchBtn = document.getElementById("fetchBtn"); 7 | const statusLog = document.getElementById("statusLog"); 8 | const statusOutput = document.getElementById("statusOutput"); 9 | const fileListElement = document.getElementById("fileList"); 10 | const fileListWrapper = document.getElementById("fileListWrapper"); 11 | // const toggleTokenBtn = document.getElementById("toggleTokenBtn"); 12 | // const tokenInfo = document.getElementById("tokenInfo"); 13 | const headerLabel = document.getElementById('header-label'); 14 | 15 | let fetchedFiles = []; 16 | let progressPercentage = 0; 17 | 18 | const updateStatus = (message) => { 19 | statusLog.textContent = message; 20 | statusOutput.style.display = "block"; 21 | }; 22 | 23 | const updateProgress = (percent) => { 24 | progressPercentage = percent; 25 | statusLog.style.background = `linear-gradient(to right, rgba(100, 255, 218, 0.3) ${percent}%, transparent ${percent}%)`; 26 | statusLog.innerHTML = `${statusLog.textContent} ${percent}% Complete`; 27 | }; 28 | 29 | const fetchFolderContents = async (octokit, owner, repo, path) => { 30 | const response = await octokit.rest.repos.getContent({ 31 | owner, 32 | repo, 33 | path, 34 | }); 35 | return response.data; 36 | }; 37 | 38 | const fetchFileContent = async (octokit, owner, repo, path, fileName, totalSize) => { 39 | const response = await octokit.rest.repos.getContent({ 40 | owner, 41 | repo, 42 | path, 43 | mediaType: { 44 | format: "raw", 45 | }, 46 | }); 47 | updateStatus(`Fetching ${fileName}`); 48 | updateProgress(100); 49 | return response.data; 50 | }; 51 | 52 | const processFolderContents = async (octokit, owner, repo, folder, zip, path = "") => { 53 | for (const item of folder) { 54 | if (item.type === "file") { 55 | updateStatus(`Queueing fetch of ${item.name}`); 56 | const fileContent = await fetchFileContent(octokit, owner, repo, item.path, item.name, item.size); 57 | fetchedFiles.push(item); 58 | updateFileList(); 59 | zip.file(item.name, fileContent); 60 | } else if (item.type === "dir") { 61 | const subFolder = await fetchFolderContents(octokit, owner, repo, item.path); 62 | const subFolderZip = zip.folder(item.name); 63 | await processFolderContents(octokit, owner, repo, subFolder, subFolderZip, item.path); 64 | } 65 | } 66 | }; 67 | 68 | const fetchRepositoryContents = async (octokit, owner, repo, path) => { 69 | try { 70 | const folderData = await fetchFolderContents(octokit, owner, repo, path); 71 | const zip = new JSZip(); 72 | await processFolderContents(octokit, owner, repo, folderData, zip, path); 73 | return zip; 74 | } catch (error) { 75 | console.error(`Error: ${error}`); 76 | throw error; 77 | } 78 | }; 79 | 80 | const updateFileList = () => { 81 | fileListElement.innerHTML = ""; 82 | fetchedFiles.forEach((file) => { 83 | const li = document.createElement("li"); 84 | li.textContent = file.name; 85 | fileListElement.appendChild(li); 86 | }); 87 | fileListWrapper.style.display = "block"; 88 | fileListElement.scrollTop = fileListElement.scrollHeight; 89 | }; 90 | 91 | const fetchRepo = async () => { 92 | updateStatus(""); 93 | fetchedFiles = []; 94 | fileListWrapper.style.display = "none"; 95 | const urlParams = new URLSearchParams(document.location.search); 96 | // const repoUrl = decodeURIComponent(urlParams.repo); 97 | const owner = decodeURIComponent(urlParams.get('owner')); 98 | const repo = decodeURIComponent(urlParams.get('repo')); 99 | const path = decodeURIComponent(urlParams.get('path')); 100 | const token = decodeURIComponent(urlParams.get('token')); 101 | headerLabel.innerHTML = `Downloading Repo: ${owner}/${repo}${path ? ';\t Dir: ' + path : ''}`; 102 | // if (!repoUrl.includes("github.com")) { 103 | // updateStatus("Invalid URL. Please enter a valid GitHub repository URL."); 104 | // return; 105 | // } 106 | 107 | // const [, , , owner, repo, , , ...dirParts] = repoUrl.split("/"); 108 | // const path = dirParts.join("/"); 109 | 110 | const octokit = new Octokit({ auth: token }); 111 | 112 | updateStatus("Fetching repository contents..."); 113 | try { 114 | const zip = await fetchRepositoryContents(octokit, owner, repo, path); 115 | 116 | updateStatus("Compressing files..."); 117 | const content = await zip.generateAsync({ type: "blob" }); 118 | saveAs(content, `${path ? repo+'_'+path.replace(/\/|%20/g, "-") : repo}.zip`); 119 | 120 | const fileList = zip.file(/.*/); 121 | 122 | updateProgress(0); 123 | updateStatus( 124 | `Fetched ${fileList.length} files\nUser: ${owner}\nGithub Repo: ${owner}/${repo}\nFolder: ${path}\nSize: ${( 125 | content.size / 126 | 1024 / 127 | 1024 128 | ).toFixed(2)} MB` 129 | ); 130 | } catch (error) { 131 | if (error.status === 403) { 132 | updateStatus("Rate limit exceeded. Please try again in a few minutes or use a GitHub Personal Access Token."); 133 | } else { 134 | updateStatus(`Error: ${error.message}`); 135 | } 136 | } 137 | }; 138 | 139 | // fetchBtn.addEventListener("click", fetchRepo); 140 | // repoInput.addEventListener("keydown", (e) => { 141 | // if (e.key === "Enter") { 142 | // fetchRepo(); 143 | // } 144 | // }); 145 | 146 | // toggleTokenBtn.addEventListener("click", () => { 147 | // const isHidden = tokenInput.style.display === "none"; 148 | // tokenInput.style.display = isHidden ? "block" : "none"; 149 | // tokenInfo.style.display = isHidden ? "block" : "none"; 150 | // toggleTokenBtn.textContent = isHidden ? "Hide Token" : "Use Token"; 151 | // }); 152 | 153 | // repoInput.focus(); 154 | 155 | fetchRepo(); 156 | }); 157 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | RepoView 25 | 54 | 55 | 56 | 57 |
58 | 59 | 60 | 67 | 77 | 78 | 79 | 80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | 123 | 146 |
147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /js/ipynp.notebook.min.js: -------------------------------------------------------------------------------- 1 | (function(){var VERSION="0.8.3";var root=this||globalThis;var isBrowser=root.window!==undefined;var doc;if(isBrowser){doc=root.document}else{var jsdom=require("jsdom");var dom=new jsdom.JSDOM;doc=dom.window.document}var ident=function(x){return x};var makeElement=function(tag,classNames){var el=doc.createElement(tag);el.className=(classNames||[]).map(function(cn){return nb.prefix+cn}).join(" ");return el};var escapeHTML=function(raw){var replaced=raw.replace(//g,">");return replaced};var joinText=function(text){if(text.join){return text.map(joinText).join("")}else{return text}};var getMarkdown=function(){var lib=root.marked||typeof require==="function"&&require("marked");return lib&&lib.parse};var getAnsi=function(){var lib=root.ansi_up||typeof require==="function"&&require("ansi_up");return lib&&lib.ansi_to_html};var getSanitizer=function(){var lib=root.DOMPurify||typeof require==="function"&&require("dompurify");if(isBrowser){return lib&&lib.sanitize}else{return lib(dom.window).sanitize}};var nb={prefix:"nb-",markdown:getMarkdown()||ident,ansi:getAnsi()||ident,sanitizer:getSanitizer()||ident,executeJavaScript:false,highlighter:ident,VERSION:VERSION};nb.Input=function(raw,cell){this.raw=raw;this.cell=cell};nb.Input.prototype.render=function(){if(!this.raw.length){return makeElement("div")}var holder=makeElement("div",["input"]);var cell=this.cell;if(typeof cell.number==="number"){holder.setAttribute("data-prompt-number",this.cell.number)}var pre_el=makeElement("pre");var code_el=makeElement("code");var notebook=cell.worksheet.notebook;var m=notebook.metadata;var lang=this.cell.raw.language||m.language||m.kernelspec&&m.kernelspec.language||m.language_info&&m.language_info.name;code_el.setAttribute("data-language",lang);code_el.className="lang-"+lang;code_el.innerHTML=nb.highlighter(escapeHTML(joinText(this.raw)),pre_el,code_el,lang);pre_el.appendChild(code_el);holder.appendChild(pre_el);this.el=holder;return holder};var imageCreator=function(format){return function(data){var el=makeElement("img",["image-output"]);el.src="data:image/"+format+";base64,"+joinText(data).replace(/\n/g,"");return el}};nb.display={};nb.display.text=function(text){var el=makeElement("pre",["text-output"]);el.innerHTML=nb.highlighter(nb.ansi(escapeHTML(joinText(text))),el);return el};nb.display["text/plain"]=nb.display.text;nb.display.html=function(html){var el=makeElement("div",["html-output"]);el.innerHTML=nb.sanitizer(joinText(html));return el};nb.display["text/html"]=nb.display.html;nb.display.marked=function(md){return nb.display.html(nb.markdown(joinText(md)))};nb.display["text/markdown"]=nb.display.marked;nb.display.svg=function(svg){var el=makeElement("div",["svg-output"]);el.innerHTML=nb.sanitizer(joinText(svg));return el};nb.display["text/svg+xml"]=nb.display.svg;nb.display["image/svg+xml"]=nb.display.svg;nb.display.latex=function(latex){var el=makeElement("div",["latex-output"]);if(root.renderMathInElement!=null){el.innerText=joinText(latex);root.renderMathInElement(el,{delimiters:math_delimiters})}else{el.innerText=joinText(latex)}return el};nb.display["text/latex"]=nb.display.latex;nb.display.javascript=function(js){if(nb.executeJavaScript){var el=makeElement("script");el.innerHTML=joinText(js);return el}else{var el=document.createElement("pre");el.innerText="JavaScript execution is disabled for this notebook";return el}};nb.display["application/javascript"]=nb.display.javascript;nb.display.png=imageCreator("png");nb.display["image/png"]=nb.display.png;nb.display.jpeg=imageCreator("jpeg");nb.display["image/jpeg"]=nb.display.jpeg;nb.display_priority=["png","image/png","jpeg","image/jpeg","svg","image/svg+xml","text/svg+xml","html","text/html","text/markdown","latex","text/latex","javascript","application/javascript","text","text/plain"];var render_display_data=function(){var o=this;var formats=nb.display_priority.filter(function(d){return o.raw.data?o.raw.data[d]:o.raw[d]});var format=formats[0];if(format){if(nb.display[format]){return nb.display[format](o.raw[format]||o.raw.data[format])}}return makeElement("div",["empty-output"])};var render_error=function(){var el=makeElement("pre",["pyerr"]);var raw=this.raw.traceback.join("\n");el.innerHTML=nb.highlighter(nb.ansi(escapeHTML(raw)),el);return el};nb.Output=function(raw,cell){this.raw=raw;this.cell=cell;this.type=raw.output_type};nb.Output.prototype.renderers={display_data:render_display_data,execute_result:render_display_data,pyout:render_display_data,pyerr:render_error,error:render_error,stream:function(){var el=makeElement("pre",[this.raw.stream||this.raw.name]);var raw=joinText(this.raw.text);el.innerHTML=nb.highlighter(nb.ansi(escapeHTML(raw)),el);return el}};nb.Output.prototype.render=function(){var outer=makeElement("div",["output"]);if(typeof this.cell.number==="number"){outer.setAttribute("data-prompt-number",this.cell.number)}var inner=this.renderers[this.type].call(this);outer.appendChild(inner);this.el=outer;return outer};nb.coalesceStreams=function(outputs){if(!outputs.length){return outputs}var last=outputs[0];var new_outputs=[last];outputs.slice(1).forEach(function(o){if(o.raw.output_type==="stream"&&last.raw.output_type==="stream"&&o.raw.stream===last.raw.stream&&o.raw.name===last.raw.name){last.raw.text=last.raw.text.concat(o.raw.text)}else{new_outputs.push(o);last=o}});return new_outputs};nb.Cell=function(raw,worksheet){var cell=this;cell.raw=raw;cell.worksheet=worksheet;cell.type=raw.cell_type;if(cell.type==="code"){cell.number=raw.prompt_number>-1?raw.prompt_number:raw.execution_count;var source=raw.input||[raw.source];cell.input=new nb.Input(source,cell);var raw_outputs=(cell.raw.outputs||[]).map(function(o){return new nb.Output(o,cell)});cell.outputs=nb.coalesceStreams(raw_outputs)}};var math_delimiters=[{left:"$$",right:"$$",display:true},{left:"\\[",right:"\\]",display:true},{left:"\\(",right:"\\)",display:false},{left:"$",right:"$",display:false}];nb.Cell.prototype.renderers={markdown:function(){var el=makeElement("div",["cell","markdown-cell"]);var joined=joinText(this.raw.source);if(root.renderMathInElement!=null){el.innerHTML=nb.sanitizer(joined);root.renderMathInElement(el,{delimiters:math_delimiters});el.innerHTML=nb.sanitizer(nb.markdown(el.innerHTML.replace(/>/g,">")))}else{el.innerHTML=nb.sanitizer(nb.markdown(joined))}return el},heading:function(){var el=makeElement("h"+this.raw.level,["cell","heading-cell"]);el.innerHTML=nb.sanitizer(joinText(this.raw.source));return el},raw:function(){var el=makeElement("div",["cell","raw-cell"]);el.innerHTML=escapeHTML(joinText(this.raw.source));return el},code:function(){var cell_el=makeElement("div",["cell","code-cell"]);cell_el.appendChild(this.input.render());var output_els=this.outputs.forEach(function(o){cell_el.appendChild(o.render())});return cell_el}};nb.Cell.prototype.render=function(){var el=this.renderers[this.type].call(this);this.el=el;return el};nb.Worksheet=function(raw,notebook){var worksheet=this;this.raw=raw;this.notebook=notebook;this.cells=raw.cells.map(function(c){return new nb.Cell(c,worksheet)});this.render=function(){var worksheet_el=makeElement("div",["worksheet"]);worksheet.cells.forEach(function(c){worksheet_el.appendChild(c.render())});this.el=worksheet_el;return worksheet_el}};nb.Notebook=function(raw,config){var notebook=this;this.raw=raw;this.config=config;var meta=this.metadata=raw.metadata||{};this.title=meta.title||meta.name;var _worksheets=raw.worksheets||[{cells:raw.cells}];this.worksheets=_worksheets.map(function(ws){return new nb.Worksheet(ws,notebook)});this.sheet=this.worksheets[0]};nb.Notebook.prototype.render=function(){var notebook_el=makeElement("div",["notebook"]);this.worksheets.forEach(function(w){notebook_el.appendChild(w.render())});this.el=notebook_el;return notebook_el};nb.parse=function(nbjson,config){return new nb.Notebook(nbjson,config)};if(typeof define==="function"&&define.amd){define(function(){return nb})}if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports){exports=module.exports=nb}exports.nb=nb}else{root.nb=nb}}).call(this); 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

~: repo-view :~

3 |

4 | 5 |

6 | — It is a static web page that uses Github REST API to preview repos.
7 | It lets employers see your private GitHub repositories in your CV (with your permission of course) without making them public 💵↗️↗️ 8 |
9 |

Overview :-

10 |
 11 | let's say you have a really good private repo that you wanna add to your CV.
 12 | wait! it is private. right ? then how ?? would you make it public ???
 13 | hell NO! it is private and MUST stay as is.
 14 | sure you don't wanna your business proprietary code base be published as open-source.
 15 | man!, you just wanna show it only to some employers.
 16 | of course you can add these employers as collaborators with read-only permissions to this private repo.
 17 | but, first you will have to get their GitHub usernames. also, you will have to add them one by one.
 18 | maybe you gonna use a cloud storage service with code syntax highlighting ?
 19 | oh, what about synchronization between your Github repo and these cloud services ??
 20 | what if the cloud shared folder got found by search engines ???
 21 | 
22 |
23 |

Solution :-

24 |
 25 | there is a website called GitFront.io :
 26 |     it can host your private repo and provide a presentation url that is accessible to anyone who has it.
 27 | - problem with GitFront is that: it allows hosting only less-than 100 MB repo for free account.
 28 |     if your private repo is larger than that, you gonna have to pay 💵💵💲
 29 |     ( this was the reason why I created this project )
 30 | - your GitFront repo does not get directly synchronized with GitHub
 31 | 
32 |
 33 | my repo-view does exactly the same except that:
 34 | - it is a static web page (no backend) -> your code exists only on GitHub. it does not goto any 3rd party server
 35 | - it uses official GitHub REST API, it gets files directly from GitHub
 36 | - no problem if repo is larger than 100 MB. you still can preview it
 37 | - it is a single page application
 38 | - it has some extra features eg: dark theme switch, preview pdf, detect internet disconnect...
 39 | 
40 |

41 | 42 |

43 |
44 |

Usage :-

45 |
46 | Manual Usage [deprecated] 47 |
 48 | it is a static web page. so, configuration is gonna be passed as url params.
 49 | 

params :-

50 | token : string => github access_token 51 | warning: use only tokens with only permissions: repos read-only
52 | repo : string => the repo name on github
53 | owner : string => the repo owner username (or organization username) on github 54 | if omitted, it would be fetched from Github (the username who created the access_token). 55 | so, if that username is not the owner of the repo, you gonna get ERROR 404
56 | token_ready : bool => it is optional: default is "false" => 57 | if true: provide token param is gonna be used as is 58 | else: 59 | the provided token param must be provied as encodes base64 string using js "btoa" method 60 | it first will be decoded back ( using js "atob" method ) 61 | why? 62 | - the token does not exist readily on url: 63 | if any web scrapper found the url and got the token and tried it on their terminal: 64 | it wont work. it fist need to be decoded 65 | - if the one ,to whom you send the url, tried to use the token (in their terminal) 66 | before they view the url on web browser. and it worked. 67 | they may think they got hands on a treasure. and they may exploit this token. 68 | but if it did not work (ie: it was base64-encoded): sooner or later, after they open the url, 69 | they gonna find that they can just get the real token from the web page. 70 | so, they wont get too excited about that token.
71 | so, the url would be: https://ibrahemesam.github.io/repo-view/?token=<token>&repo=<repo-name>&owner=<owner-username> 72 |
73 |
74 |

New way of usage:-

75 |
76 |
 77 | 

First, go to this page 👉 Create repo-view URL

78 | Then, 👇👇👇👇👇 79 |

80 | 81 | NB: If you have to, append this string "&hide_acknowledgement=true" 82 | to the created url to hide this acknowledgement. 83 | 84 | Otherwise, leave it to support me ❤️. 85 |

86 |
87 |
 88 | NB:
 89 | Github Repository: should be just the repo name. Not the whole url. ie: "foo" instead of "https://github.com/username/foo"
 90 | NB:
 91 | The resulting repo-view url has its token as encrypted string.
 92 | - Why being encrypted?
 93 | = Because when putting the url in a github.io page (or anywhere on Github),
 94 |   if the token is not encrypted, Github detects it and think it exists by mistake as a vulnerability.
 95 |   So, Github immediately disables the token, and the repo-view url is disabled as well.
 96 | 
97 |

DEMO :-

98 |
 99 | this demo repo is a private github repository. if you visit it, you get 404 because it is Private.
100 | 
101 |
NB:  public repos can also be viewed
102 |
103 |
104 | !! Warning !!: any one with the url can preview and clone the repo
105 |     so, put it only on your CV and send it only to employers
106 |         do NOT put it on places where it may be stolen eg: https://<your_username>.github.io
107 |             or the production business website of the private repo
108 | 
109 |

To create a token :-

110 |
111 | 1 - go to Github > settings > Developer settings > Personal access tokens > Fine-gained tokens
112 | 2 - set "Token name" and "Expiration" date.
113 | 3 - on "Repository access" section: select "Only select repositories" then select the repo you wanna use.
114 | 4 - under "Permissions section": under "Repository permissions": select "Contents" with "Read-only" access level.
115 | 5 - click "Generate token" then copy it.
116 | 
117 |
118 | !! Warning !!: any one with the url can get the token.
119 |     so, when creating the token, do NOT add any permissions to the token other than
120 |         read-only access to repository content (access to only one private repo. NOT all !).
121 |     otherwise, the token may be exploited !!
122 | 
123 |
124 |

feel free to report any bug through issues section

125 |
if you like this project, give it a Star ⭐
126 |
127 |

LICENSE :-

128 |
129 | This project is provided "AS IS" with absolutely "NO WARRANTY".
130 | If you gonna use its source-code somewhere: make a clear credit refering to this repo-view repository.
131 | 
132 | 133 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | /*a { color: #dc8100; text-decoration: none; } 2 | a:hover { color: #efe8e8; text-decoration: none; }*/ 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | width: 100vw !important; 7 | top: 0 !important; 8 | position: fixed; 9 | overflow-x: hidden; 10 | overflow: auto; 11 | } 12 | 13 | .container { 14 | height: 100vh !important; 15 | } 16 | 17 | img.file { 18 | content: url("../asset/file.svg"); 19 | } 20 | 21 | img.dir { 22 | content: url("../asset/dir.svg"); 23 | } 24 | 25 | .location#location-div * { 26 | user-select: none; 27 | } 28 | 29 | .blob-view .content { 30 | overflow: auto; 31 | padding: 2%; 32 | overflow-x: auto; 33 | } 34 | 35 | @media screen and (orientation: landscape) { 36 | .blob-view .content > .nb-notebook { 37 | margin-inline-start: 10%; 38 | } 39 | } 40 | 41 | @media screen and (orientation: portrait) { 42 | .blob-view .content > .nb-notebook { 43 | margin-inline-start: 25%; 44 | } 45 | } 46 | 47 | .blob-view .content pre { 48 | width: 100%; 49 | margin-top: 0; 50 | margin-bottom: 0; 51 | margin: 0; 52 | } 53 | 54 | /*.blob-view .content pre code { 55 | width: calc(100% - 4.8em); 56 | }*/ 57 | 58 | .blob-view .content pre.line-numbers { 59 | width: calc(100% - 4.8em); 60 | } 61 | 62 | .blob-view .content:has(iframe[src*="pdf.js"]) { 63 | overflow-y: hidden; 64 | } 65 | 66 | .blob-view .content pre:not(.line-numbers) { 67 | padding: 1% 1%; 68 | width: 98%; 69 | /* text-wrap: balance; */ 70 | } 71 | 72 | ul.tree { 73 | max-height: 40vh; 74 | overflow: auto; 75 | } 76 | 77 | body:has(ul.tree[hidden]), 78 | body:has(#preview-div[hidden]) { 79 | height: 100vh !important; 80 | overflow: hidden; 81 | } 82 | 83 | body:has(#preview-div[hidden]) ul.tree { 84 | max-height: 75vh; 85 | } 86 | 87 | body:has(ul.tree[hidden]) .blob-view .content { 88 | height: 70vh !important; 89 | } 90 | 91 | div.footer { 92 | /* position: fixed; */ 93 | bottom: 2px; 94 | } 95 | 96 | html[acknowledgement-hidden="true"] div.footer:has(.made-with-love) { 97 | display: none; 98 | } 99 | 100 | html[acknowledgement-hidden="true"] #main-loader-spinner-div { 101 | top: 70%; 102 | } 103 | 104 | body:has(#preview-div[hidden]) div.footer 105 | /*body:has(ul.tree[hidden]) div.footer*/ { 106 | position: fixed; 107 | bottom: 8px; 108 | /* transform: translateY(-50%); */ 109 | } 110 | 111 | #main-loader-spinner-div { 112 | position: fixed; 113 | top: 60%; 114 | zoom: 5; 115 | left: 50%; 116 | transform: translate(-50%, -100%); 117 | } 118 | body:has(ul.tree:not([hidden])) div#main-loader-spinner-div, 119 | body:has(#preview-div:not([hidden])) div#main-loader-spinner-div, 120 | body:has(.create-url:not([hidden])) div#main-loader-spinner-div { 121 | display: none; 122 | } 123 | 124 | body:has(ul.tree[hidden]):has(#preview-div[hidden]):has( 125 | .container div.location[hidden] 126 | ) 127 | div.footer { 128 | position: fixed; 129 | top: 60%; 130 | left: 50%; 131 | transform: translateX(-50%); 132 | margin: auto; 133 | padding: 10px; 134 | outline: none; 135 | user-select: none; 136 | height: fit-content; 137 | width: max-content; 138 | } 139 | 140 | div.location * { 141 | direction: ltr; 142 | } 143 | 144 | body:has(#tree-ul a[style*="text-decoration: none"][style*="cursor: default"]) 145 | div#loader-ellipsis-div { 146 | display: inline-block; 147 | } 148 | div#loader-ellipsis-div { 149 | display: none; 150 | position: fixed; 151 | top: 0vh; 152 | right: 9vw; 153 | z-index: 999999; 154 | } 155 | 156 | div#loader-ellipsis-div:before { 157 | content: ""; 158 | background-color: lavenderblush; 159 | height: 37px; 160 | position: fixed; 161 | top: 2vh; 162 | width: 62px; 163 | border-radius: 30px; 164 | } 165 | 166 | div#no-internet-div { 167 | background-color: lightcoral; 168 | position: fixed; 169 | top: 2vh; 170 | left: 50%; 171 | transform: translateX(-50%); 172 | border-radius: 10px; 173 | background-image: url(../asset/no-wifi.svg); 174 | background-repeat: no-repeat; 175 | background-position: 8px center; 176 | background-size: 30px; 177 | height: auto; 178 | align-items: center; 179 | width: max-content; 180 | padding: 8px; 181 | font-weight: bold; 182 | text-indent: 35px; 183 | } 184 | 185 | .footer > .made-with-love { 186 | border: solid; 187 | border-radius: 5px; 188 | border-color: cornflowerblue; 189 | outline: inherit; 190 | user-select: inherit; 191 | width: fit-content; 192 | margin: auto; 193 | /* margin-top: 2rem; */ 194 | padding: 5px; 195 | } 196 | 197 | body:has(#preview-div[hidden]) div.footer > .made-with-love { 198 | margin-top: 2rem; 199 | } 200 | 201 | body:has(.create-url:not([hidden])) div.footer { 202 | position: relative !important; 203 | margin: auto !important; 204 | padding: 10px !important; 205 | outline: none !important; 206 | user-select: none !important; 207 | height: fit-content !important; 208 | width: max-content !important; 209 | bottom: 0 !important; 210 | top: 0 !important; 211 | transform: initial !important; 212 | left: 0 !important; 213 | } 214 | 215 | body:has(.create-url:not([hidden])) div.footer > div { 216 | margin-top: 5px; 217 | } 218 | 219 | .create-url { 220 | width: 90vw; 221 | background-color: rgba(0, 0, 0, 0.2); 222 | position: relative; 223 | margin: auto; 224 | margin-top: 2rem; 225 | border-radius: 10px; 226 | backdrop-filter: blur(10px); 227 | border: 2px solid rgba(255, 255, 255, 0.1); 228 | box-shadow: 0 0 40px rgba(8, 7, 16, 0.6); 229 | padding: 50px 35px; 230 | padding-top: 0; 231 | padding-bottom: 70px; 232 | bottom: 0; 233 | user-select: none; 234 | } 235 | .create-url * { 236 | font-family: "Poppins", sans-serif; 237 | /* color: #ffffff; */ 238 | letter-spacing: 0.5px; 239 | outline: none; 240 | border: none; 241 | } 242 | .create-url h3 { 243 | font-size: xx-large; 244 | font-weight: bolder; 245 | line-height: 25px; 246 | text-align: center; 247 | } 248 | 249 | .create-url label { 250 | display: block; 251 | margin-top: 30px; 252 | font-size: 16px; 253 | font-weight: 500; 254 | } 255 | .create-url .input-field { 256 | display: flex; 257 | flex-direction: row; 258 | height: 50px; 259 | overflow: hidden; 260 | border-radius: 5px; 261 | } 262 | .create-url .input-field > input { 263 | height: inherit; 264 | flex-grow: 1; 265 | background-color: rgba(255, 255, 255, 0.65); 266 | border-radius: 3px; 267 | /* padding: 0 10px; */ 268 | font-size: 14px; 269 | font-weight: 300; 270 | text-align: center; 271 | text-overflow: ellipsis; 272 | padding-left: 10px; 273 | } 274 | .create-url .input-field > button { 275 | background-image: url("../asset/paste.svg"); 276 | background-size: 90% 90%; 277 | background-position: center center; 278 | background-repeat: no-repeat; 279 | width: 40px; 280 | cursor: pointer; 281 | } 282 | .create-url input::placeholder { 283 | color: gray; 284 | font-weight: bold; 285 | text-align: center; 286 | } 287 | .create-url input#token { 288 | font-size: x-small; 289 | font-weight: 200; 290 | } 291 | .create-url > button { 292 | margin-top: 20px; 293 | width: 100%; 294 | background-color: #ffffff; 295 | color: #080710; 296 | padding: 15px 0; 297 | font-size: 18px; 298 | font-weight: 600; 299 | border-radius: 5px; 300 | cursor: pointer; 301 | position: relative; 302 | left: 5px; 303 | } 304 | .create-url .created-url { 305 | position: absolute; 306 | bottom: 10px; 307 | left: 50%; 308 | transform: translateX(-50%); 309 | height: 40px; 310 | display: flex; 311 | flex-direction: row; 312 | width: calc(100% - 2 * 20px); 313 | border-radius: 10px; 314 | border: 1px solid rgba(0, 0, 0, 0.5); 315 | } 316 | .create-url .created-url > a { 317 | flex-grow: 1; 318 | border: inherit; 319 | border-top-left-radius: inherit; 320 | border-bottom-left-radius: inherit; 321 | overflow: hidden; 322 | text-overflow: ellipsis; 323 | user-select: initial; 324 | font-size: xx-small; 325 | padding: 4px; 326 | line-height: 10px; 327 | } 328 | .create-url .created-url > button { 329 | width: 80px; 330 | border: inherit; 331 | border-top-right-radius: inherit; 332 | border-bottom-right-radius: inherit; 333 | color: #080710; 334 | font-size: 18px; 335 | font-weight: 600; 336 | cursor: pointer; 337 | } 338 | 339 | div.download-logo { 340 | background-image: url(../asset/browser-download-symbolic.svg); 341 | background-position: center center; 342 | background-size: 20px 50px; 343 | background-repeat: no-repeat; 344 | height: 15px; 345 | width: 40px; 346 | border: 1px solid gray; 347 | border-radius: 5px; 348 | } 349 | 350 | html:has(head > style.darkreader) div.download-logo { 351 | background-image: url(../asset/browser-download-symbolic-dark.svg); 352 | } 353 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --text-color: #333; 3 | --link-color: #16b; 4 | --background-color: #fff; 5 | 6 | --border-color-hard: #e0e0e0; 7 | --border-color-soft: #f0f0f0; 8 | 9 | --markdown-line-color: #eee; 10 | --markdown-code-color: #f2f2f2; 11 | 12 | --header-color: #444; 13 | --header-background-color: #fafafa; 14 | 15 | --button-color: #000; 16 | --button-background-color: #eee; 17 | --button-border-color: #ddd; 18 | } 19 | 20 | html, 21 | body { 22 | min-height: 100vh; 23 | } 24 | 25 | body { 26 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, 27 | Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 28 | margin-top: 0; 29 | margin-bottom: 0; 30 | color: var(--text-color); 31 | } 32 | 33 | input { 34 | color: var(--text-color); 35 | background: var(--background-color); 36 | border: 1px solid var(--border-color-hard); 37 | border-radius: 5px; 38 | } 39 | 40 | .container { 41 | position: relative; 42 | max-height: 100vh; 43 | overflow: auto; 44 | } 45 | 46 | .container > *:first-child { 47 | padding-top: 2rem; 48 | margin-top: 0; 49 | } 50 | 51 | .container > .space { 52 | padding-bottom: 3rem; 53 | } 54 | 55 | .tree { 56 | font-size: 0.9rem; 57 | border: 1px solid var(--border-color-hard); 58 | list-style-type: none; 59 | padding: 0; 60 | margin: 0; 61 | } 62 | 63 | .tree li { 64 | padding: 1em; 65 | } 66 | 67 | .tree .entry { 68 | position: relative; 69 | display: flex; 70 | border-top: 1px solid var(--border-color-soft); 71 | } 72 | 73 | .tree .entry a { 74 | color: var(--text-color); 75 | } 76 | 77 | .tree .entry a::before { 78 | content: ""; 79 | position: absolute; 80 | left: 0; 81 | top: 0; 82 | width: 100%; 83 | height: 100%; 84 | z-index: 1; 85 | } 86 | 87 | .tree .entry a { 88 | text-decoration: none; 89 | } 90 | .tree .entry a:hover { 91 | text-decoration: underline; 92 | text-underline-position: under; 93 | } 94 | 95 | article.readme, 96 | .tree, 97 | .blob-view, 98 | .location { 99 | border-radius: 5px; 100 | margin: 2em auto; 101 | margin-bottom: 1.2em; 102 | max-width: 95vw; 103 | } 104 | 105 | article.readme, 106 | .blob-view { 107 | border: 1px solid var(--border-color-hard); 108 | } 109 | 110 | .location { 111 | color: #aaa; 112 | } 113 | .location > * { 114 | margin: 0 0.1em; 115 | color: var(--text-color); 116 | } 117 | 118 | .location a { 119 | color: var(--link-color); 120 | text-decoration: none; 121 | } 122 | .location a:hover { 123 | text-decoration: underline; 124 | } 125 | 126 | .location *:nth-child(1) { 127 | font-weight: bold; 128 | filter: brightness(80%); 129 | } 130 | 131 | .header { 132 | display: flex; 133 | flex-direction: row; 134 | align-items: center; 135 | padding: 1em; 136 | font-size: 0.9rem; 137 | background-color: var(--header-background-color); 138 | color: var(--header-color); 139 | overflow-x: auto; 140 | } 141 | 142 | .header > div:first-child { 143 | flex-grow: 1; 144 | } 145 | 146 | .last { 147 | display: grid; 148 | grid-template-columns: 1fr; 149 | grid-gap: 4px; 150 | margin: -1em auto; 151 | margin-inline-start: 5px; 152 | } 153 | 154 | .btn { 155 | display: inline-block; 156 | font-size: 10pt; 157 | background-color: var(--button-background-color); 158 | border: 1px solid var(--button-border-color); 159 | text-decoration: none; 160 | color: var(--button-color); 161 | cursor: pointer; 162 | padding: 0.5em 1.5em; 163 | border-radius: 5px; 164 | min-width: 3em; 165 | text-align: center; 166 | 167 | -webkit-touch-callout: none; 168 | -webkit-user-select: none; 169 | -khtml-user-select: none; 170 | -moz-user-select: none; 171 | -ms-user-select: none; 172 | user-select: none; 173 | } 174 | 175 | .icon { 176 | margin: auto 1em auto 0; 177 | width: 16px; 178 | height: 16px; 179 | } 180 | 181 | .content { 182 | padding: 1em; 183 | border-top: 1px solid var(--border-color-soft); 184 | } 185 | 186 | .content .content-img { 187 | display: flex; 188 | justify-content: center; 189 | } 190 | .content .content-img img { 191 | max-width: 100%; 192 | } 193 | 194 | .footer { 195 | zoom: 1.5; 196 | bottom: 0; 197 | width: 100%; 198 | /* height: 3rem; */ 199 | 200 | margin: auto; 201 | text-align: center; 202 | font-size: 0.8rem; 203 | } 204 | 205 | .footer a { 206 | color: var(--link-color); 207 | } 208 | 209 | .markdown { 210 | font-size: 0.9rem; 211 | padding: 2em; 212 | } 213 | .markdown h1 { 214 | font-size: 1.5rem; 215 | margin: 1em 0; 216 | padding-bottom: 0.3em; 217 | border-bottom: 1px solid var(--markdown-line-color); 218 | } 219 | .markdown h2 { 220 | font-size: 1.3rem; 221 | margin: 1em 0; 222 | padding-bottom: 0.3em; 223 | border-bottom: 1px solid var(--markdown-line-color); 224 | } 225 | .markdown p { 226 | line-height: 1.5; 227 | } 228 | .markdown img { 229 | vertical-align: middle; 230 | max-width: 100%; 231 | } 232 | .markdown a { 233 | color: var(--link-color); 234 | text-decoration: none; 235 | } 236 | .markdown a:hover { 237 | text-decoration: underline; 238 | } 239 | 240 | .markdown pre { 241 | background-color: var(--background-color); 242 | font-size: 0.8rem; 243 | padding: 0.8em 1em; 244 | border: 1px solid var(--markdown-line-color); 245 | border-radius: 5px; 246 | } 247 | .markdown pre code { 248 | background-color: inherit; 249 | padding: 0; 250 | } 251 | .markdown code { 252 | background-color: var(--markdown-code-color); 253 | border-radius: 5px; 254 | padding: 2px 4px; 255 | } 256 | .markdown p code { 257 | font-size: 0.88em; 258 | } 259 | .markdown table { 260 | border-spacing: 0; 261 | border-collapse: collapse; 262 | } 263 | .markdown td, 264 | .markdown th { 265 | padding: 0.5em 1em; 266 | border: 1px solid var(--markdown-line-color); 267 | } 268 | .markdown br { 269 | display: none; 270 | } 271 | 272 | /* Spinner elipses */ 273 | 274 | .loader--ellipsis { 275 | display: inline-block; 276 | position: relative; 277 | height: 64px; 278 | width: 64px; 279 | } 280 | 281 | .loader--ellipsis div { 282 | position: absolute; 283 | top: 27px; 284 | animation-timing-function: cubic-bezier(0, 1, 1, 0); 285 | border-radius: 50%; 286 | height: 11px; 287 | width: 11px; 288 | } 289 | 290 | .loader--ellipsis div:nth-child(1) { 291 | left: 6px; 292 | animation: loader--ellipsis1 0.6s infinite; 293 | } 294 | 295 | .loader--ellipsis div:nth-child(2) { 296 | left: 6px; 297 | animation: loader--ellipsis2 0.6s infinite; 298 | } 299 | 300 | .loader--ellipsis div:nth-child(3) { 301 | left: 26px; 302 | animation: loader--ellipsis2 0.6s infinite; 303 | } 304 | 305 | .loader--ellipsis div:nth-child(4) { 306 | left: 45px; 307 | animation: loader--ellipsis3 0.6s infinite; 308 | } 309 | 310 | @keyframes loader--ellipsis1 { 311 | 0% { 312 | transform: scale(0); 313 | } 314 | 315 | 100% { 316 | transform: scale(1); 317 | } 318 | } 319 | 320 | @keyframes loader--ellipsis3 { 321 | 0% { 322 | transform: scale(1); 323 | } 324 | 325 | 100% { 326 | transform: scale(0); 327 | } 328 | } 329 | 330 | @keyframes loader--ellipsis2 { 331 | 0% { 332 | transform: translate(0, 0); 333 | } 334 | 335 | 100% { 336 | transform: translate(19px, 0); 337 | } 338 | } 339 | 340 | .loader--spinner div:after, 341 | .loader--ellipsis div { 342 | background: crimson; 343 | } 344 | 345 | .loader--spinner { 346 | display: inline-block; 347 | position: relative; 348 | color: official; 349 | height: 60px; 350 | width: 60px; 351 | } 352 | 353 | .loader--spinner div { 354 | animation: loader--spinner 1.2s linear infinite; 355 | transform-origin: 30px 30px; 356 | } 357 | 358 | .loader--spinner div:after { 359 | display: block; 360 | position: absolute; 361 | top: 3px; 362 | left: 27px; 363 | border-radius: 20%; 364 | content: " "; 365 | height: 10px; 366 | width: 5px; 367 | } 368 | 369 | .loader--spinner div:nth-child(1) { 370 | animation-delay: -1.1s; 371 | transform: rotate(0deg); 372 | } 373 | 374 | .loader--spinner div:nth-child(2) { 375 | animation-delay: -1s; 376 | transform: rotate(30deg); 377 | } 378 | 379 | .loader--spinner div:nth-child(3) { 380 | animation-delay: -0.9s; 381 | transform: rotate(60deg); 382 | } 383 | 384 | .loader--spinner div:nth-child(4) { 385 | animation-delay: -0.8s; 386 | transform: rotate(90deg); 387 | } 388 | 389 | .loader--spinner div:nth-child(5) { 390 | animation-delay: -0.7s; 391 | transform: rotate(120deg); 392 | } 393 | 394 | .loader--spinner div:nth-child(6) { 395 | animation-delay: -0.6s; 396 | transform: rotate(150deg); 397 | } 398 | 399 | .loader--spinner div:nth-child(7) { 400 | animation-delay: -0.5s; 401 | transform: rotate(180deg); 402 | } 403 | 404 | .loader--spinner div:nth-child(8) { 405 | animation-delay: -0.4s; 406 | transform: rotate(210deg); 407 | } 408 | 409 | .loader--spinner div:nth-child(9) { 410 | animation-delay: -0.3s; 411 | transform: rotate(240deg); 412 | } 413 | 414 | .loader--spinner div:nth-child(10) { 415 | animation-delay: -0.2s; 416 | transform: rotate(270deg); 417 | } 418 | 419 | .loader--spinner div:nth-child(11) { 420 | animation-delay: -0.1s; 421 | transform: rotate(300deg); 422 | } 423 | 424 | .loader--spinner div:nth-child(12) { 425 | animation-delay: 0s; 426 | transform: rotate(330deg); 427 | } 428 | 429 | @keyframes loader--spinner { 430 | 0% { 431 | opacity: 1; 432 | } 433 | 434 | 100% { 435 | opacity: 0; 436 | } 437 | } 438 | 439 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | window.DEFAULT_API_HEADERS = { 2 | headers: { 3 | "X-GitHub-Api-Version": "2022-11-28", 4 | "Accept-Charset": "UTF-8", 5 | }, 6 | }; 7 | const NEW_LINE_EXP = /\n(?!$)/g; 8 | 9 | const rawBtn = document.getElementById("raw-btn"), 10 | previewDiv = document.getElementById("preview-div"), 11 | previewItemNameDiv = document.getElementById("preview-item-name-div"), 12 | previewItemContentDiv = document.getElementById("preview-item-content-div"), 13 | treeUl = document.getElementById("tree-ul"), 14 | cwdNameDiv = document.getElementById("cwd-name-div"), 15 | locationDiv = document.getElementById("location-div"), 16 | treeHeaderLast = document.querySelector(".tree > .header > .last"), 17 | noInternetDiv = document.getElementById("no-internet-div"), 18 | footerDiv = document.querySelector("div.footer"), 19 | mainLoaderSpinnerDiv = document.getElementById("main-loader-spinner-div"), 20 | createUrlDiv = document.getElementById("create-url"), 21 | aCreatedUrl = document.querySelector("#created-url > a"), 22 | btnCopyCreatedUrl = document.querySelector( 23 | 'button[name="btn-copy-created-url"]' 24 | ), 25 | inpToken = document.getElementById("token"), 26 | inpUsername = document.getElementById("owner"), 27 | inpRepo = document.getElementById("repo"), 28 | btnDownloadDir = document.getElementById("btnDownloadDir"), 29 | btnDownloadFile = document.getElementById("btnDownloadFile"); 30 | 31 | var onlineLock = {}; 32 | onlineLock.lock = () => { 33 | onlineLock.p = new Promise((r) => (onlineLock.unlock = r)); 34 | }; 35 | onlineLock.wait = () => onlineLock.p; 36 | 37 | window.addEventListener("online", () => { 38 | onlineLock.unlock(); 39 | noInternetDiv.hidden = true; 40 | }); 41 | 42 | window.addEventListener("offline", () => { 43 | noInternetDiv.hidden = false; 44 | }); 45 | 46 | if (!navigator.onLine) noInternetDiv.hidden = true; 47 | 48 | async function initMarkdownView(md) { 49 | var el = document.createElement("pre"); 50 | el.classList.add("markdown"); 51 | // console.log(md); 52 | el.innerHTML = DOMPurify.sanitize(await marked.parse(md)); 53 | el.querySelectorAll("[data-repoview-lazy-src]").forEach(async (el) => { 54 | // lazy-loading src 55 | const observer = new IntersectionObserver((entries) => { 56 | entries.forEach(async (entry) => { 57 | if (entry.isIntersecting) { 58 | observer.disconnect(); // only load src once 59 | // console.log(el); 60 | var obj = JSON.parse(el.getAttribute("data-repoview-lazy-src")); 61 | el.removeAttribute("data-repoview-lazy-src"); 62 | el.setAttribute( 63 | "src", 64 | resopnse2imgSrc( 65 | await octokit.request( 66 | "GET /repos/{owner}/{repo}/contents/{path}", 67 | { 68 | owner: obj.owner, 69 | repo: obj.repo, 70 | path: obj.path, 71 | headers: DEFAULT_API_HEADERS.headers, 72 | } 73 | ) 74 | ) 75 | ); 76 | } 77 | }); 78 | }); 79 | observer.observe(el); 80 | }); 81 | // :href 82 | el.querySelectorAll("a[data-repoview-href]").forEach((el) => { 83 | var path = el.getAttribute("data-repoview-href"); 84 | el.removeAttribute("data-repoview-href"); 85 | if (el.hasAttribute("href")) e.removeAttribute("href"); 86 | el.style.cursor = "pointer"; 87 | el.setAttribute("title", path); 88 | el.setAttribute("onclick", `gotoPath("${path}"); return false;`); 89 | }); 90 | // md links 91 | el.querySelectorAll('a[href^="data-repoview-href="]').forEach((el) => { 92 | var path = el.getAttribute("href").slice("data-repoview-href=".length); 93 | el.removeAttribute("href"); 94 | el.style.cursor = "pointer"; 95 | el.setAttribute("onclick", `gotoPath("${path}"); return false;`); 96 | }); 97 | /* open external links in new tab */ 98 | el.querySelectorAll("a").forEach((el) => { 99 | if (!el.hasAttribute("onclick")) { 100 | el.setAttribute("target", "_blank"); 101 | el.setAttribute("rel", "noreferrer"); 102 | } 103 | }); 104 | // el.innerHTML = (await octokit.request('POST /markdown', { 105 | // text: md, 106 | // headers: DEFAULT_API_HEADERS.headers 107 | // })).data; 108 | previewItemContentDiv.appendChild(el); 109 | } 110 | 111 | function decodeContent(str) { 112 | // Going backwards: from bytestream, to percent-encoding, to original string. 113 | return decodeURIComponent( 114 | atob(str) 115 | .split("") 116 | .map(function (c) { 117 | return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); 118 | }) 119 | .join("") 120 | ); 121 | } 122 | 123 | function updateHistory(path) { 124 | history.pushState( 125 | "", 126 | "", 127 | `?token=${window.tokenUrlParam}&owner=${window.owner}&repo=${window.repo}${ 128 | path ? "&path=" + path : "" 129 | }` 130 | ); 131 | } 132 | 133 | window.resopnse2imgSrc = (response) => { 134 | // extracts img-src from GitHub API octokit response 135 | // console.log(response); 136 | var download_url = response.data.download_url; 137 | if (download_url) { 138 | return download_url; 139 | } else if (response.data.content) { 140 | return `data:${mime.getType( 141 | response.data.name 142 | )};base64,${response.data.content.replaceAll("\n", "")}`; 143 | } else { 144 | return null; 145 | } 146 | }; 147 | 148 | window.gotoPath = async function ( 149 | path, 150 | boolUpdateHistory = true, 151 | disableAels = true 152 | ) { 153 | /* first of all: remove click event to prevent multiple concurrency calls to this methods */ 154 | if (disableAels) { 155 | Array.from(locationDiv.querySelectorAll("a")) 156 | .concat(Array.from(treeUl.querySelectorAll("tree-item a"))) 157 | .forEach((a) => { 158 | a.onclick = undefined; 159 | var style = a.style; 160 | style.textDecoration = "none"; 161 | // style.color = 'inherit'; 162 | style.cursor = "default"; 163 | }); 164 | } 165 | // .. 166 | if (path.endsWith("..")) { 167 | // go back 168 | path = path.split("/").slice(0, -2).join("/"); 169 | } 170 | if (boolUpdateHistory) updateHistory(path); 171 | var headerName = path ? path.split("/").at(-1) : window.repo; 172 | // get required path 173 | // 2 caces: requied path is a dir || file 174 | // if it is a dir: do dir view & view dir's README.md if exists 175 | // if it is a file: show content of this file 176 | try { 177 | var response = await octokit.request( 178 | "GET /repos/{owner}/{repo}/contents/{path}", 179 | { 180 | owner, 181 | repo, 182 | path, 183 | headers: DEFAULT_API_HEADERS.headers, 184 | } 185 | ); 186 | } catch (err) { 187 | if (err.response.status === 404) { 188 | // if path is invalid or repo is invalid => panic:- 189 | // owner, repo or path is invalid => panic 190 | showErrorMsg("[404]: owner, repo or path is invalid"); 191 | return; 192 | } 193 | } 194 | treeUl.querySelectorAll("tree-item").forEach((el) => el.remove()); 195 | if (response.data.length) { 196 | /* path is a dir */ 197 | previewDiv.hidden = true; 198 | cwdNameDiv.innerHTML = headerName; 199 | treeUl.hidden = false; 200 | // set treeUl items 201 | if (path != "") { 202 | var treeItem = document.createElement("tree-item"); 203 | treeItem.setAttribute("data-item-name", ".."); 204 | treeItem.setAttribute("data-item-path", path + "/.."); 205 | treeItem.setAttribute("data-item-type", "dir"); 206 | treeUl.appendChild(treeItem); 207 | } 208 | var dirItems = [], 209 | fileItems = []; 210 | response.data.forEach((item) => { 211 | item.type === "dir" ? dirItems.push(item) : fileItems.push(item); 212 | }); 213 | dirItems.concat(fileItems).forEach((item) => { 214 | var treeItem = document.createElement("tree-item"); 215 | treeItem.setAttribute("data-item-name", item.name); 216 | treeItem.setAttribute("data-item-path", item.path); 217 | treeItem.setAttribute("data-item-type", item.type); 218 | treeUl.appendChild(treeItem); 219 | }); 220 | var readMeExists = 221 | treeUl.querySelector('[data-item-name*="readme"]') || 222 | treeUl.querySelector('[data-item-name*="README"]'); 223 | if (readMeExists) { 224 | try { 225 | response = await octokit 226 | .request("GET /repos/{owner}/{repo}/readme/{dir}", { 227 | owner, 228 | repo, 229 | dir: path, 230 | headers: DEFAULT_API_HEADERS.headers, 231 | }) 232 | .catch((e) => {}); 233 | previewItemContentDiv.innerHTML = ""; 234 | previewDiv.hidden = false; 235 | previewItemNameDiv.innerHTML = response.data.name; 236 | // set btn-download-file 237 | btnDownloadFile.setAttribute( 238 | "onclick", 239 | `downloadFile('${response.data.name}', '${response.data.download_url}')` 240 | ); 241 | // set Raw url 242 | rawBtn.setAttribute("href", response.data.download_url); 243 | initMarkdownView(decodeContent(response.data.content)); 244 | } catch (err) { 245 | if (err.response.status === 404 || err.status === 404) { 246 | // no README.md, this is ok 247 | } else { 248 | throw err; 249 | } 250 | } 251 | } 252 | } else { 253 | /* path is a file */ 254 | treeUl.hidden = true; 255 | previewItemNameDiv.innerHTML = headerName; 256 | previewItemContentDiv.innerHTML = ""; 257 | var m = String(mime.getType(path)); // m.slice(0, m.indexOf('/')) 258 | if (m.startsWith("image/")) { 259 | // console.log(response); 260 | // img render 261 | previewItemContentDiv.innerHTML = ` 262 |
263 | 264 |
265 | `; 266 | } else if (m.endsWith("/pdf")) { 267 | // pdf render 268 | previewItemContentDiv.innerHTML = ` 269 | 283 | `; 284 | } else if (m.endsWith("/markdown")) { 285 | // md parse 286 | initMarkdownView(decodeContent(response.data.content)); 287 | } else { 288 | // normal text file 289 | var pre = document.createElement("pre"), 290 | code = document.createElement("code"), 291 | lang = headerName.split(".").at(-1).toLowerCase(), 292 | txt = decodeContent(response.data.content), 293 | txtJson = false; 294 | try { 295 | txtJson = JSON.parse(txt); 296 | } catch (err) {} 297 | if (lang === "ipynp" && txtJson) { 298 | var notebook = nb.parse(txtJson); 299 | previewItemContentDiv.appendChild(notebook.render()); 300 | Prism.highlightAll(); 301 | } else { 302 | if (!Prism.languages[lang]) { 303 | var _ = /\blang(?:uage)?-([\w-]+)\b/i.exec(txt); 304 | if (_) lang = _[1]; 305 | else if (txtJson) lang = "json"; 306 | else lang = "txt"; 307 | } 308 | // if (Prism.languages[lang]) { 309 | pre.className = "line-numbers language-" + lang; 310 | code.className = "language-" + lang; 311 | code.style.display = "inline-block"; 312 | var match = txt.match(NEW_LINE_EXP); 313 | var lineNumbersWrapper = ``; 316 | code.innerHTML = 317 | DOMPurify.sanitize( 318 | Prism.highlight(txt, Prism.languages[lang], lang) 319 | ) + lineNumbersWrapper; 320 | pre.appendChild(code); 321 | // } else { 322 | // el.textContent = txt; 323 | // } 324 | previewItemContentDiv.appendChild(pre); 325 | } 326 | } 327 | previewDiv.hidden = false; 328 | // set btn-download-file 329 | btnDownloadFile.setAttribute( 330 | "onclick", 331 | `downloadFile('${response.data.name}', '${response.data.download_url}')` 332 | ); 333 | // set Raw url 334 | rawBtn.setAttribute("href", response.data.download_url); 335 | } 336 | if (path.startsWith("/")) path = path.slice(1); 337 | window.path = path; 338 | // set Location bar 339 | locationDiv.innerHTML = ""; 340 | var pathSplit = path.split("/"); 341 | var onLocationClick = (evt) => { 342 | var el = evt.target; 343 | var parentChildren = el.parentNode.querySelectorAll("a"); 344 | var pathToGo = "", 345 | child, 346 | itemName; 347 | for (let i = 0; i <= parentChildren.length; i++) { 348 | child = parentChildren[i]; 349 | itemName = child.getAttribute("data-item-name"); 350 | if (itemName) { 351 | pathToGo += "/" + itemName; 352 | } 353 | if (child == el) break; 354 | } 355 | gotoPath(pathToGo); 356 | }; 357 | 358 | if (path) { 359 | var a = document.createElement("a"); 360 | a.style.cursor = "pointer"; 361 | a.onclick = onLocationClick; 362 | a.setAttribute("data-item-name", ""); 363 | } else { 364 | var a = document.createElement("span"); 365 | } 366 | a.innerHTML = window.repo; 367 | locationDiv.appendChild(a); 368 | if (path) { 369 | pathSplit.slice(0, -1).forEach((i) => { 370 | var slash = document.createElement("span"); 371 | slash.innerHTML = " / "; 372 | locationDiv.appendChild(slash); 373 | var a = document.createElement("a"); 374 | a.style.cursor = "pointer"; 375 | a.onclick = onLocationClick; 376 | a.setAttribute("data-item-name", i); 377 | a.innerHTML = i; 378 | locationDiv.appendChild(a); 379 | }); 380 | var slash = document.createElement("span"); 381 | slash.innerHTML = " / "; 382 | locationDiv.appendChild(slash); 383 | var span = document.createElement("span"); 384 | span.innerHTML = pathSplit.at(-1); 385 | locationDiv.appendChild(span); 386 | } 387 | locationDiv.hidden = false; 388 | }; 389 | 390 | class TreeItem extends HTMLElement { 391 | constructor() { 392 | super(); 393 | } 394 | connectedCallback() { 395 | this.innerHTML = ` 396 |
  • 397 | 398 | ${this.getAttribute("data-item-name")} 399 |
  • 400 | `; 401 | this.getElementsByTagName("a")[0].onclick = () => { 402 | gotoPath(this.getAttribute("data-item-path")); 403 | }; 404 | } 405 | } 406 | customElements.define("tree-item", TreeItem); 407 | 408 | function showErrorMsg(msg) { 409 | mainLoaderSpinnerDiv.hidden = true; 410 | mainLoaderSpinnerDiv.innerHTML = msg; 411 | mainLoaderSpinnerDiv.style.zoom = 2; 412 | mainLoaderSpinnerDiv.style.height = "fit-content"; 413 | mainLoaderSpinnerDiv.style.width = "fit-content"; 414 | mainLoaderSpinnerDiv.style.textAlign = "center"; 415 | mainLoaderSpinnerDiv.style.top = "40%"; 416 | mainLoaderSpinnerDiv.style.border = "solid 1px crimson"; 417 | mainLoaderSpinnerDiv.style.padding = "5px"; 418 | mainLoaderSpinnerDiv.style.borderRadius = "5px"; 419 | footerDiv.style.top = "30%"; 420 | mainLoaderSpinnerDiv.hidden = false; 421 | } 422 | /* ~: js encryption by MetaTron@stackoverflow :~ */ 423 | /* https://stackoverflow.com/a/66938952/10701585 */ 424 | const crypt = (salt, text) => { 425 | const textToChars = (text) => text.split("").map((c) => c.charCodeAt(0)); 426 | const byteHex = (n) => ("0" + Number(n).toString(16)).substr(-2); 427 | const applySaltToChar = (code) => 428 | textToChars(salt).reduce((a, b) => a ^ b, code); 429 | return text 430 | .split("") 431 | .map(textToChars) 432 | .map(applySaltToChar) 433 | .map(byteHex) 434 | .join(""); 435 | }; 436 | const decrypt = (salt, encoded) => { 437 | const textToChars = (text) => text.split("").map((c) => c.charCodeAt(0)); 438 | const applySaltToChar = (code) => 439 | textToChars(salt).reduce((a, b) => a ^ b, code); 440 | return encoded 441 | .match(/.{1,2}/g) 442 | .map((hex) => parseInt(hex, 16)) 443 | .map(applySaltToChar) 444 | .map((charCode) => String.fromCharCode(charCode)) 445 | .join(""); 446 | }; 447 | /* ------------------------------------------ */ 448 | function encryptToken(token) { 449 | return crypt("", token) + "_enc"; 450 | } 451 | function decryptToken(token) { 452 | return decrypt("", token.slice(0, token.lastIndexOf("_enc"))); 453 | } 454 | /* ------------------------------------------ */ 455 | function copyTextToClipboard(text) { 456 | // Create a temporary textarea element to hold the text 457 | const tempElement = document.createElement("textarea"); 458 | tempElement.value = text; 459 | tempElement.style.position = "fixed"; // Prevent scrolling to bottom of page in MS Edge. 460 | document.body.appendChild(tempElement); 461 | // Select the text inside the textarea 462 | tempElement.select(); 463 | // Copy the selected text to the clipboard 464 | let successful = false; 465 | try { 466 | successful = document.execCommand("copy"); 467 | } catch (err) { 468 | console.error("Failed to copy text: ", err); 469 | } 470 | // Remove the temporary textarea element 471 | document.body.removeChild(tempElement); 472 | return successful; 473 | } 474 | window.downloadFile = (filename, url) => { 475 | const fileStream = streamSaver.createWriteStream(filename); 476 | fetch(url).then((res) => { 477 | const readableStream = res.body; 478 | // more optimized 479 | if (window.WritableStream && readableStream.pipeTo) { 480 | return readableStream.pipeTo(fileStream); 481 | // .then(() => console.log('done writing')) 482 | } 483 | var writer = fileStream.getWriter(); 484 | const reader = res.body.getReader(); 485 | const pump = () => 486 | reader 487 | .read() 488 | .then((res) => 489 | res.done ? writer.close() : writer.write(res.value).then(pump) 490 | ); 491 | pump(); 492 | }); 493 | }; 494 | async function importModule(module) { 495 | /* try importing a module forever until done */ 496 | while (true) { 497 | try { 498 | return await import(module); 499 | } catch (err) { 500 | console.error(err); 501 | } 502 | } 503 | } 504 | async function mainLoop() { 505 | if (document.location.search === "?new") { 506 | // handleNewUrlCreation 507 | createUrlDiv.hidden = false; 508 | document.addEventListener("click", async (evt) => { 509 | var el = evt.target; 510 | switch (el.getAttribute("name")) { 511 | case "btn-inp-paste": 512 | evt.preventDefault(); 513 | try { 514 | var clipboardText = await navigator.clipboard.readText(); 515 | var inputElement = document.querySelector( 516 | el.getAttribute("target") 517 | ); 518 | inputElement.value = clipboardText; 519 | } catch (err) { 520 | console.error(err); 521 | } 522 | break; 523 | case "btn-copy-created-url": 524 | evt.preventDefault(); 525 | var txt = aCreatedUrl.getAttribute("href"); 526 | if (txt === "") break; 527 | var success = copyTextToClipboard(txt); 528 | var btnCopyCreatedUrlText = btnCopyCreatedUrl.innerText; 529 | if (success) btnCopyCreatedUrl.innerText = "Copied"; 530 | else btnCopyCreatedUrl.innerText = "Can't copy"; 531 | setTimeout(() => { 532 | btnCopyCreatedUrl.innerText = btnCopyCreatedUrlText; 533 | }, 1000); 534 | break; 535 | case "btn-create-url": 536 | evt.preventDefault(); 537 | var btnText = el.innerText; 538 | var url = 539 | document.location.origin + 540 | document.location.pathname + 541 | (document.location.pathname !== "/" ? "/" : "") + 542 | `?token=${encryptToken( 543 | inpToken.value.trim() 544 | )}&owner=${inpUsername.value.trim()}&repo=${inpRepo.value.trim()}`; 545 | url = url.replace("//?", "/?"); 546 | aCreatedUrl.setAttribute("href", url); 547 | aCreatedUrl.innerText = url; 548 | el.innerText = "Created"; 549 | setTimeout(() => { 550 | el.innerText = btnText; 551 | }, 1000); 552 | break; 553 | default: 554 | break; 555 | } 556 | }); 557 | return; 558 | } 559 | window.Octokit = (await importModule("https://cdn.skypack.dev/@octokit/core")).Octokit; 560 | window.Mime = ( 561 | await importModule("https://unpkg.com/mime@latest/dist/src/index_lite.js") 562 | ).Mime; 563 | window.stdMimeTypes = ( 564 | await importModule("https://unpkg.com/mime@latest/dist/types/standard.js") 565 | ).default; 566 | window.otherMimeTypes = ( 567 | await importModule("https://unpkg.com/mime@latest/dist/types/other.js") 568 | ).default; 569 | window.programmingTypes = ( 570 | await importModule("./programming-txt-mime.js") 571 | ).default; 572 | window.mime = new Mime(stdMimeTypes, otherMimeTypes, programmingTypes); 573 | marked.setOptions({ 574 | highlight: (code, lang) => { 575 | if (Prism.languages[lang]) { 576 | return Prism.highlight(code, Prism.languages[lang], lang); 577 | } else { 578 | return code; 579 | } 580 | }, 581 | pedantic: false, 582 | gfm: true, 583 | }); 584 | const urlParams = new URLSearchParams(document.location.search); 585 | window.repo = urlParams.get("repo"); 586 | if (!repo) { 587 | // no repo-name is supplied => panic 588 | showErrorMsg("no repo name is supplied in url params"); 589 | return; 590 | } 591 | window.token = urlParams.get("token"); 592 | if (!token) { 593 | // no token is supplied => panic 594 | showErrorMsg("no token is supplied in url params"); 595 | return; 596 | } 597 | if ( 598 | JSON.parse( 599 | urlParams.get("token_ready") || 600 | token.startsWith("github_") || 601 | token.startsWith("ghp_") 602 | ) 603 | ) { 604 | window.tokenUrlParam = `${token}&token_ready=true`; 605 | } else { 606 | window.tokenUrlParam = token; 607 | try { 608 | if (token.endsWith("_enc")) token = decryptToken(token); 609 | else token = atob(token); 610 | } catch { 611 | // token is not valid: panic 612 | showErrorMsg("can't decode Github access token"); 613 | return; 614 | } 615 | } 616 | window.octokit = new Octokit({ auth: token }); 617 | 618 | // wrap octokit.request with unsafe method to overcome internet disconnections 619 | var __octokit_request = octokit.request; 620 | octokit.request = async function () { 621 | while (true) { 622 | onlineLock.lock(); 623 | try { 624 | return await __octokit_request.apply(null, arguments); 625 | } catch (err) { 626 | // when net::ERR_INTERNET_DISCONNECTED error catched has .response undefined 627 | if (err.response !== undefined) throw err; 628 | // wait for 'online' event before retrying 629 | await onlineLock.wait(); 630 | } 631 | } 632 | }; 633 | 634 | // check token 635 | try { 636 | var tokenAuthenticatedUser = ( 637 | await octokit.request("GET /user", DEFAULT_API_HEADERS) 638 | ).data.login; 639 | } catch (err) { 640 | if (err.response.status === 401) { 641 | // token is not valid: panic 642 | showErrorMsg("Github access token is NOT valid"); 643 | return; 644 | } 645 | } 646 | 647 | window.path = urlParams.get("path"); 648 | path = path ? path : ""; 649 | 650 | window.owner = urlParams.get("owner"); 651 | // if no usernae is supplied, then: set is as the user who created that access_token 652 | owner = owner ? owner : tokenAuthenticatedUser; 653 | marked.use(extensionFixUrls()); 654 | 655 | document.title = `${owner} / ${repo} · ${document.title}`; 656 | (function addBtnClone() { 657 | const clone = document.createElement("a"); 658 | clone.className = "btn"; 659 | clone.textContent = "Clone"; 660 | 661 | clone.onclick = () => { 662 | treeHeaderLast.style.gridTemplateColumns = "1fr 1fr"; 663 | const input = document.createElement("input"); 664 | input.setAttribute("readonly", true); 665 | input.style.fontSize = "bold"; 666 | input.style.padding = "5px"; 667 | input.value = `git clone https://${window.token}@github.com/${window.owner}/${window.repo}`; 668 | clone.remove(); 669 | 670 | const copy = document.createElement("a"); 671 | copy.className = "btn"; 672 | copy.textContent = "Copy"; 673 | copy.onclick = () => { 674 | input.focus(); 675 | input.select(); 676 | document.execCommand("copy"); 677 | treeHeaderLast.innerHTML = ""; 678 | treeHeaderLast.style.gridTemplateColumns = "1fr"; 679 | treeHeaderLast.append(clone); 680 | var _ = clone.onclick; 681 | clone.onclick = undefined; 682 | clone.textContent = "Copied!"; 683 | clone.style.cursor = "default"; 684 | setTimeout(() => { 685 | clone.style.cursor = "pointer"; 686 | clone.onclick = _; 687 | clone.textContent = "Clone"; 688 | }, 1500); 689 | }; 690 | treeHeaderLast.innerHTML = ""; 691 | treeHeaderLast.append(input, copy); 692 | }; 693 | treeHeaderLast.append(clone); 694 | })(); 695 | btnDownloadDir.addEventListener("click", async (evt) => { 696 | evt.preventDefault(); 697 | // var repoUrl = `https://github.com/${owner}/repo`; 698 | // if (path !== '') 699 | // { 700 | // try { 701 | // var response = await octokit.request("GET /repos/{owner}/{repo}", { 702 | // owner, 703 | // repo, 704 | // headers: DEFAULT_API_HEADERS.headers, 705 | // }); 706 | // } catch (err) { 707 | // console.error("can't download repo"); 708 | // console.error(err); 709 | // return; 710 | // } 711 | // var defaultBranch = response.data.default_branch; 712 | // repoUrl += `/tree/${defaultBranch}/${path}`; 713 | // } 714 | window.open((document.location.pathname + '/download/').replaceAll('//', '/') + `?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}&path=${encodeURIComponent(path)}&token=${encodeURIComponent(token)}`); 715 | 716 | // var repoDownloadURL = `https://${token}@github.com/${owner}/${repo}/archive/refs/heads/${defaultBranch}.zip`; 717 | // var a = document.createElement("a"); 718 | // a.setAttribute("href", repoDownloadURL); 719 | // a.setAttribute("download", `${repo}-${defaultBranch}.zip`); 720 | // document.body.appendChild(a); 721 | // a.click(); 722 | // document.body.removeChild(a); 723 | }); 724 | 725 | // get required path 726 | await gotoPath(path, false, false); 727 | } 728 | 729 | window.addEventListener("popstate", function (event) { 730 | var previousPath = new URLSearchParams(document.location.search).get("path"); 731 | gotoPath(previousPath ? previousPath : "", false); 732 | }); 733 | 734 | mainLoop(); 735 | -------------------------------------------------------------------------------- /asset/paste.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /asset/no-wifi.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------