-
-
Notifications
You must be signed in to change notification settings - Fork 416
/
Copy pathmain.js
1366 lines (1202 loc) · 50.5 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* -*- coding: utf-8; js-indent-level: 2 -*-
* ----------------------------------------------------------------------------
* Copyright (c) 2013-2017 Damián Avila and contributors.
*
* Distributed under the terms of the Modified BSD License.
*
* A Jupyter notebook extension to support *Live* Reveal.js-based slideshows.
* -----------------------------------------------------------------------------
*/
define([
'require',
'jquery',
'base/js/namespace',
'base/js/utils',
'services/config',
], function(require, $, Jupyter, utils, configmod) {
"use strict";
/*
* load configuration
*
* 1) start from the hardwired settings (in this file)
* 2) add settings configured in python; these typically can be
* 2a) either in a legacy file named `livereveal.json`
* 2b) or, with the official name, in `rise.json`
* 3) add the settings from nbext_configurator (i.e. .jupyter/nbconfig/notebook.json)
* they should all belong in the 'rise' category
* the configurator came after the shift from 'livereveal' to 'rise'
* so no need to consider 'livereveal' here
* 4) and finally add the settings from the notebook metadata
* 4a) for legacy reasons: use the 'livereveal' key
* 4b) for more consistency, then override with the 'rise' key
*
* configLoaded alters the complete_config object in place.
* it will hold a consolidated set of all relevant settings with their priorities resolved
*
* it returns a promise that can be then'ed once the config is loaded
*
* setup waits for the config to be loaded before it actually enables keyboard shortcuts
* and other menu items; so this means that the bulk of the code can assume that the config
* is already loaded and does not need to worry about using promises, or
* waiting for any asynchronous code to complete
*/
let complete_config = {};
// returns a promise; you can do 'then()' on this promise
// to do stuff *after* the configuration is completely loaded
function configLoaded() {
// see rise.yaml for more details
let hardwired_config = {
// behaviour
autolaunch: false,
start_slideshow_at: 'selected',
auto_select: 'code',
auto_select_fragment: true,
show_buttons_on_startup: true,
// aspect
header: undefined,
footer: undefined,
backimage: undefined,
overlay: undefined,
// timeouts
// wait for that amont before calling ensure_focused on the
// selected cell
restore_timeout: 500,
// wait for that amount before actually selected auto-selected fragment
// when going too short, like 250, size of selected cell get odd
auto_select_timeout: 450,
// wait for that amount before calling sync() again
// this is a workaround that fixes #504
sync_timeout: 250,
// UI
toolbar_icon: 'fa-bar-chart',
shortcuts: {
'slideshow' : 'alt-r',
'toggle-slide': 'shift-i',
'toggle-subslide': 'shift-b',
'toggle-fragment': 'shift-g',
// this can be helpful
'rise-nbconfigurator': 'shift-c',
// unassigned by default
'toggle-notes': '',
'toggle-skip': '',
},
// reveal native settings passed as-is
// see also the 'inherited' variable below in Revealer
theme: 'simple',
transition: 'linear',
// xxx there might be a need to tweak this one when set
// by the configurator, as e.g. 'false' or 'true' will result
// in a string and not a boolean
slideNumber: true,
width: "100%",
height: "100%",
controls: true,
progress: true,
history: true,
scroll: false,
center: true,
margin: 0.1,
minScale: 1.0, // we need this for codemirror to work right
// turn off reveal's help overlay that is by default bound to question mark / ?
help: false,
// plugins
enable_chalkboard: false,
enable_leap_motion: false,
};
// honour the 2 names: 'livereveal' and 'rise'
// use the ones in livereveal/legacy first
// so they get overridden if redefined in rise
let config_section_legacy = new configmod.ConfigSection(
'livereveal',
{base_url: utils.get_body_data("baseUrl")});
// trigger an asynchronous load
config_section_legacy.load();
let config_section = new configmod.ConfigSection(
'rise',
{base_url: utils.get_body_data("baseUrl")});
config_section.load();
// this is also a ConfigSection object as per notebook/static/services/config.js
let nbext_configurator = Jupyter.notebook.config;
nbext_configurator.load();
// with Promise.all we can wait for all 3 configs to have loaded
return Promise.all([
config_section_legacy.loaded,
config_section.loaded,
nbext_configurator.loaded,
]).then(
// and now we can compute the layered config
function() {
// 1) initialize with hardwired defaults
$.extend(true, complete_config, hardwired_config);
// 2a) and 2b)
$.extend(true, complete_config, config_section_legacy.data);
$.extend(true, complete_config, config_section.data);
// 3)
$.extend(true, complete_config, nbext_configurator.data.rise);
// 4a) from the notebook metadata
let metadata_legacy = Jupyter.notebook.metadata.livereveal;
$.extend(true, complete_config, metadata_legacy);
// 4b) ditto
let metadata = Jupyter.notebook.metadata.rise;
$.extend(true, complete_config, metadata);
// console.log("complete_config is OK");
});
}
/*
* this function is a heuristic that says if this notebook seems to
* be meant to be a slideshow.
* this primarily is for autolaunch, so that somebody who would
* enable autolaunch in her ~/.jupyter/ area would not
* see RISE trigger on every single notebook
*
* xxx note that this might take too long on large notebooks
* a possible improvement would be to look for the first, say, 10 cells only
* as a matter of fact, in most cases the first cell would be a slide cell really
*/
function is_slideshow(notebook) {
for (let cell of notebook.get_cells())
if (is_slide(cell) || is_subslide(cell))
return true;
return false;
}
/*
* Version of get_cell_elements that will see cell divs at any depth in the HTML tree,
* allowing container divs, etc to be used without breaking notebook machinery.
* You'll need to make sure the cells are getting detected in the right order.
* NOTE: We use the Object prototype to workaround a firefox issue, check the following
* link to know more about the discussion leading to this use:
* https://github.com/damianavila/RISE/issues/117#issuecomment-127331816
*/
Object.getPrototypeOf(Jupyter.notebook).get_cell_elements = function () {
return this.container.find("div.cell");
};
/* uniform way to access slide type, whether the slideshow metadata is set or not
* also sometimes slide_type is set to '-' by the toolbar
*/
function get_slide_type(cell) {
let slide_type = (cell.metadata.slideshow || {}).slide_type;
return ( (slide_type === undefined) || (slide_type == '-')) ? '' : slide_type;
}
function is_slide(cell) {return get_slide_type(cell) == 'slide';}
function is_subslide(cell) {return get_slide_type(cell) == 'subslide';}
function is_fragment(cell) {return get_slide_type(cell) == 'fragment';}
function is_skip(cell) {return get_slide_type(cell) == 'skip';}
function is_notes(cell) {return get_slide_type(cell) == 'notes';}
function is_regular(cell) {return get_slide_type(cell) == '';}
/* Use the slideshow metadata to rearrange cell DOM elements into the
* structure expected by reveal.js
*
* in the process, each cell receives a 'smart_exec' tag that says
* how to behave when the cell gets executed with Shift-Enter
* this tag can be either
* 'smart_exec_slide' : just do exec, which is what RISE did on all cells at first
this is for the last cell on a (sub)slide
i.e. if next cell is slide or subslide
* 'smart_exec_fragment' : do exec + show next fragment
if next cell is a fragment
* 'smart_exec_next' : do the usual exec + select next like in classic notebook
*/
function markupSlides(container) {
// Machinery to create slide/subslide <section>s and give them IDs
let slide_counter = -1, subslide_counter = -1;
let slide_section, subslide_section;
function new_slide() {
slide_counter++;
subslide_counter = -1;
return $('<section>').appendTo(container);
}
function new_subslide() {
subslide_counter++;
return $('<section>').attr('id', 'slide-'+slide_counter+'-'+subslide_counter)
.appendTo(slide_section);
}
// Containers for the first slide.
slide_section = new_slide();
subslide_section = new_subslide();
let current_fragment = subslide_section;
let selected_cell_idx = Jupyter.notebook.get_selected_index();
let selected_cell_slide = [0, 0];
/* Special handling for the first slide: it will work even if the user
* doesn't start with a 'Slide' cell. But if the user does explicitly
* start with slide/subslide, we don't want a blank first slide. So we
* don't create a new slide/subslide until there is visible content on
* the first slide.
*/
let content_on_slide1 = false;
let cells = Jupyter.notebook.get_cells();
for (let i=0; i < cells.length; i++) {
let cell = cells[i];
let slide_type = get_slide_type(cell);
if (content_on_slide1) {
if (slide_type === 'slide') {
// Start new slide
slide_section = new_slide();
// In each subslide, we insert cells directly into the
// <section> until we reach a fragment, when we create a div.
current_fragment = subslide_section = new_subslide();
} else if (slide_type === 'subslide') {
// Start new subslide
current_fragment = subslide_section = new_subslide();
} else if (slide_type === 'fragment') {
// record the <div class='fragment'> element corresponding
// to each fragment cell in the 'fragment_div' attribute
cell.fragment_div = current_fragment = $('<div>').addClass('fragment')
.appendTo(subslide_section);
}
} else if (slide_type !== 'notes' && slide_type !== 'skip') {
// Subsequent cells should be able to start new slides
content_on_slide1 = true;
}
// Record that this slide contains the selected cell
// this is where we need i as set in the loop over cells
if (i === selected_cell_idx) {
selected_cell_slide = [slide_counter, subslide_counter];
}
// Move the cell element into the slide <section>
// N.B. jQuery append takes the element out of the DOM where it was
if (slide_type === 'notes') {
// Notes are wrapped in an <aside> element
subslide_section.append(
$('<aside>').addClass('notes').append(cell.element)
);
} else {
current_fragment.append(cell.element);
}
// Hide skipped cells
if (slide_type === 'skip') {
cell.element.addClass('reveal-skip');
}
}
/* set on all cells a smart_exec tag that says how smart exec
* should behave on that cell
* the fragment cell also get a smart_exec_next_fragment
* attribute that points at the <div class='fragment'>
* corresponding to the (usually immediately) next cell
* that is a fragment cell
*/
for (let i=0; i < cells.length; i++) {
let cell = cells[i];
// default is 'pinned' because this applies to the last cell
let tag = 'smart_exec_slide';
for (let j = i+1; j < cells.length; j++) {
let next_cell = cells[j];
let next_type = get_slide_type(next_cell);
if ((next_type == 'slide') || (next_type) == 'subslide') {
tag = 'smart_exec_slide';
break;
} else if (next_type == 'fragment') {
tag = 'smart_exec_fragment';
/* these cells are the last before a fragment
* and when running smart-exec we'll want to know
* if that fragment is visible, so we keep a link to
* the <div class='fragment'> element of that (next)
* fragment cell
*/
cell.smart_exec_next_fragment = next_cell.fragment_div;
break;
} else if (next_type == '') {
tag = 'smart_exec_next';
break;
}
}
cell.smart_exec = tag;
}
return selected_cell_slide;
}
/* Set the #slide-x-y part of the URL to control where the slideshow will start.
* N.B. We do this instead of using Reveal.slide() after reveal initialises,
* because that leaves one slide clearly visible on screen for a moment before
* changing to the one we want. By changing the URL before setting up reveal,
* the slideshow really starts on the desired slide.
*/
function setStartingSlide(selected) {
let start_slideshow = complete_config.start_slideshow_at;
if (start_slideshow === 'selected') {
// Start from the selected cell
Reveal.slide(selected[0], selected[1]);
} else {
// Start from the beginning
Reveal.slide(0, 0);
}
setScrollingSlide();
// warkaround for #504
// when editing if you swap out of reveal, and then
// come back in, with 5.6 most of the time display
// becomes empty or the contents is way too low
// this patch makes the situation much better,
// although it is clearly suboptimal to have
// to resort to that sort of dirty patch
setTimeout(()=>Reveal.sync(), complete_config.sync_timeout);
}
/* Setup the scrolling in the current slide if the config option is activated
* and the content is greater than 0.95 * slide height
*/
function setScrollingSlide() {
let scroll = complete_config.scroll;
if (scroll === true) {
let h = $('.reveal').height() * 0.95;
$('section.present').find('section')
.filter(function() {
return $(this).height() > h;
})
.css('height', 'calc(95vh)')
.css('overflow-y', 'scroll')
.css('margin-top', '20px');
}
}
/*
* Setup the auto-launch function, which checks metadata to see if
* RISE should launch automatically when the notebook is opened.
*
* this will trigger only on notebooks that have
* either a 'livereveal' or a 'rise' section in their metadata
* this is because autolaunch can be enabled in nbextensions_configurator
* and so can possibly have a too big impact if we are not careful
*/
function autoLaunch() {
if (complete_config.autolaunch && is_slideshow(Jupyter.notebook)) {
revealMode();
}
// Ref: https://stackoverflow.com/a/7739035
let url = (window.location != window.parent.location)
? document.referrer
: document.location.href;
let lastPart = url.substr(url.lastIndexOf('/') + 1);
if (lastPart === "notes.html") {
revealMode();
}
}
/* Setup a MutationObserver to call Reveal.sync when an output is generated.
* This fixes issue #188: https://github.com/damianavila/RISE/issues/188
*/
let outputObserver = null;
function setupOutputObserver() {
function mutationHandler(mutationRecords) {
mutationRecords.forEach(function(mutation) {
if (mutation.addedNodes && mutation.addedNodes.length) {
Reveal.sync();
setScrollingSlide();
}
});
}
let $output = $(".output");
let MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
outputObserver = new MutationObserver(mutationHandler);
let observerOptions = { childList: true,
characterData: false,
attributes: false,
subtree: false
};
$output.each(function () {
outputObserver.observe(this, observerOptions);
});
}
function disconnectOutputObserver() {
if (outputObserver !== null) {
outputObserver.disconnect();
}
}
function addHeaderFooterOverlay() {
let overlay = complete_config.overlay;
let header = complete_config.header;
let footer = complete_config.footer;
let backimage = complete_config.backimage;
// minimum styling to make these 3 things look
// like what their name says they should look
let header_style = "position: absolute; top: 0px;";
let footer_style = "position: absolute; bottom: 0px;";
let backimage_style = "width: 100%; height: 100%;";
let overlay_body = "";
if (overlay) {
overlay_body = overlay;
} else {
if (header)
overlay_body += `<div id='rise-header' style='${header_style}'>${header}</div>`;
if (backimage)
overlay_body += `<img id='rise-backimage' style='${backimage_style}' src='${backimage}' />`;
if (footer)
overlay_body += `<div id='rise-footer' style='${footer_style}'>${footer}</div>`;
}
let overlay_div = `<div id='rise-overlay'>${overlay_body}</div>`;
$('div.reveal').append(overlay_div);
}
function removeHeaderFooterOverlay() {
// it's easier to remove than to hide, plus this way
// changes in the metadata will be reflected each time
// we enter reveal again
$('div#rise-overlay').remove();
}
function toggleAllRiseButtons() {
$('#help_b,#exit_b,#toggle-chalkboard,#toggle-notes').fadeToggle()
}
function Revealer(selected_slide) {
// console.log(`complete_config: ${JSON.stringify(complete_config)}`);
$('body').addClass("rise-enabled");
// Prepare the DOM to start the slideshow
$('div#header').hide();
$('.end_space').hide();
// Add the main reveal.js classes
$('div#notebook').addClass("reveal");
$('div#notebook-container').addClass("slides");
// Header
// Available themes are in static/css/theme
let theme = complete_config.theme;
$('body').addClass(`theme-${theme}`);
let theme_path = `./reveal.js/css/theme/${theme}.css`;
$('head').prepend(
`<link rel="stylesheet" href="${require.toUrl(theme_path)}" id="theme" />`);
// Add reveal css
let main_path = "./reveal.js/css/reveal.css";
$('head').prepend(
`<link rel="stylesheet" href="${require.toUrl(main_path)}" id="revealcss" />`);
/* this policy of trying ./rise.css and then <notebook>.css
* should be redefinable in the config
*/
// https://github.com/damianavila/RISE/issues/509
let name = Jupyter.notebook.notebook_name;
// remove extension if any
let dot_index = name.lastIndexOf('.');
let stem = (dot_index == -1) ? name : name.substr(0, dot_index);
// associated css
let name_css = `${stem}.css`;
// Attempt to load rise.css
$('head').append(
`<link rel="stylesheet" href="rise.css" id="rise-custom-css" />`);
// Attempt to load css with the same path as notebook
$('head').append(
`<link rel="stylesheet" href="${name_css}" id="rise-notebook-css" />`);
// Tailer
require([
// no longer current
// https://github.com/hakimel/reveal.js/commit/29b0e86089eb3ec0d4bb5811c9b723dfcf36703c
// './reveal.js/lib/js/head.min.js',
'./reveal.js/js/reveal.js'
].map(require.toUrl),
function() {
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
// all these settings are passed along to reveal as-is
// xxx it might be just better to copy the whole complete_config instead
// of selecting some names, which would allow users to transparently use
// all reveal's features
let inherited = ['controls', 'progress', 'history', 'width', 'height', 'margin',
'minScale', 'transition', 'slideNumber', 'center', 'help'];
let options = {
//parallaxBackgroundImage: 'https://raw.github.com/damianavila/par_IPy_slides_example/gh-pages/figs/star_wars_stormtroopers_darth_vader.jpg',
//parallaxBackgroundSize: '2560px 1600px',
// turn off reveal native help
help: false,
// key bindings configurable are now defined in the reveal_default_bindings dict -
// this should only be used to unbind keys
// note that toggleAllRiseButtons is bound to comma here as jupyter does not
// allow to bind anything to comma!
keyboard: {
13: null, // Enter disabled
27: null, // ESC disabled
35: null, // End - last slide disabled (will be set in custom keys)
36: null, // Home - first slide disabled (will be set in custom keys)
38: null, // up arrow disabled
40: null, // down arrow disabled
66: null, // b, black pause disabled, use period or forward slash
70: null, // disable fullscreen inside the slideshow, makes codemirror unreliable
72: null, // h, left disabled
74: null, // j, down disabled
75: null, // k, up disabled
76: null, // l, right disabled
78: null, // n, down disabled
79: null, // o disabled
80: null, // p, up disabled
84: null, // t, modified in the custom notes plugin.
87: null, // w, toggle overview
188: toggleAllRiseButtons, // comma
},
dependencies: [
// Optional libraries used to extend on reveal.js
/* { src: "static/custom/livereveal/reveal.js/lib/js/classList.js",
* condition: function() { return !document.body.classList; } },
* { src: "static/custom/livereveal/reveal.js/plugin/highlight/highlight.js",
* async: true,
* callback: function() { hljs.initHighlightingOnLoad(); } },
*/
{ src: require.toUrl("./reveal.js/plugin/notes/notes.js"),
async: true,
},
],
};
for (let setting of inherited) {
options[setting] = complete_config[setting];
}
////////// set up the leap motion integration if configured
let enable_leap_motion = complete_config.enable_leap_motion;
if (enable_leap_motion) {
options.dependencies.push({ src: require.toUrl('./reveal.js/plugin/leap/leap.js'),
async: true });
options.leap = enable_leap_motion;
}
//$.extend(options.keyboard, reveal_bindings);
////////// set up chalkboard if configured
let enable_chalkboard = complete_config.enable_chalkboard;
if (enable_chalkboard) {
if ("chalkboard" in complete_config) {
options["chalkboard"] = complete_config["chalkboard"];
}
options.dependencies.push({ src: require.toUrl('./reveal.js-chalkboard/chalkboard.js'),
async: true });
// xxx need to explore the option of registering jupyter actions
// and have jupyter handle the keyboard entirely instead of this approach
// could hopefully avoid conflicting behaviours in case of overlaps
// comment from thecker:
// this is not implemented - reveal.js & chalkboard bindings are now defined in
// nbconfing and setupKeys + registerJupyterActions is used to set the bindings
//$.extend(options.keyboard, cb_bindings);
}
if (Reveal.initialized) {
//delete options["dependencies"];
Reveal.configure(options);
//console.log("Reveal is already initialized and is being configured");
} else {
Reveal.initialize(options);
//console.log("Reveal initialized");
Reveal.initialized = true;
}
Reveal.addEventListener('ready', function(event) {
Unselecter();
// check and set the scrolling slide when you start the whole thing
setScrollingSlide();
autoSelectHook();
});
Reveal.addEventListener('slidechanged', function(event) {
Unselecter();
// check and set the scrolling slide every time the slide change
setScrollingSlide();
autoSelectHook();
});
Reveal.addEventListener('fragmentshown', function(event) {
autoSelectHook();
});
Reveal.addEventListener('fragmenthidden', function(event) {
autoSelectHook();
});
// Sync when an output is generated.
setupOutputObserver();
// Setup the starting slide
setStartingSlide(selected_slide);
addHeaderFooterOverlay();
if (! complete_config.show_buttons_on_startup) {
/* safer, and nicer too, to wait for reveal extensions to start */
setTimeout(toggleAllRiseButtons, 2000);
}
});
}
function Unselecter(){
let cells = Jupyter.notebook.get_cells();
for (let cell of cells){
cell.unselect();
}
}
function fixCellHeight(){
// Let's start with all the cell unselected, the unselect the current selected one
let scell = Jupyter.notebook.get_selected_cell();
scell.unselect();
// This select/unselect code cell triggers the "correct" heigth in the codemirror instance
let cells = Jupyter.notebook.get_cells();
for (let cell of cells){
if (cell.cell_type === "code") {
cell.select();
cell.unselect();
}
}
}
/* from notebook/actions.js
* jupyter-notebook:run-cell -> notebook.execute_selected_cells()
* jupyter-notebook:run-cell-and-select-next -> notebook.execute_cell_and_select_below()
*/
function smartExec() {
// is it really the selected cell that matters ?
let smart_exec = Jupyter.notebook.get_selected_cell().smart_exec;
if (smart_exec == 'smart_exec_slide') {
Jupyter.notebook.execute_selected_cells();
} else if (smart_exec == "smart_exec_fragment") {
// let's see if the next fragment is visible or not
let cell = Jupyter.notebook.get_selected_cell();
let fragment_div = cell.smart_exec_next_fragment;
let visible = $(fragment_div).hasClass('visible');
if (visible) {
Jupyter.notebook.execute_cell_and_select_below();
} else {
Jupyter.notebook.execute_selected_cells();
}
} else {
Jupyter.notebook.execute_cell_and_select_below();
}
}
// action for reveal.js and reveal.js plug-in bindings
// this a the dictionary structure as generated by nbextension_configurator
// with the corresponding API calls to RISE/reveal.js and/or its plug-ins
let reveal_actions = {
'main': { // RISE/reveal.js API calls
'firstSlide': () => Reveal.slide(0), // jump to first slide
'lastSlide': () => Reveal.slide( Number.MAX_VALUE ), // jump to last slide
'toggleOverview': () => Reveal.toggleOverview(), // toggle overview
'toggleAllRiseButtons': toggleAllRiseButtons, // show/hide buttons
'fullscreenHelp': fullscreenHelp, // show fullscreen help
'riseHelp': riseHelp, // '?' show our help
},
'chalkboard': { // API calls for RevealChalkboard plug-in
'clear': () => RevealChalkboard.clear(), // clear full size chalkboard
'reset': () => RevealChalkboard.reset(), // reset chalkboard data on current slide
'toggleChalkboard': () => RevealChalkboard.toggleChalkboard(), // toggle full size chalkboard
'toggleNotesCanvas': () => RevealChalkboard.toggleNotesCanvas(), // toggle notes (slide-local)
'colorNext': () => RevealChalkboard.colorNext(), // next color
'colorPrev': () => RevealChalkboard.colorPrev(), // previous color
'download': () => RevealChalkboard.download() // download recorded chalkboard drawing
},
'notes': { // API calls for RevealNotes plug-in
'openNotes' : () => RevealNotes.open(), // open speaker notes window
},
}
let reveal_helpstr = {
'main': { // RISE/reveal.js API calls
'firstSlide': 'jump to first slide',
'lastSlide': 'jump to last slide',
'toggleOverview': 'toggle overview',
'toggleAllRiseButtons': 'show/hide buttons',
'fullscreenHelp': 'show fullscreen help',
'riseHelp': 'show this help dialog'
},
'chalkboard': { // API calls for RevealChalkboard plug-in
'clear': 'clear full size chalkboard',
'reset': 'reset chalkboard data on current slide',
'toggleChalkboard': 'toggle full size chalkboard',
'toggleNotesCanvas': 'toggle notes (slide-local)',
'colorNext': 'cycle to next pen color',
'colorPrev': 'cycle to previous pen color',
'download': 'download recorded chalkboard drawing'
},
'notes': { // API calls for RevealNotes plug-in
'openNotes' : 'open speaker notes window'
},
}
// need to check, if we can fetch the default bindings from rise.yaml (nbconfig)
let reveal_default_bindings = {
'main': {
'firstSlide': 'home',
'lastSlide': 'end', // keycode 35
'toggleOverview': 'w', // keycode 87
//'toggleAllRiseButtons': 'm', // keycode 188 (",") is not allowed in jupyter! using m instead
'fullscreenHelp': 'f', // keycode 70
'riseHelp': '?', // keycode 63
},
'chalkboard': {
'clear': 'minus', // keycode 189 (and 173 on firefox)
'reset': '=', // keycode 187 (and 61 on firefox)
'toggleChalkboard': '[', // keycode 219
'toggleNotesCanvas': ']', // keycode 221
'colorPrev': 'q', // keycode 81
'colorNext': 's', // kecode 83
'download': '\\' // keycode 220
},
'notes': { // API calls for RevealNotes plug-in
'openNotes' : 't' // keycode 84
},
}
// update reveal bindings with custom key codes
function updateRevealBindings(default_bindings){
// console.log(`complete_config in updateRevealBindings`, complete_config);
let custom_shortcuts = complete_config.reveal_shortcuts;
// console.log(`custom_shortcuts in updateRevealBindings`, custom_shortcuts);
if (custom_shortcuts) {
for (const module of Object.keys(custom_shortcuts)){
for (const action of Object.keys(custom_shortcuts[module])){
default_bindings[module][action] = custom_shortcuts[module][action];
}
}
}
return default_bindings;
}
function setupKeys(mode){
let key_str;
let reveal_bindings = updateRevealBindings(reveal_default_bindings);
// Lets setup some specific keys for the reveal_mode
if (mode === 'reveal_mode') {
Jupyter.keyboard_manager.command_shortcuts.set_shortcut("shift-enter", "RISE:smart-exec");
Jupyter.keyboard_manager.edit_shortcuts.set_shortcut("shift-enter", "RISE:smart-exec");
// Save the f keyboard event for the Reveal fullscreen action
// see also #375
// reveal.js and chalkboard key bindings
// console.log(`complete_config in setupKeys: ${JSON.stringify(complete_config)}`);
// add all reveal.js bindings to jupyter
for (const module of Object.keys(reveal_bindings)){
for (const action of Object.keys(reveal_bindings[module])){
key_str = reveal_bindings[module][action];
Jupyter.keyboard_manager.command_shortcuts.set_shortcut(key_str, `RISE:${action}`);
// console.log(`Setup jupyter keybinding: ${key_str}, RISE:${action}.`);
}
}
try {
Jupyter.keyboard_manager.command_shortcuts.remove_shortcut("f");
Jupyter.keyboard_manager.command_shortcuts.set_shortcut("shift-f", "jupyter-notebook:find-and-replace");
} catch(error) {
console.log(`entering RISE : could not remove shortcut 'f' - ignored`);
}
} else if (mode === 'notebook_mode') {
Jupyter.keyboard_manager.command_shortcuts.set_shortcut("shift-enter", "jupyter-notebook:run-cell-and-select-next");
Jupyter.keyboard_manager.edit_shortcuts.set_shortcut("shift-enter", "jupyter-notebook:run-cell-and-select-next");
try {
Jupyter.keyboard_manager.command_shortcuts.remove_shortcut("shift-f");
Jupyter.keyboard_manager.command_shortcuts.set_shortcut("f", "jupyter-notebook:find-and-replace");
} catch(error) {
console.log(`exiting RISE : could not remove shortcut 'shift-f' - ignored`);
}
}
}
/*
* Creates a string of the valid shortcuts (i.e. the ones for which a key
* code could be identified). If no key code could be identified the keys are
* still mapped to the default key code values (a string provided by
* the default_str argument will be used instead).
*/
function shortcutRepr(shortcuts){
let key_str = "";
let first_entry = true;
if (shortcuts.length > 0){
for (const key of shortcuts.split(",")){
if (!first_entry){
key_str += ",<kbd>" + key + "</kbd>";
} else {
key_str += "<kbd>" + key + "</kbd>";
first_entry = false;
}
}
} else {
key_str += "<kbd>" + default_str + "</kbd>";
}
return key_str;
}
/*
* Creates a list item string for help dialog
*
* Args:
* shortcut_str = string representation of keyboard shortcut(s)
* default_str = default (fall back) string for key
* help_str = help text to be shown for item
*/
function helpListItem(shortcut_str, help_str){
return `<li>${shortcutRepr(shortcut_str)} : ${help_str}</li>`;
}
function riseHelp() {
let jupyter_keys;
let reveal_keys;
let cb_keys;
let no_keys;
//check if custom bindings for registered jupyter calls are defined
if (typeof complete_config.shortcuts !== 'undefined'){
jupyter_keys = complete_config.shortcuts;
}
else{
jupyter_keys = {};
}
let updated_keybindings = updateRevealBindings(reveal_default_bindings);
//console.log(`updated bindings: ${JSON.stringify(updated_keybindings)}`);
reveal_keys = updated_keybindings['main'];
cb_keys = updated_keybindings['chalkboard'];
no_keys = updated_keybindings['notes'];
let reveal_help = reveal_helpstr['main'];
let cb_help = reveal_helpstr['chalkboard'];
let no_help= reveal_helpstr['notes'];
let message = $('<div/>').append(
$("<p/></p>").addClass('dialog').html(
"<ul>" +
helpListItem(reveal_keys.riseHelp, reveal_help.riseHelp) +
"<li><kbd>Alt</kbd>+<kbd>r</kbd>: enter/exit RISE</li>" +
"<li><kbd>Space</kbd>: next</li>" +
"<li><kbd>Shift</kbd>+<kbd>Space</kbd>: previous</li>" +
"<li><kbd>Shift</kbd>+<kbd>Enter</kbd>: eval and select next cell if visible</li>" +
helpListItem(reveal_keys.firstSlide, reveal_help.firstSlide) +
helpListItem(reveal_keys.lastSlide, reveal_help.lastSlide) +
helpListItem(reveal_keys.toggleOverview, reveal_help.toggleOverview) +
helpListItem(no_keys.openNotes, no_help.openNotes) +
`<li><kbd>,</kbd>: ${reveal_help.toggleAllRiseButtons}</li>` +
"<li><kbd>/</kbd>: black screen</li>" +
"<li><strong>less useful:</strong>" +
"<ul>" +
"<li><kbd>PgUp</kbd>: up</li>" +
"<li><kbd>PgDn</kbd>: down</li>" +
"<li><kbd>Left Arrow</kbd>: left <em>(note: Space preferred)</em></li>" +
"<li><kbd>Right Arrow</kbd>: right <em>(note: Shift Space preferred)</em></li>" +
"</ul>" +
"<li><strong>with chalkboard enabled:</strong>" +
"<ul>" +
helpListItem(cb_keys.toggleChalkboard, cb_help.toggleChalkboard) +
helpListItem(cb_keys.toggleNotesCanvas, cb_help.toggleNotesCanvaas) +
helpListItem(cb_keys.colorNext, cb_help.colorNext) +
helpListItem(cb_keys.colorPrev, cb_help.colorPrev) +
helpListItem(cb_keys.download, cb_help.download) +
helpListItem(cb_keys.reset, cb_help.reset) +
helpListItem(cb_keys.clear, cb_help.clear) +
"</ul>" +
"</ul>" +
"<b>NOTE</b>: of course you have to use these shortcuts <b>in command mode.</b>"
)
);
Jupyter.dialog.modal({
title : "Reveal Shortcuts Help",
body : message,
buttons : {
OK : {class: "btn-danger"}
}
});
}
function buttonHelp() {
let help_button = $('<i/>')
.attr('id','help_b')
.attr('title','Reveal Shortcuts Help')
.addClass('fa-question fa-4x fa')
.addClass('my-main-tool-bar')
.click(riseHelp);
$('.reveal').after(help_button);
}
function buttonExit() {
let exit_button = $('<i/>')
.attr('id','exit_b')
.attr('title','Exit RISE')
.addClass('fa-times-circle fa-4x fa')
.addClass('my-main-tool-bar')
.click(revealMode);
$('.reveal').after(exit_button);
}
function fullscreenHelp() {
let message = $('<div/>').append(
$("<p/></p>").addClass('dialog').html(
"<b>Entering Fullscreen mode from inside RISE is disabled.</b>" +
"<br>" +
"<b>Exit RISE, make you browser Fullscreen and re-enter RISE</b>" +
"<br>" +
"That will help Reveal.js to perform the correct transformations " +
"at the time to interact with code cells."
)
);
Jupyter.dialog.modal({
title : "Fullscreen Help",
body : message,
buttons : {
OK : {class: "btn-danger"}
}
});
}
function removeHash() {
history.pushState("", document.title, window.location.pathname
+ window.location.search);
}