Skip to content

Commit 8db398e

Browse files
committed
Merge with upstream nbviwer.js
Merge commit '3d0623678923e98594ad2d6e443372e71efd6e6d'
2 parents d713af9 + 3d06236 commit 8db398e

File tree

4 files changed

+257
-22
lines changed

4 files changed

+257
-22
lines changed

nbviewer.js/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
### client side rendering of jupyter notebooks
22

3-
*tl;dr: Render Jupyter notebooks straight in the browser, without a back end converter. Can be used as a library.*
3+
*tl;dr: Render Jupyter notebooks straight in the browser, without a back end converter. Can be used as a library. Or if you're on macOS, you can even fire it up in Quick Look, see [ipynb-quicklook](https://github.com/tuxu/ipynb-quicklook).*
44

55
I often want to read through my Jupyter notebooks, but I rarely have my Jupyter instances running in the right folders. I can't quite use the [online nbviewer](http://nbviewer.jupyter.org/), because I don't have a public URL for these, so I resort to running dummy Jupyter instances or uploading my file as a one-time gist on Github (one I have to delete thereafter). One last possibility is `nbconvert` in the command line.
66

@@ -16,7 +16,7 @@ Or you can use [the demo](https://kokes.github.io/nbviewer.js/viewer.html) (or a
1616

1717
Two immediate use cases come to mind:
1818

19-
1. In Mac OS, you can preview files and this system, [Quick Look](https://support.apple.com/kb/PH21920?locale=en_US), supports plugins. It would be rather handy to have a notebook preview one keystroke away.
19+
1. ~~In Mac OS, you can preview files and this system, [Quick Look](https://support.apple.com/kb/PH21920?locale=en_US), supports plugins. It would be rather handy to have a notebook preview one keystroke away.~~ This has been done, see [ipynb-quicklook](https://github.com/tuxu/ipynb-quicklook).
2020
2. Ever since GitHub introduced notebook rendering last year, Gitlab users have been [requesting the same](https://gitlab.com/gitlab-org/gitlab-ce/issues/2508). Gitlab itself recently dropped Python as a dependency, so its reintroduction just for converting notebooks is rather unlikely. A browser-based solution like this could be a good substitute.
2121

2222
### Tech details

nbviewer.js/github-browser.html

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset='utf-8' />
5+
<title>nbviewer.js</title>
6+
7+
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/prism/1.5.1/themes/prism.min.css'/>
8+
<script src='https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.6/marked.min.js'></script>
9+
<script src='https://cdnjs.cloudflare.com/ajax/libs/prism/1.5.1/prism.min.js' data-manual></script>
10+
<script src='https://cdnjs.cloudflare.com/ajax/libs/prism/1.5.1/components/prism-python.min.js' data-manual></script>
11+
12+
<style type='text/css'>
13+
body {
14+
font: 0.8em Arial, sans-serif;
15+
background-color: #eee;
16+
}
17+
div#instructions {
18+
max-width: 960px;
19+
font-size: 2em;
20+
color: #aaa;
21+
text-align: center;
22+
/*padding-top: 5%;*/
23+
margin: 0 auto;
24+
}
25+
p#examples {
26+
font-size: .5em;
27+
}
28+
div#serving-info {
29+
font-size: .7em;
30+
font-style: italic;
31+
margin-top: 1em;
32+
}
33+
div#bugs {
34+
font-size: .6em;
35+
}
36+
input#ghproj {
37+
display: block;
38+
font-size: 1em;
39+
text-align: center;
40+
margin: 0 auto;
41+
width: 50%;
42+
}
43+
div#filelist {
44+
margin: 0 auto;
45+
width: 50%;
46+
text-align: left;
47+
font-size: .8em;
48+
}
49+
div#filelist ul {
50+
list-style-type: none;
51+
}
52+
div#filelist ul li {
53+
vertical-align: center;
54+
}
55+
div#filelist ul li a {
56+
text-decoration: none;
57+
}
58+
</style>
59+
60+
<!-- nbviewer.js -->
61+
<script src='lib/nbv.js'></script>
62+
</head>
63+
<body>
64+
<a href="https://github.com/kokes/nbviewer.js"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/a6677b08c955af8400f44c6298f40e7d19cc5b2d/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f677261795f3664366436642e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png"></a>
65+
66+
<div id='doc'>
67+
<div id='instructions'>
68+
<p>Enter a <code>username/repo</code> combination from Github</p>
69+
<p id='examples'>
70+
<a href='#aHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9yc3ZwL2ZlY29uMjM1L2NvbnRlbnRzL25iL2ZyZWQtZW1wbG95LW5mcC5pcHluYj9yZWY9bWFzdGVy'>Example 1</a>
71+
<a href='#aHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hcm9rZW0vdGVhY2hfb3B0aW1pemF0aW9uL2NvbnRlbnRzL29wdGltaXphdGlvbi5pcHluYj9yZWY9bWFzdGVy'>Example 2</a>
72+
<form>
73+
<input id='ghproj' type='text' placeholder='username/repo' />
74+
<input id='ghlookup' type='submit' value='Browse repo' />
75+
</form>
76+
77+
<div id='serving-info'></div>
78+
<div id='bugs'>(We know there's an encoding issue making non-latin characters render incorrectly.)</div>
79+
80+
<div id='filelist'>
81+
82+
</div>
83+
</div>
84+
<div id='notebook'>
85+
86+
</div>
87+
88+
</div>
89+
90+
91+
<script type='text/javascript'>
92+
var d = document;
93+
var cache = {};
94+
var gg; // debugging
95+
96+
window.onload = function() {
97+
var lk = d.getElementById('ghlookup');
98+
var ghproj = d.getElementById('ghproj');
99+
100+
// block submitting again
101+
lk.addEventListener('click', function(e) {
102+
e.target.disabled = 'disabled';
103+
// TODO: validate inputs
104+
// should be something like [a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-], not sure what exactly
105+
var turl = get_gh_url('repos/' + ghproj.value + '/contents/'); // living on the edgeeee
106+
window.location.hash = btoa(turl);
107+
});
108+
// re-enable the submit button when changed
109+
ghproj.addEventListener('keyup', function(e) {
110+
lk.disabled = '';
111+
});
112+
hash_changed();
113+
};
114+
115+
window.onhashchange = hash_changed;
116+
117+
function hash_changed() {
118+
render_gh_files();
119+
}
120+
121+
function render_gh_files() {
122+
wipeElement(d.getElementById('serving-info')); // clear
123+
var hs = window.location.hash.slice(1); // without '#'
124+
var hsurl = atob(hs); // base64 -> text
125+
ajax_get_json(hsurl, function(dt) {
126+
gg = dt;
127+
// get pathname
128+
var a = d.createElement('a');
129+
a.href = hsurl;
130+
var path = a.pathname;
131+
132+
if (path.endsWith('ipynb')) {
133+
var nb = JSON.parse(atob(dt.content)); // decode incoming data
134+
var tg = d.getElementById('notebook');
135+
nbv.render(nb, tg);
136+
137+
// serving info
138+
var turl = window.location;
139+
d.getElementById('serving-info').innerHTML = `Serving <code>${dt.name}</code>. <a href='${turl}'>Link to this render</a> / <a href='${dt.html_url}'>original Github source</a>`;
140+
return;
141+
}
142+
143+
var fl = d.getElementById('filelist');
144+
var ul = d.createElement('ul');
145+
wipeElement(fl);
146+
for (var j=0; j<dt.length; j++) {
147+
var el = dt[j];
148+
var li = d.createElement('li');
149+
li.appendChild(function() {
150+
if (el.type === 'file' && !el.name.endsWith('.ipynb')) {
151+
return d.createTextNode(el.name);
152+
}
153+
154+
var a = d.createElement('a');
155+
a.textContent = el.name;
156+
a.setAttribute('href', '#' + btoa(el.url));
157+
return a;
158+
}());
159+
160+
ul.appendChild(li);
161+
}
162+
fl.appendChild(ul);
163+
164+
});
165+
}
166+
167+
function get_gh_url(path) {
168+
return 'https://api.github.com/' + path;
169+
}
170+
171+
function ajax_get_json(url, callback)
172+
{
173+
console.log(url);
174+
if (cache[url] !== undefined) {
175+
console.log('serving from cache');
176+
return callback(cache[url]);
177+
}
178+
179+
var rr = new XMLHttpRequest();
180+
rr.onreadystatechange = function() {
181+
if (rr.readyState == 4 && rr.status == 200) {
182+
cache[url] = JSON.parse(rr.responseText)
183+
callback(JSON.parse(rr.responseText));
184+
} else if (rr.readyState == 4) {
185+
throw Error(err);
186+
}
187+
}
188+
rr.open('GET', url);
189+
rr.send(null);
190+
}
191+
192+
function wipeElement(el) {
193+
while (el.firstChild) {
194+
el.removeChild(el.firstChild);
195+
}
196+
}
197+
198+
</script>
199+
200+
<script>
201+
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
202+
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
203+
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
204+
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
205+
206+
ga('create', 'UA-98368126-1', 'auto');
207+
ga('send', 'pageview');
208+
209+
</script>
210+
211+
</body>
212+
</html>
213+

nbviewer.js/lib/nbv.js

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ var nbv = (function() {
107107
'margin-bottom: 0',
108108
'margin-top: 0',
109109
'border-radius: 2px',
110-
'overflow-x: scroll',
111110
'min-height: .85em'
112111
].join(';'));
113112
var code = d.createElement('code');
@@ -141,6 +140,10 @@ var nbv = (function() {
141140
case 'stream':
142141
dm.appendChild(handle_stream_output(dt));
143142
break;
143+
case 'pyerr': // v3
144+
case 'error': // v4
145+
dm.appendChild(handle_error_cell(dt));
146+
break;
144147
case 'display_data':
145148
if (st.nbformat > 3) {
146149
dm.appendChild(handle_cell_output(dt));
@@ -184,7 +187,6 @@ var nbv = (function() {
184187
break;
185188

186189
case 'text/html':
187-
dm.style.overflow = 'scroll';
188190
dm.innerHTML = dt.data[fmt].join('');
189191

190192
// we may have generated some HTML tables we need to style
@@ -203,13 +205,17 @@ var nbv = (function() {
203205
}
204206

205207
}
206-
208+
break;
209+
210+
case 'image/svg+xml':
211+
dm.innerHTML = dt.data[fmt].join('');
207212
break;
208213

209214
default:
210215
if (fmt.startsWith('image/')) {
211216
dm = d.createElement('img');
212-
dm.setAttribute('src', 'data:' + fmt + ';base64,' + dt.data[fmt]);
217+
dm.setAttribute('src', 'data:' + fmt + ';base64,' +
218+
((typeof dt.data[fmt]) == 'string' ? dt.data[fmt] : dt.data[fmt].join('')));
213219
break;
214220
}
215221
console.error('unexpected format: ' + fmt);
@@ -221,15 +227,29 @@ var nbv = (function() {
221227
return el;
222228
}
223229

230+
function handle_error_cell(dt) {
231+
var cn = d.createElement('pre');
232+
var txt = dt.traceback.join('\n');
233+
// stripping ANSI colours for now https://github.com/chalk/ansi-regex/blob/master/index.js#L3
234+
// could use a full library at some point https://github.com/drudru/ansi_up
235+
txt = txt.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
236+
cn.textContent = txt;
237+
238+
return cn;
239+
}
240+
224241
function handle_stream_output(dt) {
225242
// name in v4, stream in v3
226-
if ( (dt.name || dt.stream) != 'stdout' || dt.output_type != 'stream')
227-
console.error('unexpected stream spec');
243+
var outt = dt.name || dt.stream; // v4 || v3; contains 'stdout' or 'stderr'
228244

229245
if (!dt.hasOwnProperty('text'))
230246
console.error('data for stream missing');
231247

232248
var cn = d.createElement('pre');
249+
// stderr red background, stdout is plain white
250+
if (outt === 'stderr') {
251+
cn.setAttribute('style', 'background-color: #fdd; padding: .5em');
252+
}
233253
cn.textContent = dt.text.join('');
234254

235255
return cn;
@@ -287,18 +307,6 @@ var nbv = (function() {
287307
return el.toLowerCase().replace(/\W+/g, '-');
288308
}
289309

290-
// function render_toc() {
291-
// var els = st.target.querySelector('h1,h2,h3,h4,h5,h6')
292-
// var minl = 6
293-
// for (var j=0; j<els.length;j++) {
294-
// minl = (els[j].nodeName[1] < minl) ?
295-
// parseInt(els[j].nodeName[1]) : minl
296-
// }
297-
// console.log(minl)
298-
299-
// }
300-
301-
302310
return {
303311
render: render_ipynb
304312
};

nbviewer.js/viewer.html

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
padding-top: 10%;
2323
margin: 0 auto;
2424
}
25+
div#instructions small {
26+
font-size: .7em;
27+
}
2528
div#dropzone {
2629
position: fixed;
2730
top: 0; left: 0;
@@ -36,7 +39,6 @@
3639
/*display: block;*/
3740
font-size: 2em;
3841
margin: 0 auto;
39-
z
4042
}
4143
</style>
4244

@@ -48,7 +50,8 @@
4850

4951
<div id='doc'>
5052
<select id='file-selector'></select>
51-
<div id='instructions'>Drag and drop Jupyter notebooks anywhere here</div>
53+
<div id='instructions'>Drag and drop Jupyter notebooks anywhere here<br />
54+
<small>(if you wish to render directly from Github, <a href='github-browser.html'>try our new tool</a>)</small></div>
5255

5356
</div>
5457

@@ -161,6 +164,17 @@
161164

162165
</script>
163166

167+
<script>
168+
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
169+
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
170+
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
171+
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
172+
173+
ga('create', 'UA-98368126-1', 'auto');
174+
ga('send', 'pageview');
175+
176+
</script>
177+
164178
</body>
165179
</html>
166180

0 commit comments

Comments
 (0)