Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .cache/pip/http-v2/0/4/1/8/c/0418c83b80f7f7bfaec2738bfbbee53d2c1562196c0781702f6eddc8 +0 -0
- .cache/pip/http-v2/0/4/1/8/c/0418c83b80f7f7bfaec2738bfbbee53d2c1562196c0781702f6eddc8.body +0 -0
- .cache/pip/http-v2/0/7/5/9/3/07593bb905dded4b84aacb1d96c1e64704669d6bab658dcaeab79c36 +0 -0
- .cache/pip/http-v2/0/c/2/1/8/0c218d526767991766d9b365a4f3e4449ce3579fd8b0f96a707d5a52 +0 -0
- .cache/pip/http-v2/0/c/2/1/8/0c218d526767991766d9b365a4f3e4449ce3579fd8b0f96a707d5a52.body +0 -0
- .cache/pip/http-v2/0/c/f/6/e/0cf6e817e2c5554000c735ecab0f3cf492f7d33b50d5a474a801ba24 +0 -0
- .cache/pip/http-v2/0/c/f/6/e/0cf6e817e2c5554000c735ecab0f3cf492f7d33b50d5a474a801ba24.body +0 -0
- .cache/pip/http-v2/6/1/4/f/4/614f46c6d1c16fa5b0800dfd0497e41c5b320e16ee8c9d943d4dd341.body +0 -0
- .cache/pip/http-v2/6/1/6/7/8/61678d682a1ea716fb4acccebc1350da197d2251a96e4b9220061051 +0 -0
- .cache/pip/http-v2/8/7/7/1/9/87719c54152648e1f5dd42370f81c27a1523da9b52543b783f02ec41 +0 -0
- .cache/pip/http-v2/8/7/7/1/9/87719c54152648e1f5dd42370f81c27a1523da9b52543b783f02ec41.body +0 -0
- .cache/pip/http-v2/8/9/8/0/7/8980772ed68d7fbcfe2ef2b3f1911b9bd45462ad1becf93269532743.body +0 -0
- .local/share/jupyter/nbextensions/codefolding/codefolding_firstline_unfolded.png +0 -0
- .local/share/jupyter/nbextensions/collapsible_headings/main.js +1092 -0
- .local/share/jupyter/nbextensions/collapsible_headings/readme.md +125 -0
- .local/share/jupyter/nbextensions/comment-uncomment/icon.png +0 -0
- .local/share/jupyter/nbextensions/comment-uncomment/readme.md +11 -0
- .local/share/jupyter/nbextensions/contrib_nbextensions_help_item/contrib_nbextensions_help_item.yaml +7 -0
- .local/share/jupyter/nbextensions/datestamper/icon.png +0 -0
- .local/share/jupyter/nbextensions/equation-numbering/info.yaml +7 -0
- .local/share/jupyter/nbextensions/equation-numbering/main.js +39 -0
- .local/share/jupyter/nbextensions/execute_time/ExecuteTime.css +6 -0
- .local/share/jupyter/nbextensions/execute_time/ExecuteTime.js +349 -0
- .local/share/jupyter/nbextensions/execute_time/ExecuteTime.yaml +88 -0
- .local/share/jupyter/nbextensions/execute_time/execution-timings-box.png +0 -0
- .local/share/jupyter/nbextensions/execute_time/icon.png +0 -0
- .local/share/jupyter/nbextensions/execute_time/readme.md +206 -0
- .local/share/jupyter/nbextensions/execution_dependencies/README.md +43 -0
- .local/share/jupyter/nbextensions/execution_dependencies/execution_dependencies.js +139 -0
- .local/share/jupyter/nbextensions/exercise/history.md +26 -0
- .local/share/jupyter/nbextensions/exercise/icon.png +0 -0
- .local/share/jupyter/nbextensions/exercise/image.gif +0 -0
- .local/share/jupyter/nbextensions/exercise/main.js +169 -0
- .local/share/jupyter/nbextensions/exercise2/icon.png +0 -0
- .local/share/jupyter/nbextensions/exercise2/image.gif +0 -0
- .local/share/jupyter/nbextensions/exercise2/main.css +60 -0
- .local/share/jupyter/nbextensions/exercise2/main.js +169 -0
- .local/share/jupyter/nbextensions/export_embedded/export_embedded.yaml +7 -0
- .local/share/jupyter/nbextensions/export_embedded/main.js +56 -0
- .local/share/jupyter/nbextensions/export_embedded/readme.md +7 -0
- .local/share/jupyter/nbextensions/freeze/config.yaml +20 -0
- .local/share/jupyter/nbextensions/freeze/icon.png +0 -0
- .local/share/jupyter/nbextensions/freeze/main.js +205 -0
- .local/share/jupyter/nbextensions/freeze/readme.md +24 -0
- .local/share/jupyter/nbextensions/gist_it/gist_it.yaml +18 -0
- .local/share/jupyter/nbextensions/gist_it/icon.png +0 -0
- .local/share/jupyter/nbextensions/gist_it/main.js +481 -0
- .local/share/jupyter/nbextensions/go_to_current_running_cell/README.md +22 -0
- .local/share/jupyter/nbextensions/go_to_current_running_cell/eye.png +0 -0
- .local/share/jupyter/nbextensions/go_to_current_running_cell/go_to_current_running_cell.yaml +26 -0
.cache/pip/http-v2/0/4/1/8/c/0418c83b80f7f7bfaec2738bfbbee53d2c1562196c0781702f6eddc8
ADDED
Binary file (1.82 kB). View file
|
|
.cache/pip/http-v2/0/4/1/8/c/0418c83b80f7f7bfaec2738bfbbee53d2c1562196c0781702f6eddc8.body
ADDED
Binary file (147 kB). View file
|
|
.cache/pip/http-v2/0/7/5/9/3/07593bb905dded4b84aacb1d96c1e64704669d6bab658dcaeab79c36
ADDED
Binary file (1.81 kB). View file
|
|
.cache/pip/http-v2/0/c/2/1/8/0c218d526767991766d9b365a4f3e4449ce3579fd8b0f96a707d5a52
ADDED
Binary file (1.82 kB). View file
|
|
.cache/pip/http-v2/0/c/2/1/8/0c218d526767991766d9b365a4f3e4449ce3579fd8b0f96a707d5a52.body
ADDED
Binary file (43.7 kB). View file
|
|
.cache/pip/http-v2/0/c/f/6/e/0cf6e817e2c5554000c735ecab0f3cf492f7d33b50d5a474a801ba24
ADDED
Binary file (1.81 kB). View file
|
|
.cache/pip/http-v2/0/c/f/6/e/0cf6e817e2c5554000c735ecab0f3cf492f7d33b50d5a474a801ba24.body
ADDED
Binary file (7.87 kB). View file
|
|
.cache/pip/http-v2/6/1/4/f/4/614f46c6d1c16fa5b0800dfd0497e41c5b320e16ee8c9d943d4dd341.body
ADDED
Binary file (32.2 kB). View file
|
|
.cache/pip/http-v2/6/1/6/7/8/61678d682a1ea716fb4acccebc1350da197d2251a96e4b9220061051
ADDED
Binary file (1.81 kB). View file
|
|
.cache/pip/http-v2/8/7/7/1/9/87719c54152648e1f5dd42370f81c27a1523da9b52543b783f02ec41
ADDED
Binary file (1.81 kB). View file
|
|
.cache/pip/http-v2/8/7/7/1/9/87719c54152648e1f5dd42370f81c27a1523da9b52543b783f02ec41.body
ADDED
Binary file (3.42 kB). View file
|
|
.cache/pip/http-v2/8/9/8/0/7/8980772ed68d7fbcfe2ef2b3f1911b9bd45462ad1becf93269532743.body
ADDED
Binary file (530 kB). View file
|
|
.local/share/jupyter/nbextensions/codefolding/codefolding_firstline_unfolded.png
ADDED
.local/share/jupyter/nbextensions/collapsible_headings/main.js
ADDED
@@ -0,0 +1,1092 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(requirejs.specified('base/js/namespace') ? define : function (deps, callback) {
|
2 |
+
// if here, the Jupyter namespace hasn't been specified to be loaded.
|
3 |
+
// This means that we're probably embedded in a page, so we need to make
|
4 |
+
// our definition with a specific module name
|
5 |
+
"use strict";
|
6 |
+
return define('nbextensions/collapsible_headings/main', deps, callback);
|
7 |
+
})(['jquery', 'require'], function ($, requirejs) {
|
8 |
+
"use strict";
|
9 |
+
|
10 |
+
var mod_name = 'collapsible_headings';
|
11 |
+
var log_prefix = '[' + mod_name + ']';
|
12 |
+
var action_names = { // set on registration
|
13 |
+
insert_above: '',
|
14 |
+
insert_below: '',
|
15 |
+
collapse: '',
|
16 |
+
uncollapse: '',
|
17 |
+
select: ''
|
18 |
+
};
|
19 |
+
var select_reveals = true; // used as a flag to prevent selecting a heading section from also opening it
|
20 |
+
|
21 |
+
// define default values for config parameters
|
22 |
+
var params = {
|
23 |
+
add_button : false,
|
24 |
+
add_all_cells_button: false,
|
25 |
+
add_insert_header_buttons: false,
|
26 |
+
use_toggle_controls : true,
|
27 |
+
make_toggle_controls_buttons : false,
|
28 |
+
size_toggle_controls_by_level : true,
|
29 |
+
toggle_open_icon : 'fa-caret-down',
|
30 |
+
toggle_closed_icon : 'fa-caret-right',
|
31 |
+
toggle_color : '#aaaaaa',
|
32 |
+
use_shortcuts : true,
|
33 |
+
shortcuts: {
|
34 |
+
collapse: 'left',
|
35 |
+
collapse_all: 'ctrl-shift-left',
|
36 |
+
uncollapse: 'right',
|
37 |
+
uncollapse_all: 'ctrl-shift-right',
|
38 |
+
select: 'shift-right',
|
39 |
+
insert_above: 'shift-a',
|
40 |
+
insert_below: 'shift-b',
|
41 |
+
},
|
42 |
+
show_section_brackets : false,
|
43 |
+
section_bracket_width : 10,
|
44 |
+
show_ellipsis : true,
|
45 |
+
select_reveals : true,
|
46 |
+
collapse_to_match_toc: false,
|
47 |
+
indent_px: 8,
|
48 |
+
};
|
49 |
+
|
50 |
+
// ------------------------------------------------------------------------
|
51 |
+
// Jupyter is used when we're in a live notebook, but in non-live notebook
|
52 |
+
// settings, it remains undefined.
|
53 |
+
// It is declared here to allow us to keep logic for live/nonlive functions
|
54 |
+
// together.
|
55 |
+
var Jupyter;
|
56 |
+
// similarly, in a live notebook, events is the Jupyter global events
|
57 |
+
// object, but in a non-live notebook, we must construct our own version
|
58 |
+
var events;
|
59 |
+
try {
|
60 |
+
events = requirejs('base/js/events');
|
61 |
+
}
|
62 |
+
catch (err) {
|
63 |
+
// in non-live notebook, there's no events structure, so we make our own
|
64 |
+
if (window.events === undefined) {
|
65 |
+
var Events = function () {};
|
66 |
+
window.events = $([new Events()]);
|
67 |
+
}
|
68 |
+
events = window.events;
|
69 |
+
}
|
70 |
+
|
71 |
+
// global flag denoting whether we're in a live notebook or exported html.
|
72 |
+
// In a live notebook we operate on Cell instances, in exported html we
|
73 |
+
// operate on jQuery collections of '.cell' elements
|
74 |
+
var live_notebook = false;
|
75 |
+
|
76 |
+
|
77 |
+
// Some functions providing things akin to Jupyter.notebook methods, but
|
78 |
+
// which can work using jQuery collections in place of Cell instances.
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Return all cells in the notebook (or cell elements if notebook not live)
|
82 |
+
*/
|
83 |
+
function _get_cells () {
|
84 |
+
return live_notebook ? Jupyter.notebook.get_cells() : $('#notebook-container > .cell');
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Return cell at index index (or cell element if notebook not live)
|
89 |
+
*/
|
90 |
+
function _get_cell_at_index (index) {
|
91 |
+
return live_notebook ? Jupyter.notebook.get_cell(index) : $('.cell').eq(index);
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Return the index of the given cell (or cell element if notebook not live)
|
96 |
+
*/
|
97 |
+
function _find_cell_index (cell) {
|
98 |
+
return live_notebook ? Jupyter.notebook.find_cell_index(cell) : $(cell).index();
|
99 |
+
}
|
100 |
+
|
101 |
+
// ------------------------------------------------------------------------
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Return the level of nbcell.
|
105 |
+
* The cell level is an integer in the range 1-7 inclusive
|
106 |
+
*
|
107 |
+
* @param {Object} cell Cell instance or jQuery collection of '.cell' elements
|
108 |
+
* @return {Integer} cell level
|
109 |
+
*/
|
110 |
+
function get_cell_level (cell) {
|
111 |
+
// headings can have a level up to 6, so 7 is used for a non-heading
|
112 |
+
var level = 7;
|
113 |
+
if (cell === undefined) {
|
114 |
+
return level;
|
115 |
+
}
|
116 |
+
if (live_notebook) {
|
117 |
+
if ((typeof(cell) === 'object') && (cell.cell_type === 'markdown')) {
|
118 |
+
level = cell.get_text().match(/^#*/)[0].length || level;
|
119 |
+
}
|
120 |
+
}
|
121 |
+
else {
|
122 |
+
// the jQuery pseudo-selector :header is useful for us, but is
|
123 |
+
// implemented in javascript rather than standard css selectors,
|
124 |
+
// which get implemented in native browser code.
|
125 |
+
// So we get best performance by using css-native first, then filtering
|
126 |
+
var only_child_header = $(cell).find(
|
127 |
+
'.inner_cell > .rendered_html > :only-child'
|
128 |
+
).filter(':header');
|
129 |
+
if (only_child_header.length > 0) {
|
130 |
+
level = Number(only_child_header[0].tagName.substring(1));
|
131 |
+
}
|
132 |
+
}
|
133 |
+
return Math.min(level, 7); // we rely on 7 being max
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Check if a cell is a heading cell.
|
138 |
+
*
|
139 |
+
* @param {Object} cell Cell instance or jQuery collection of '.cell' elements
|
140 |
+
* @return {Boolean}
|
141 |
+
*/
|
142 |
+
function is_heading (cell) {
|
143 |
+
return get_cell_level(cell) < 7;
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Check if a heading cell is collapsed.
|
148 |
+
*
|
149 |
+
* Should in general return false on non-heading cells, but this is
|
150 |
+
* dependent on metadata/css classes, so don't rely on it.
|
151 |
+
*
|
152 |
+
* @param {Object} cell Cell instance or jQuery collection of '.cell' elements
|
153 |
+
* @return {Boolean}
|
154 |
+
*/
|
155 |
+
function _is_collapsed (heading_cell) {
|
156 |
+
if (live_notebook) {
|
157 |
+
return heading_cell.metadata.heading_collapsed === true;
|
158 |
+
}
|
159 |
+
return $(heading_cell).hasClass('collapsible_headings_collapsed');
|
160 |
+
}
|
161 |
+
|
162 |
+
/**
|
163 |
+
* Alter cell so that _is_collapsed called on it will return set_collapsed
|
164 |
+
*/
|
165 |
+
function _set_collapsed (heading_cell, set_collapsed) {
|
166 |
+
set_collapsed = set_collapsed !== undefined ? set_collapsed : true;
|
167 |
+
if (live_notebook) {
|
168 |
+
if (set_collapsed) {
|
169 |
+
heading_cell.metadata.heading_collapsed = true;
|
170 |
+
}
|
171 |
+
else {
|
172 |
+
delete heading_cell.metadata.heading_collapsed;
|
173 |
+
}
|
174 |
+
}
|
175 |
+
else {
|
176 |
+
$(heading_cell).toggleClass('collapsible_headings_collapsed', set_collapsed);
|
177 |
+
}
|
178 |
+
return set_collapsed;
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* Check if a cell is a collapsed heading cell.
|
183 |
+
*
|
184 |
+
* @param {Object} cell Cell instance or jQuery collection of '.cell' elements
|
185 |
+
* @return {Boolean}
|
186 |
+
*/
|
187 |
+
function is_collapsed_heading (cell) {
|
188 |
+
return is_heading(cell) && _is_collapsed(cell);
|
189 |
+
}
|
190 |
+
|
191 |
+
/**
|
192 |
+
* Uncollapse any headings which are hiding the cell at index
|
193 |
+
*
|
194 |
+
* @param {Integer} index - index of cell to reveal
|
195 |
+
*/
|
196 |
+
function reveal_cell_by_index (index) {
|
197 |
+
// Restrict the search to cells that are of the same level and lower
|
198 |
+
// than the currently selected cell by index.
|
199 |
+
var ref_cell = _get_cell_at_index(index);
|
200 |
+
// ref_cell may be null, if we've attempted to extend selection beyond
|
201 |
+
// the existing cells
|
202 |
+
if (!ref_cell) {
|
203 |
+
return;
|
204 |
+
}
|
205 |
+
var pivot_level = get_cell_level(ref_cell);
|
206 |
+
var cells = _get_cells();
|
207 |
+
while (index > 0 && pivot_level > 1) {
|
208 |
+
index--;
|
209 |
+
var cell = cells[index];
|
210 |
+
var cell_level = get_cell_level(cell);
|
211 |
+
if (cell_level < pivot_level) {
|
212 |
+
if (is_collapsed_heading(cell)) {
|
213 |
+
toggle_heading(cell);
|
214 |
+
}
|
215 |
+
pivot_level = cell_level;
|
216 |
+
}
|
217 |
+
}
|
218 |
+
}
|
219 |
+
|
220 |
+
/**
|
221 |
+
* Add or remove collapsed/uncollapsed classes & metadata to match the
|
222 |
+
* cell's status as a non-heading or collapsed/uncollapsed heading
|
223 |
+
*
|
224 |
+
* @param {Object} cell Cell instance or jQuery collection of '.cell' elements
|
225 |
+
* @return {undefined}
|
226 |
+
*/
|
227 |
+
function update_heading_cell_status (cell) {
|
228 |
+
var level = get_cell_level(cell);
|
229 |
+
var cell_is_heading = level < 7;
|
230 |
+
var cell_elt = live_notebook ? cell.element : $(cell);
|
231 |
+
var cht = cell_elt.find('.input_prompt > .collapsible_headings_toggle');
|
232 |
+
if (cell_is_heading) {
|
233 |
+
var collapsed = _is_collapsed(cell);
|
234 |
+
cell_elt.toggleClass('collapsible_headings_collapsed', collapsed);
|
235 |
+
cell_elt.toggleClass('collapsible_headings_ellipsis', params.show_ellipsis);
|
236 |
+
if (params.use_toggle_controls) {
|
237 |
+
if (cht.length < 1) {
|
238 |
+
cht = $('<div/>')
|
239 |
+
.addClass('collapsible_headings_toggle')
|
240 |
+
.css('color', params.toggle_color)
|
241 |
+
.append('<div><i class="fa fa-fw"></i></div>')
|
242 |
+
.appendTo(cell_elt.find('.input_prompt'));
|
243 |
+
var clickable = cht.find('i');
|
244 |
+
if (params.make_toggle_controls_buttons) {
|
245 |
+
cht.addClass('btn btn-default');
|
246 |
+
clickable = cht;
|
247 |
+
}
|
248 |
+
if (live_notebook) {
|
249 |
+
clickable.on('click', function () { toggle_heading(cell); });
|
250 |
+
}
|
251 |
+
else {
|
252 |
+
// in non-live notebook, cell isn;t editable, so make it clickable also
|
253 |
+
var only_child_header = cell_elt.find(
|
254 |
+
'.inner_cell > .rendered_html > :only-child'
|
255 |
+
).filter(':header');
|
256 |
+
clickable.add(only_child_header)
|
257 |
+
.css('cursor', 'pointer')
|
258 |
+
.on('click', function (evt) {
|
259 |
+
// evt.target is what was clicked, not what the handler was attached to
|
260 |
+
if (!$(evt.target).hasClass('anchor-link')) {
|
261 |
+
toggle_heading(cell);
|
262 |
+
}
|
263 |
+
});
|
264 |
+
}
|
265 |
+
}
|
266 |
+
// Update the cell's toggle control classes
|
267 |
+
var hwrap = cht.children();
|
268 |
+
hwrap.find('.fa')
|
269 |
+
.toggleClass(params.toggle_closed_icon, collapsed)
|
270 |
+
.toggleClass(params.toggle_open_icon, !collapsed);
|
271 |
+
if (params.size_toggle_controls_by_level) {
|
272 |
+
for (var hh = 1; hh < 7; hh++) {
|
273 |
+
hwrap.toggleClass('h' + hh, hh == level);
|
274 |
+
}
|
275 |
+
}
|
276 |
+
}
|
277 |
+
}
|
278 |
+
else {
|
279 |
+
_set_collapsed(cell, false);
|
280 |
+
cell_elt.removeClass('collapsible_headings_collapsed');
|
281 |
+
cht.remove();
|
282 |
+
}
|
283 |
+
}
|
284 |
+
|
285 |
+
/**
|
286 |
+
* find the closest header cell to input cell
|
287 |
+
*
|
288 |
+
* @param {Object} cell Cell instance or jQuery collection of '.cell' elements
|
289 |
+
* @param {Function} a function to filter which header cells can be
|
290 |
+
* returned. Should take a notebook cell/jquer element as
|
291 |
+
* input (depending on whether we're in a live notebook),
|
292 |
+
* and return true if the given cell is acceptable.
|
293 |
+
* @return {Object | undefined}
|
294 |
+
*/
|
295 |
+
function find_header_cell (cell, test_func) {
|
296 |
+
var index = _find_cell_index(cell);
|
297 |
+
for (; index >= 0; index--) {
|
298 |
+
cell = _get_cell_at_index(index);
|
299 |
+
if (is_heading(cell) && (test_func === undefined || test_func(cell))) {
|
300 |
+
return cell;
|
301 |
+
}
|
302 |
+
}
|
303 |
+
return undefined;
|
304 |
+
}
|
305 |
+
|
306 |
+
/**
|
307 |
+
* Select the section enclosed by the given heading cell.
|
308 |
+
*
|
309 |
+
* Only callable from a live notebook, so require no special cell handling
|
310 |
+
*
|
311 |
+
* @param {Object} head_cell Cell instance or jQuery collection of '.cell' elements
|
312 |
+
* @return {undefined}
|
313 |
+
*/
|
314 |
+
function select_heading_section(head_cell, extend) {
|
315 |
+
var head_lvl = get_cell_level(head_cell);
|
316 |
+
var ncells = Jupyter.notebook.ncells();
|
317 |
+
var head_ind = _find_cell_index(head_cell);
|
318 |
+
var tail_ind;
|
319 |
+
for (tail_ind = head_ind; tail_ind + 1 < ncells; tail_ind++) {
|
320 |
+
if (get_cell_level(_get_cell_at_index(tail_ind + 1)) <= head_lvl) {
|
321 |
+
break;
|
322 |
+
}
|
323 |
+
}
|
324 |
+
select_reveals = params.select_reveals;
|
325 |
+
if (extend) {
|
326 |
+
var ank_ind = Jupyter.notebook.get_anchor_index();
|
327 |
+
if (ank_ind <= head_ind) {
|
328 |
+
// keep current anchor, extend to head
|
329 |
+
Jupyter.notebook.select(tail_ind, false);
|
330 |
+
select_reveals = true;
|
331 |
+
return;
|
332 |
+
}
|
333 |
+
else if (ank_ind >= tail_ind) {
|
334 |
+
// keep current anchor, extend to tail
|
335 |
+
Jupyter.notebook.select(head_ind, false);
|
336 |
+
select_reveals = true;
|
337 |
+
return;
|
338 |
+
}
|
339 |
+
// head_ind < ank_ind < tail_ind i.e. anchor is inside section
|
340 |
+
}
|
341 |
+
// move_anchor to header cell
|
342 |
+
Jupyter.notebook.select(head_ind, true);
|
343 |
+
// don't move anchor, i.e. extend, to tail cell
|
344 |
+
Jupyter.notebook.select(tail_ind, false);
|
345 |
+
select_reveals = true;
|
346 |
+
}
|
347 |
+
|
348 |
+
/**
|
349 |
+
* Return all of the cell _elements _which are part of the section headed by
|
350 |
+
* the given cell
|
351 |
+
*
|
352 |
+
* @param {Object} head_cell Cell instance or jQuery collection of '.cell' elements
|
353 |
+
*/
|
354 |
+
function get_jquery_bracket_section (head_cell) {
|
355 |
+
var head_lvl = get_cell_level(head_cell);
|
356 |
+
var cells = _get_cells();
|
357 |
+
var cell_elements = $(live_notebook ? head_cell.element : head_cell);
|
358 |
+
for (var ii = _find_cell_index(head_cell); ii < cells.length; ii++) {
|
359 |
+
var cell = live_notebook ? cells[ii] : cells.eq(ii);
|
360 |
+
|
361 |
+
if (get_cell_level(cell) <= head_lvl) {
|
362 |
+
break;
|
363 |
+
}
|
364 |
+
cell_elements = cell_elements.add(live_notebook ? cell.element : cell);
|
365 |
+
}
|
366 |
+
return cell_elements;
|
367 |
+
}
|
368 |
+
|
369 |
+
/**
|
370 |
+
* Callback function attached to the bracket-containing div, should toggle
|
371 |
+
* the relevant heading
|
372 |
+
*/
|
373 |
+
var bracket_callback_timeout_id;
|
374 |
+
function bracket_callback (evt) {
|
375 |
+
// prevent bubbling, otherwise when closing a section, the cell gets
|
376 |
+
// selected & re-revealed after being hidden
|
377 |
+
evt.preventDefault();
|
378 |
+
evt.stopPropagation();
|
379 |
+
// evt.target is what was clicked, not what the handler was attached to
|
380 |
+
var bracket = $(evt.target);
|
381 |
+
var bracket_level = Number(bracket.attr('data-bracket-level'));
|
382 |
+
if (bracket_level) {
|
383 |
+
var bracket_cell = live_notebook ? bracket.closest('.cell').data('cell') : bracket.closest('.cell');
|
384 |
+
var header_cell = find_header_cell(bracket_cell, function (cell) {
|
385 |
+
return get_cell_level(cell) == bracket_level;
|
386 |
+
});
|
387 |
+
switch (evt.type) {
|
388 |
+
case 'dblclick':
|
389 |
+
clearTimeout(bracket_callback_timeout_id);
|
390 |
+
bracket_callback_timeout_id = undefined;
|
391 |
+
toggle_heading(header_cell);
|
392 |
+
break;
|
393 |
+
case 'click':
|
394 |
+
if (live_notebook && (bracket_callback_timeout_id === undefined)) {
|
395 |
+
bracket_callback_timeout_id = setTimeout(function () {
|
396 |
+
select_heading_section(header_cell, evt.shiftKey);
|
397 |
+
bracket_callback_timeout_id = undefined;
|
398 |
+
}, 300);
|
399 |
+
}
|
400 |
+
break;
|
401 |
+
case 'mouseenter':
|
402 |
+
case 'mouseleave':
|
403 |
+
var in_section = get_jquery_bracket_section(header_cell)
|
404 |
+
.find('.chb div[data-bracket-level=' + bracket_level + ']');
|
405 |
+
$('.chb div').not(in_section).removeClass('chb-hover');
|
406 |
+
in_section.toggleClass('chb-hover', evt.type === 'mouseenter');
|
407 |
+
break;
|
408 |
+
}
|
409 |
+
}
|
410 |
+
return false;
|
411 |
+
}
|
412 |
+
|
413 |
+
/**
|
414 |
+
* Update the hidden/collapsed status of all the cells under
|
415 |
+
* - the notebook, if param cell === undefined
|
416 |
+
* - the heading which contains the specified cell (if cell !== undefined,
|
417 |
+
* but is also not a heading)
|
418 |
+
* - the specified heading cell (if specified cell is a heading)
|
419 |
+
*
|
420 |
+
* @param {Object} cell Cell instance or jQuery collection of '.cell' elements
|
421 |
+
* @return {undefined}
|
422 |
+
*/
|
423 |
+
function update_collapsed_headings (cell) {
|
424 |
+
var index = 0;
|
425 |
+
var section_level = 0;
|
426 |
+
var show = true;
|
427 |
+
if (cell !== undefined && (cell = find_header_cell(cell)) !== undefined) {
|
428 |
+
index = _find_cell_index(cell) + 1;
|
429 |
+
section_level = get_cell_level(cell);
|
430 |
+
show = !_is_collapsed(cell);
|
431 |
+
}
|
432 |
+
var hide_above = 7;
|
433 |
+
var brackets_open = {};
|
434 |
+
var max_open = 0; // count max number open at one time to calc padding
|
435 |
+
for (var cells = _get_cells(); index < cells.length; index++) {
|
436 |
+
cell = cells[index];
|
437 |
+
var cell_elt = live_notebook ? cell.element : $(cell);
|
438 |
+
var level = get_cell_level(cell);
|
439 |
+
if (level <= section_level) {
|
440 |
+
break;
|
441 |
+
}
|
442 |
+
if (show && level <= hide_above) {
|
443 |
+
cell_elt.slideDown('fast');
|
444 |
+
hide_above = is_collapsed_heading(cell) ? level : 7;
|
445 |
+
if (live_notebook) {
|
446 |
+
delete cell.metadata.hidden;
|
447 |
+
}
|
448 |
+
}
|
449 |
+
else {
|
450 |
+
cell_elt.slideUp('fast');
|
451 |
+
if (live_notebook) {
|
452 |
+
cell.metadata.hidden = true;
|
453 |
+
}
|
454 |
+
continue;
|
455 |
+
}
|
456 |
+
|
457 |
+
if (params.show_section_brackets) {
|
458 |
+
var chb = cell_elt.find('.chb').empty();
|
459 |
+
if (chb.length < 1) {
|
460 |
+
chb = $('<div/>')
|
461 |
+
.addClass('chb')
|
462 |
+
.on('click dblclick', bracket_callback)
|
463 |
+
.appendTo(cell_elt);
|
464 |
+
}
|
465 |
+
var num_open = 0; // count number of brackets currently open
|
466 |
+
for (var jj = 1; jj < 7; jj++) {
|
467 |
+
if (brackets_open[jj] && level <= jj) {
|
468 |
+
brackets_open[jj].addClass('chb-end'); // closing, add class
|
469 |
+
delete brackets_open[jj]; // closed
|
470 |
+
}
|
471 |
+
var opening = level == jj;
|
472 |
+
if (brackets_open[jj] || opening) {
|
473 |
+
num_open++;
|
474 |
+
brackets_open[jj] = $('<div/>')
|
475 |
+
.on('mouseenter mouseleave', bracket_callback)
|
476 |
+
.attr('data-bracket-level', jj)
|
477 |
+
.appendTo(chb); // add bracket element
|
478 |
+
if (opening) { // opening, add class
|
479 |
+
brackets_open[jj].addClass('chb-start');
|
480 |
+
}
|
481 |
+
}
|
482 |
+
}
|
483 |
+
max_open = Math.max(num_open, max_open);
|
484 |
+
}
|
485 |
+
}
|
486 |
+
if (params.show_section_brackets) {
|
487 |
+
// close any remaining
|
488 |
+
for (var ii in brackets_open) {
|
489 |
+
brackets_open[ii].addClass('chb-end');
|
490 |
+
}
|
491 |
+
// adjust padding to fit in brackets
|
492 |
+
var bwidth = params.section_bracket_width;
|
493 |
+
var dwidth = max_open * (2 + bwidth);
|
494 |
+
$('#notebook-container').css('padding-right', (16 + dwidth) + 'px');
|
495 |
+
$('.chb')
|
496 |
+
.css('right', '-' + (3 + dwidth) + 'px')
|
497 |
+
.find('div')
|
498 |
+
.css('width', bwidth);
|
499 |
+
}
|
500 |
+
}
|
501 |
+
|
502 |
+
/**
|
503 |
+
* Hide/reveal all cells in the section headed by cell.
|
504 |
+
*
|
505 |
+
* @param {Object} cell Cell instance or jQuery collection of '.cell' elements
|
506 |
+
*/
|
507 |
+
function toggle_heading (cell, set_collapsed, trigger_event) {
|
508 |
+
if (is_heading(cell)) {
|
509 |
+
if (set_collapsed === undefined) {
|
510 |
+
set_collapsed = !_is_collapsed(cell);
|
511 |
+
}
|
512 |
+
_set_collapsed(cell, set_collapsed);
|
513 |
+
update_heading_cell_status(cell);
|
514 |
+
update_collapsed_headings(params.show_section_brackets ? undefined : cell);
|
515 |
+
console.log(log_prefix, set_collapsed ? 'collapsed' : 'expanded', 'cell', _find_cell_index(cell));
|
516 |
+
if (trigger_event !== false) {
|
517 |
+
events.trigger((set_collapsed ? '' : 'un') + 'collapse.CollapsibleHeading', {cell: cell});
|
518 |
+
}
|
519 |
+
}
|
520 |
+
}
|
521 |
+
|
522 |
+
/**
|
523 |
+
* Return a promise which resolves when the Notebook class methods have
|
524 |
+
* been appropriately patched.
|
525 |
+
* Patches methods
|
526 |
+
* - Notebook.select
|
527 |
+
* - Notebook.undelete
|
528 |
+
*
|
529 |
+
* @return {Promise}
|
530 |
+
*/
|
531 |
+
function patch_Notebook () {
|
532 |
+
return new Promise(function (resolve, reject) {
|
533 |
+
requirejs(['notebook/js/notebook'], function on_success (notebook) {
|
534 |
+
console.debug(log_prefix, 'patching Notebook.protoype');
|
535 |
+
|
536 |
+
// we have to patch select, since the select.Cell event is only fired
|
537 |
+
// by cell click events, not by the notebook select method
|
538 |
+
var orig_notebook_select = notebook.Notebook.prototype.select;
|
539 |
+
notebook.Notebook.prototype.select = function (index, moveanchor) {
|
540 |
+
if (select_reveals) {
|
541 |
+
reveal_cell_by_index(index);
|
542 |
+
}
|
543 |
+
return orig_notebook_select.apply(this, arguments);
|
544 |
+
};
|
545 |
+
resolve();
|
546 |
+
}, reject);
|
547 |
+
}).catch(function on_reject (reason) {
|
548 |
+
console.warn(log_prefix, 'error patching Notebook.protoype:', reason);
|
549 |
+
});
|
550 |
+
}
|
551 |
+
|
552 |
+
/**
|
553 |
+
* Return a promise which resolves when the TextCell class methods have
|
554 |
+
* been appropriately patched.
|
555 |
+
*
|
556 |
+
* Patches TextCell.set_text to update headings.
|
557 |
+
* This is useful for undelete and copy/paste of cells, which don't fire
|
558 |
+
* markdown.
|
559 |
+
*
|
560 |
+
* @return {Promise}
|
561 |
+
*/
|
562 |
+
function patch_TextCell () {
|
563 |
+
return new Promise(function (resolve, reject) {
|
564 |
+
requirejs(['notebook/js/textcell'], function on_success (textcell) {
|
565 |
+
console.debug(log_prefix, 'patching TextCell.protoype');
|
566 |
+
var orig_set_text = textcell.TextCell.prototype.set_text;
|
567 |
+
textcell.TextCell.prototype.set_text = function (text) {
|
568 |
+
var ret = orig_set_text.apply(this, arguments);
|
569 |
+
if (Jupyter.notebook._fully_loaded) {
|
570 |
+
update_heading_cell_status(this);
|
571 |
+
update_collapsed_headings();
|
572 |
+
}
|
573 |
+
return ret;
|
574 |
+
};
|
575 |
+
resolve();
|
576 |
+
}, reject);
|
577 |
+
}).catch(function on_reject (reason) {
|
578 |
+
console.warn(log_prefix, 'error patching TextCell.protoype:', reason);
|
579 |
+
});
|
580 |
+
}
|
581 |
+
|
582 |
+
/**
|
583 |
+
* Return a promise which resolves when the Tooltip class methods have
|
584 |
+
* been appropriately patched.
|
585 |
+
*
|
586 |
+
* For notebook 4.x, cells had css position:static, and changing them to
|
587 |
+
* relative to get heading brackets working broke the tooltip position
|
588 |
+
* calculation. In order to fix this, we patch the 4.x Tooltip._show
|
589 |
+
* method to temporarily reapply position:static while the tooltip
|
590 |
+
* position is calculated & the animation queued, before revertign to the
|
591 |
+
* css-appled position:relative.
|
592 |
+
* For notebook 5.x, cells are already position:relative, so the patch is
|
593 |
+
* unecessary.
|
594 |
+
*
|
595 |
+
* @return {Promise}
|
596 |
+
*/
|
597 |
+
function patch_Tooltip () {
|
598 |
+
if (Number(Jupyter.version[0]) >= 5) {
|
599 |
+
return Promise.resolve();
|
600 |
+
}
|
601 |
+
return new Promise(function (resolve, reject) {
|
602 |
+
requirejs(['notebook/js/tooltip'], function on_success (tooltip) {
|
603 |
+
console.debug(log_prefix, 'patching Tooltip.prototype');
|
604 |
+
|
605 |
+
var orig_tooltip__show = tooltip.Tooltip.prototype._show;
|
606 |
+
tooltip.Tooltip.prototype._show = function (reply) {
|
607 |
+
var $cell = $(this.code_mirror.getWrapperElement()).closest('.cell');
|
608 |
+
$cell.css('position', 'static');
|
609 |
+
var ret = orig_tooltip__show.apply(this, arguments);
|
610 |
+
$cell.css('position', '');
|
611 |
+
return ret;
|
612 |
+
};
|
613 |
+
|
614 |
+
resolve();
|
615 |
+
}, reject);
|
616 |
+
}).catch(function on_reject (reason) {
|
617 |
+
console.warn(log_prefix, 'error patching Tooltip.prototype:', reason);
|
618 |
+
});
|
619 |
+
}
|
620 |
+
|
621 |
+
/**
|
622 |
+
* Return a promise which resolves when the appropriate Jupyter actions
|
623 |
+
* have been patched correctly.
|
624 |
+
*
|
625 |
+
* We patch the up/down arrow actions to skip selecting cells which are
|
626 |
+
* hidden by a collapsed heading
|
627 |
+
*
|
628 |
+
* @return {Promise}
|
629 |
+
*/
|
630 |
+
function patch_actions () {
|
631 |
+
return new Promise(function (resolve, reject) {
|
632 |
+
requirejs(['notebook/js/tooltip'], function on_success (tooltip) {
|
633 |
+
console.debug(log_prefix, 'patching Jupyter up/down actions');
|
634 |
+
|
635 |
+
var kbm = Jupyter.keyboard_manager;
|
636 |
+
|
637 |
+
var action_up = kbm.actions.get("jupyter-notebook:select-previous-cell");
|
638 |
+
var orig_up_handler = action_up.handler;
|
639 |
+
action_up.handler = function (env) {
|
640 |
+
for (var index = env.notebook.get_selected_index() - 1; (index !== null) && (index >= 0); index--) {
|
641 |
+
if (env.notebook.get_cell(index).element.is(':visible')) {
|
642 |
+
env.notebook.select(index);
|
643 |
+
env.notebook.focus_cell();
|
644 |
+
return;
|
645 |
+
}
|
646 |
+
}
|
647 |
+
return orig_up_handler.apply(this, arguments);
|
648 |
+
};
|
649 |
+
|
650 |
+
var action_down = kbm.actions.get("jupyter-notebook:select-next-cell");
|
651 |
+
var orig_down_handler = action_down.handler;
|
652 |
+
action_down.handler = function (env) {
|
653 |
+
var ncells = env.notebook.ncells();
|
654 |
+
for (var index = env.notebook.get_selected_index() + 1; (index !== null) && (index < ncells); index++) {
|
655 |
+
if (env.notebook.get_cell(index).element.is(':visible')) {
|
656 |
+
env.notebook.select(index);
|
657 |
+
env.notebook.focus_cell();
|
658 |
+
return;
|
659 |
+
}
|
660 |
+
}
|
661 |
+
return orig_down_handler.apply(this, arguments);
|
662 |
+
};
|
663 |
+
|
664 |
+
resolve();
|
665 |
+
}, reject);
|
666 |
+
}).catch(function on_reject (reason) {
|
667 |
+
console.warn(log_prefix, 'error patching Jupyter up/down actions:', reason);
|
668 |
+
});
|
669 |
+
}
|
670 |
+
|
671 |
+
/**
|
672 |
+
* register actions to collapse and uncollapse the selected heading cell
|
673 |
+
*/
|
674 |
+
function register_new_actions () {
|
675 |
+
action_names.collapse = Jupyter.keyboard_manager.actions.register({
|
676 |
+
handler : function (env) {
|
677 |
+
var cell = env.notebook.get_selected_cell();
|
678 |
+
var is_h = is_heading(cell);
|
679 |
+
if (is_h && !_is_collapsed(cell)) {
|
680 |
+
toggle_heading(cell, true);
|
681 |
+
return;
|
682 |
+
}
|
683 |
+
var filter_func;
|
684 |
+
if (is_h) {
|
685 |
+
var lvl = get_cell_level(cell);
|
686 |
+
filter_func = function (c) { return get_cell_level(c) < lvl; };
|
687 |
+
}
|
688 |
+
cell = find_header_cell(cell, filter_func);
|
689 |
+
if (cell !== undefined) {
|
690 |
+
Jupyter.notebook.select(Jupyter.notebook.find_cell_index(cell));
|
691 |
+
cell.focus_cell();
|
692 |
+
}
|
693 |
+
},
|
694 |
+
help : "Collapse the selected heading cell's section",
|
695 |
+
icon : params.toggle_closed_icon,
|
696 |
+
help_index: 'c1'
|
697 |
+
},
|
698 |
+
'collapse_heading', mod_name
|
699 |
+
);
|
700 |
+
|
701 |
+
action_names.collapse_all = Jupyter.keyboard_manager.actions.register({
|
702 |
+
handler : function (env) {
|
703 |
+
env.notebook.get_cells().forEach(function (c, idx, arr) {
|
704 |
+
toggle_heading(c, true);
|
705 |
+
});
|
706 |
+
var cell = env.notebook.get_selected_cell();
|
707 |
+
if (cell.element.is(':hidden')) {
|
708 |
+
cell = find_header_cell(cell, function (c) { return c.element.is(':visible'); });
|
709 |
+
if (cell !== undefined) {
|
710 |
+
Jupyter.notebook.select(Jupyter.notebook.find_cell_index(cell));
|
711 |
+
cell.focus_cell();
|
712 |
+
}
|
713 |
+
}
|
714 |
+
},
|
715 |
+
help : "Collapse all heading cells' sections",
|
716 |
+
icon : params.toggle_closed_icon,
|
717 |
+
help_index: 'c2'
|
718 |
+
},
|
719 |
+
'collapse_all_headings', mod_name
|
720 |
+
);
|
721 |
+
|
722 |
+
action_names.uncollapse = Jupyter.keyboard_manager.actions.register({
|
723 |
+
handler : function (env) {
|
724 |
+
var cell = env.notebook.get_selected_cell();
|
725 |
+
if (is_heading(cell)) {
|
726 |
+
toggle_heading(cell, false);
|
727 |
+
}
|
728 |
+
else {
|
729 |
+
var ncells = env.notebook.ncells();
|
730 |
+
for (var ii = env.notebook.find_cell_index(cell); ii < ncells; ii++) {
|
731 |
+
cell = env.notebook.get_cell(ii);
|
732 |
+
if (is_heading(cell)) {
|
733 |
+
env.notebook.select(ii);
|
734 |
+
cell.focus_cell();
|
735 |
+
break;
|
736 |
+
}
|
737 |
+
}
|
738 |
+
}
|
739 |
+
},
|
740 |
+
help : "Un-collapse (expand) the selected heading cell's section",
|
741 |
+
icon : params.toggle_open_icon,
|
742 |
+
help_index: 'c3'
|
743 |
+
},
|
744 |
+
'uncollapse_heading', mod_name
|
745 |
+
);
|
746 |
+
|
747 |
+
action_names.uncollapse_all = Jupyter.keyboard_manager.actions.register({
|
748 |
+
handler : function (env) {
|
749 |
+
env.notebook.get_cells().forEach(function (c, idx, arr) {
|
750 |
+
toggle_heading(c, false);
|
751 |
+
});
|
752 |
+
env.notebook.get_selected_cell().focus_cell();
|
753 |
+
},
|
754 |
+
help : "Un-collapse (expand) all heading cells' sections",
|
755 |
+
icon : params.toggle_open_icon,
|
756 |
+
help_index: 'c4'
|
757 |
+
},
|
758 |
+
'uncollapse_all_headings', mod_name
|
759 |
+
);
|
760 |
+
|
761 |
+
action_names.toggle = Jupyter.keyboard_manager.actions.register ({
|
762 |
+
handler: function () {
|
763 |
+
var heading_cell = find_header_cell(Jupyter.notebook.get_selected_cell(), function (cell) {
|
764 |
+
return cell.element.is(':visible') && !_is_collapsed(cell);
|
765 |
+
});
|
766 |
+
if (is_heading(heading_cell)) {
|
767 |
+
toggle_heading(heading_cell, true);
|
768 |
+
Jupyter.notebook.select(Jupyter.notebook.find_cell_index(heading_cell));
|
769 |
+
}
|
770 |
+
},
|
771 |
+
help : "Toggle closest heading's collapsed status",
|
772 |
+
icon : 'fa-angle-double-up',
|
773 |
+
},
|
774 |
+
'toggle_collapse_heading', mod_name
|
775 |
+
);
|
776 |
+
|
777 |
+
action_names.toggle_all = Jupyter.keyboard_manager.actions.register ({
|
778 |
+
handler: function () {
|
779 |
+
var cells = Jupyter.notebook.get_cells();
|
780 |
+
for (var ii = 0; ii < cells.length; ii++) {
|
781 |
+
if (is_heading(cells[ii])) {
|
782 |
+
Jupyter.keyboard_manager.actions.call(action_names[
|
783 |
+
is_collapsed_heading(cells[ii]) ? 'uncollapse_all' : 'collapse_all']);
|
784 |
+
return;
|
785 |
+
}
|
786 |
+
}
|
787 |
+
},
|
788 |
+
help : 'Collapse/uncollapse all headings based on the status of the first',
|
789 |
+
icon : 'fa-angle-double-up',
|
790 |
+
},
|
791 |
+
'toggle_collapse_all_headings', mod_name
|
792 |
+
);
|
793 |
+
|
794 |
+
action_names.select = Jupyter.keyboard_manager.actions.register({
|
795 |
+
handler : function (env) {
|
796 |
+
var cell = env.notebook.get_selected_cell();
|
797 |
+
if (is_heading(cell)) {
|
798 |
+
select_heading_section(cell, true);
|
799 |
+
}
|
800 |
+
},
|
801 |
+
help : "Select all cells in the selected heading cell's section",
|
802 |
+
help_index: 'c3'
|
803 |
+
},
|
804 |
+
'select_heading_section', mod_name
|
805 |
+
);
|
806 |
+
|
807 |
+
action_names.insert_above = Jupyter.keyboard_manager.actions.register({
|
808 |
+
handler : function (env) { insert_heading_cell(true); },
|
809 |
+
help : "Insert a heading cell above the selected cell",
|
810 |
+
help_index: 'c4',
|
811 |
+
icon: 'fa-caret-up'
|
812 |
+
},
|
813 |
+
'insert_heading_above', mod_name
|
814 |
+
);
|
815 |
+
|
816 |
+
action_names.insert_below = Jupyter.keyboard_manager.actions.register({
|
817 |
+
handler : function (env) { insert_heading_cell(false); },
|
818 |
+
help : "Insert a heading cell below the selected cell's section",
|
819 |
+
help_index: 'c5',
|
820 |
+
icon: 'fa-caret-down'
|
821 |
+
},
|
822 |
+
'insert_heading_below', mod_name
|
823 |
+
);
|
824 |
+
}
|
825 |
+
|
826 |
+
function imitate_hash_click ($element) {
|
827 |
+
var site = $('#site');
|
828 |
+
var adjust = $element.offset().top - site.offset().top;
|
829 |
+
site.animate({scrollTop: site.scrollTop() + adjust});
|
830 |
+
}
|
831 |
+
|
832 |
+
/**
|
833 |
+
* Insert a new heading cell either above or below the current section.
|
834 |
+
* only works in a live notebook.
|
835 |
+
*/
|
836 |
+
function insert_heading_cell (above) {
|
837 |
+
var selected_cell = Jupyter.notebook.get_selected_cell();
|
838 |
+
var ref_cell = find_header_cell(selected_cell) || selected_cell;
|
839 |
+
var level = get_cell_level(ref_cell);
|
840 |
+
level = (level == 7) ? 1 : level; // default to biggest level (1)
|
841 |
+
if (above) {
|
842 |
+
// if above, insert just above selected cell, but keep ref_cell's level
|
843 |
+
ref_cell = selected_cell;
|
844 |
+
}
|
845 |
+
var index = ref_cell.element.index();
|
846 |
+
if (!above) {
|
847 |
+
// below requires special handling, as we really want to put it
|
848 |
+
// below the currently selected heading's *content*
|
849 |
+
var cells = _get_cells();
|
850 |
+
for (index=index + 1; index < cells.length; index++) {
|
851 |
+
if (get_cell_level(cells[index]) <= level) {
|
852 |
+
break;
|
853 |
+
}
|
854 |
+
}
|
855 |
+
// if we make it here, index will be == cells.length, which is ok
|
856 |
+
// as it gets the new cell inserted at the bottom of the notebook
|
857 |
+
}
|
858 |
+
// we don't want our newly-inserted cell to trigger opening of headings
|
859 |
+
var cached_select_reveals = select_reveals;
|
860 |
+
select_reveals = false;
|
861 |
+
var new_cell = Jupyter.notebook.insert_cell_above('markdown', index);
|
862 |
+
var new_text = 'New heading';
|
863 |
+
new_cell.set_text(new_text);
|
864 |
+
new_cell.set_heading_level(level);
|
865 |
+
new_cell.code_mirror.setSelection({line:0, ch: level + 1}, {line:0, ch: level + 1 + new_text.length});
|
866 |
+
Jupyter.notebook.select(index, true);
|
867 |
+
// restore cached setting
|
868 |
+
select_reveals = cached_select_reveals;
|
869 |
+
Jupyter.notebook.focus_cell();
|
870 |
+
Jupyter.notebook.edit_mode();
|
871 |
+
}
|
872 |
+
|
873 |
+
function refresh_all_headings () {
|
874 |
+
var cells = _get_cells();
|
875 |
+
for (var ii=0; ii < cells.length; ii++) {
|
876 |
+
update_heading_cell_status(cells[ii]);
|
877 |
+
}
|
878 |
+
update_collapsed_headings();
|
879 |
+
}
|
880 |
+
|
881 |
+
function set_collapsible_headings_options (options) {
|
882 |
+
// options may be undefined here, but it's still handled ok by $.extend
|
883 |
+
$.extend(true, params, options);
|
884 |
+
// bind/unbind toc-collapse handler
|
885 |
+
events[params.collapse_to_match_toc ? 'on' : 'off']('collapse.Toc uncollapse.Toc', callback_toc_collapse);
|
886 |
+
// add css for indents
|
887 |
+
if (params.indent_px !== 0) {
|
888 |
+
var lines = [];
|
889 |
+
for (var hh = 1; hh <= 6; hh++) {
|
890 |
+
lines.push(
|
891 |
+
'.collapsible_headings_toggle .h' + hh +
|
892 |
+
' { margin-right: ' + ((6 - hh) * params.indent_px) + 'px; }'
|
893 |
+
);
|
894 |
+
}
|
895 |
+
$('<style id="collapsible_headings_indent_css"/>')
|
896 |
+
.html(lines.join('\n'))
|
897 |
+
.appendTo('head');
|
898 |
+
}
|
899 |
+
return params;
|
900 |
+
}
|
901 |
+
|
902 |
+
function add_buttons_and_shortcuts () {
|
903 |
+
// (Maybe) add buttons to the toolbar
|
904 |
+
if (params.add_button) {
|
905 |
+
Jupyter.toolbar.add_buttons_group([action_names.toggle]);
|
906 |
+
}
|
907 |
+
if (params.add_all_cells_button) {
|
908 |
+
Jupyter.toolbar.add_buttons_group([action_names.toggle_all]);
|
909 |
+
}
|
910 |
+
if (params.add_insert_header_buttons) {
|
911 |
+
Jupyter.toolbar.add_buttons_group([
|
912 |
+
action_names.insert_above, action_names.insert_below
|
913 |
+
],'insert_heading_cell_btns');
|
914 |
+
}
|
915 |
+
// add hashes
|
916 |
+
$('#insert_heading_cell_btns .btn').prepend('# ');
|
917 |
+
|
918 |
+
// (Maybe) register keyboard shortcuts
|
919 |
+
if (params.use_shortcuts) {
|
920 |
+
var cmd_shrts = Jupyter.keyboard_manager.command_shortcuts;
|
921 |
+
for (var act in action_names) {
|
922 |
+
if (action_names.hasOwnProperty(act) && params.shortcuts[act]) {
|
923 |
+
cmd_shrts.add_shortcut(params.shortcuts[act], action_names[act]);
|
924 |
+
}
|
925 |
+
}
|
926 |
+
}
|
927 |
+
}
|
928 |
+
|
929 |
+
var callback_toc_collapse = function (evt, data) {
|
930 |
+
// use trigger_event false to avoid re-triggering toc2
|
931 |
+
toggle_heading(data.cell, evt.type.indexOf('un') < 0, false);
|
932 |
+
}
|
933 |
+
|
934 |
+
/**
|
935 |
+
* Return a promise which resolves once event handlers have been bound
|
936 |
+
*
|
937 |
+
* @return {Promise}
|
938 |
+
*/
|
939 |
+
function bind_events () {
|
940 |
+
|
941 |
+
// Callbacks bound to the create.Cell event can execute before the cell
|
942 |
+
// data has been loaded from JSON.
|
943 |
+
// So, we rely on rendered.MarkdownCell event to catch headings from
|
944 |
+
// JSON, and the only reason we use create.Cell is to update brackets
|
945 |
+
function callback_create_cell (evt, data) {
|
946 |
+
if (params.show_section_brackets) {
|
947 |
+
update_collapsed_headings();
|
948 |
+
}
|
949 |
+
}
|
950 |
+
|
951 |
+
function callback_delete_cell(evt, data) {
|
952 |
+
update_collapsed_headings();
|
953 |
+
}
|
954 |
+
|
955 |
+
function callback_markdown_rendered (evt, data) {
|
956 |
+
update_heading_cell_status(data.cell);
|
957 |
+
// we update all headings to avoid pasted headings ending up hidden
|
958 |
+
// by other pre-existing collapsed headings - see
|
959 |
+
// https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/1082
|
960 |
+
// for details
|
961 |
+
update_collapsed_headings();
|
962 |
+
}
|
963 |
+
|
964 |
+
return new Promise (function (resolve, reject) {
|
965 |
+
requirejs(['base/js/events'], function on_success (events) {
|
966 |
+
|
967 |
+
// ensure events are detached while notebook loads, in order to
|
968 |
+
// speed up loading (otherwise headings are updated for every
|
969 |
+
// new cell in the notebook), then reattached when load is
|
970 |
+
// complete
|
971 |
+
function events_attach () {
|
972 |
+
refresh_all_headings();
|
973 |
+
events.on('create.Cell', callback_create_cell);
|
974 |
+
events.on('delete.Cell', callback_delete_cell);
|
975 |
+
events.on('rendered.MarkdownCell', callback_markdown_rendered);
|
976 |
+
}
|
977 |
+
function events_detach () {
|
978 |
+
events.off('create.Cell', callback_create_cell);
|
979 |
+
events.off('delete.Cell', callback_delete_cell);
|
980 |
+
events.off('rendered.MarkdownCell', callback_markdown_rendered);
|
981 |
+
}
|
982 |
+
|
983 |
+
if (Jupyter.notebook._fully_loaded) {
|
984 |
+
events_attach();
|
985 |
+
}
|
986 |
+
events.on('notebook_loaded.Notebook', events_attach);
|
987 |
+
events.on('notebook_loading.Notebook', events_detach);
|
988 |
+
|
989 |
+
resolve();
|
990 |
+
}, reject);
|
991 |
+
}).catch(function on_reject (reason) {
|
992 |
+
console.warn(log_prefix, 'error binding events:', reason);
|
993 |
+
});
|
994 |
+
}
|
995 |
+
|
996 |
+
/**
|
997 |
+
* Return a menu list item with a link that calls the specified action
|
998 |
+
* name.
|
999 |
+
*
|
1000 |
+
* @param {String} action_name the name of the action which the menu item
|
1001 |
+
* should call
|
1002 |
+
* @param {String} menu_item_html the html to use as the link's content
|
1003 |
+
* @return {jQuery}
|
1004 |
+
*/
|
1005 |
+
function make_action_menu_item (action_name, menu_item_html) {
|
1006 |
+
var act = Jupyter.menubar.actions.get(action_name);
|
1007 |
+
var menu_item = $('<li/>');
|
1008 |
+
$('<a/>')
|
1009 |
+
.html(menu_item_html)
|
1010 |
+
.attr({'title' : act.help, 'href' : '#'})
|
1011 |
+
.on('click', function (evt) {
|
1012 |
+
Jupyter.menubar.actions.call(action_name, evt);
|
1013 |
+
})
|
1014 |
+
.appendTo(menu_item);
|
1015 |
+
return menu_item;
|
1016 |
+
}
|
1017 |
+
|
1018 |
+
/**
|
1019 |
+
* Add any new items to the notebook menu
|
1020 |
+
*/
|
1021 |
+
function insert_menu_items () {
|
1022 |
+
$('#insert_menu')
|
1023 |
+
.append('<li class="divider"/>')
|
1024 |
+
.append(make_action_menu_item(action_names.insert_above, 'Insert Heading Above'))
|
1025 |
+
.append(make_action_menu_item(action_names.insert_below, 'Insert Heading Below'));
|
1026 |
+
}
|
1027 |
+
|
1028 |
+
/**
|
1029 |
+
* Initialize the extension.
|
1030 |
+
*/
|
1031 |
+
function load_jupyter_extension () {
|
1032 |
+
// Load css first
|
1033 |
+
$('<link/>')
|
1034 |
+
.attr({
|
1035 |
+
id: 'collapsible_headings_css',
|
1036 |
+
rel: 'stylesheet',
|
1037 |
+
type: 'text/css',
|
1038 |
+
href: requirejs.toUrl('./main.css')
|
1039 |
+
})
|
1040 |
+
.appendTo('head');
|
1041 |
+
|
1042 |
+
// ensure Jupyter module is defined before proceeding further
|
1043 |
+
new Promise(function (resolve, reject) {
|
1044 |
+
requirejs(['base/js/namespace'], function (Jupyter_mod) {
|
1045 |
+
live_notebook = true;
|
1046 |
+
Jupyter = Jupyter_mod;
|
1047 |
+
resolve(Jupyter);
|
1048 |
+
}, reject);
|
1049 |
+
})
|
1050 |
+
|
1051 |
+
// load config & update params
|
1052 |
+
.then(function (Jupyter) {
|
1053 |
+
return Jupyter.notebook.config.loaded.catch(function on_err (reason) {
|
1054 |
+
console.warn(log_prefix, 'error loading config:', reason);
|
1055 |
+
}).then(function () {
|
1056 |
+
// may be undefined, but that's ok.
|
1057 |
+
return Jupyter.notebook.config.data.collapsible_headings;
|
1058 |
+
});
|
1059 |
+
})
|
1060 |
+
// set values using resolution val of previous .then
|
1061 |
+
.then(set_collapsible_headings_options)
|
1062 |
+
|
1063 |
+
// apply all promisory things in arbitrary order
|
1064 |
+
.then(patch_actions)
|
1065 |
+
.then(patch_Notebook)
|
1066 |
+
.then(patch_TextCell)
|
1067 |
+
.then(patch_Tooltip)
|
1068 |
+
.then(bind_events)
|
1069 |
+
// finally add user-interaction stuff
|
1070 |
+
.then(function () {
|
1071 |
+
register_new_actions();
|
1072 |
+
insert_menu_items();
|
1073 |
+
add_buttons_and_shortcuts();
|
1074 |
+
})
|
1075 |
+
.catch(function on_reject (reason) {
|
1076 |
+
console.error(log_prefix, 'error:', reason);
|
1077 |
+
});
|
1078 |
+
}
|
1079 |
+
|
1080 |
+
/**
|
1081 |
+
* Export things
|
1082 |
+
*/
|
1083 |
+
return {
|
1084 |
+
get_cell_level : get_cell_level,
|
1085 |
+
reveal_cell_by_index : reveal_cell_by_index,
|
1086 |
+
update_collapsed_headings : update_collapsed_headings,
|
1087 |
+
set_collapsible_headings_options : set_collapsible_headings_options,
|
1088 |
+
refresh_all_headings: refresh_all_headings,
|
1089 |
+
load_jupyter_extension : load_jupyter_extension,
|
1090 |
+
load_ipython_extension : load_jupyter_extension
|
1091 |
+
};
|
1092 |
+
});
|
.local/share/jupyter/nbextensions/collapsible_headings/readme.md
ADDED
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Collapsible Headings
|
2 |
+
====================
|
3 |
+
|
4 |
+
Allows notebook to have collapsible sections, separated by headings.
|
5 |
+
|
6 |
+
Any markdown heading cell (that is, one which begins with 1-6 `#` characters),
|
7 |
+
becomes collapsible once rendered.
|
8 |
+
|
9 |
+
The collapsed/expanded status of the headings is stored in the cell metadata,
|
10 |
+
and reloaded on notebook load.
|
11 |
+
|
12 |
+
![screenshot](screenshot.png)
|
13 |
+
|
14 |
+
|
15 |
+
Options
|
16 |
+
-------
|
17 |
+
|
18 |
+
The extension offers a few options for how to display and toggle the collapsed
|
19 |
+
status of headings, each of which can be enabled, disabled or configured from
|
20 |
+
the nbextensions config page:
|
21 |
+
|
22 |
+
* Command-mode keyboard shortcuts, (enabled by default, and set to left and
|
23 |
+
right arrow keys to collapse/expand sections, or go to the previous/next
|
24 |
+
heading, plus shift-right to select a heading cell's section, shift-a/b to
|
25 |
+
insert a heading above/below the current cell, ctrl-shift-left and
|
26 |
+
ctrl-shift-right to collapse/uncollapse all headings).
|
27 |
+
Bindings are also configurable from the config page
|
28 |
+
* A toggle control in the input prompt area of each heading cell (as seen in
|
29 |
+
the screenshot below, enabled by default)
|
30 |
+
* Configurable icons and icon color for the toggle control (by default, grey
|
31 |
+
right/down carets are used)
|
32 |
+
* The option to make the toggle control into a button (by default it's just a
|
33 |
+
clickable icon)
|
34 |
+
* Mathematica-style grouping brackets around each collapsible section on the
|
35 |
+
right of the notebook. Single-clicking a bracket will select all cells in the
|
36 |
+
section (hold shift to extend existing selection), while double-clicking the
|
37 |
+
bracket toggles the section's collpased/expanded status (disabled by default)
|
38 |
+
* Bracket width is configurable, defaults to 10 (px)
|
39 |
+
* A gray bracketed ellipsis added to the end of each collapsed heading,
|
40 |
+
indicating hidden content (disabled by default)
|
41 |
+
* A toolbar button to collapse the nearest heading to the curently selected
|
42 |
+
cell (disabled by default)
|
43 |
+
* Collapse/uncollapse sections when ToC2 sections are collapsed/uncollapsed
|
44 |
+
* A toolbar button to collapse/uncollapse all headings (disabled by default)
|
45 |
+
* Shift more-significant headings' collapse controls further to the left
|
46 |
+
|
47 |
+
|
48 |
+
css
|
49 |
+
---
|
50 |
+
|
51 |
+
The extension add the css class `collapsible_headings_collapsed` to each
|
52 |
+
collapsed heading cell, which you could use for custom css rules, such as
|
53 |
+
adding a bottom border to collapsed headings, to visually distinguish them a
|
54 |
+
bit more.
|
55 |
+
|
56 |
+
The toggle controls' icons currently spin by 360 degrees when the heading gets
|
57 |
+
collapsed or uncollapsed, via a css transition property (not in IE).
|
58 |
+
If this annoys you,
|
59 |
+
you could turn it off using the following rule in your `custom.css`:
|
60 |
+
|
61 |
+
```css
|
62 |
+
.cell .collapsible_headings_toggle .fa {
|
63 |
+
transition: transform 0s;
|
64 |
+
}
|
65 |
+
```
|
66 |
+
|
67 |
+
|
68 |
+
Internals
|
69 |
+
---------
|
70 |
+
|
71 |
+
Heading cells which are collapsed have a value set in the cell metadata, so
|
72 |
+
that
|
73 |
+
|
74 |
+
```javascript
|
75 |
+
cell.metadata.heading_collapsed = true
|
76 |
+
```
|
77 |
+
|
78 |
+
The extension patches some Jupyter methods:
|
79 |
+
* `TextCell.prototype.execute` is patched to add/remove the toggle buttons,
|
80 |
+
as well as update the visibility of any cells below the new one.
|
81 |
+
* `Notebook.prototype.select` is patched to make sure any collapsed headings
|
82 |
+
which would be hiding the new selection get uncollapsed (expanded).
|
83 |
+
* `Notebook.prototype.undelete` and `Notebook.prototype.delete_cells` are
|
84 |
+
patched to trigger an update of which cells should be visible or hidden.
|
85 |
+
* `Tooltip._show` is patched to toggle the `div.cell { position:relative; }`
|
86 |
+
css rule of while the tooltip displays, as otherwise it interferes with the
|
87 |
+
tooltip's position-determining logic. Since this method is not part of the
|
88 |
+
public API (leading underscore), this may break in future, but it should
|
89 |
+
degrade in a non-catastrophic manner, with the result that the tooltip will
|
90 |
+
appear at the top of the notebook document, rather than where the cursor is
|
91 |
+
currently.
|
92 |
+
|
93 |
+
The extension also patches two existing Jupyter actions: those triggered in
|
94 |
+
command mode by the up/down arrow keys. Ordinarily, these select the cell
|
95 |
+
above/below the current selection. Once patched by `collapsible_headings`, they
|
96 |
+
have the same behaviour, but skip over any cells which have been hidden (by a
|
97 |
+
collapsed heading, or, in fact, by any other mechanism).
|
98 |
+
|
99 |
+
Finally, `collapsible_headings` registers two new actions, namely
|
100 |
+
`collapsible_headings:collapse_heading` and
|
101 |
+
`collapsible_headings:uncollapse_heading`, which are used by the keyboard
|
102 |
+
shortcuts (if used), and can be called as with any other action.
|
103 |
+
|
104 |
+
The previously-provided preprocessor has been retired in favour of an exporter
|
105 |
+
which embeds functionality correctly. See the [exporting section] for details.
|
106 |
+
If you have questions, comments, or would like alterations (particularly for
|
107 |
+
nbconvert support, of which I don't have much experience), get in touch
|
108 |
+
([@jcb91](https://github.com/jcb91))
|
109 |
+
and I'll see what I can do :)
|
110 |
+
|
111 |
+
|
112 |
+
Exporting
|
113 |
+
---------
|
114 |
+
|
115 |
+
It is possible to export most of the features of collapsible_headings to html.
|
116 |
+
The process is to embed the relevant css & js files into the html output, with
|
117 |
+
suitable functionality for a non-live notebook.
|
118 |
+
|
119 |
+
This is accomplished through use of the `ExporterInliner` class and its
|
120 |
+
associated `inliner.tpl` template, provided as part of the
|
121 |
+
`jupyter_contrib_nbextensions.nbconvert_support` module.
|
122 |
+
To convert to html embedding collapsible headings functionality, use `html_ch`
|
123 |
+
exporter, with a command like
|
124 |
+
|
125 |
+
jupyter nbconvert --to html_ch FILE.ipynb
|
.local/share/jupyter/nbextensions/comment-uncomment/icon.png
ADDED
.local/share/jupyter/nbextensions/comment-uncomment/readme.md
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Comment/Uncomment
|
2 |
+
=================
|
3 |
+
|
4 |
+
Adds a new configurable hotkey binding to toggle comments on/off.
|
5 |
+
|
6 |
+
Options
|
7 |
+
-------
|
8 |
+
|
9 |
+
**comment_uncomment_keybinding**: keybinding for toggling comments (default: Alt-c)
|
10 |
+
|
11 |
+
***comment_uncomment_indent***: add comment at current indent level instead of at beginning of line
|
.local/share/jupyter/nbextensions/contrib_nbextensions_help_item/contrib_nbextensions_help_item.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Type: Jupyter Notebook Extension
|
2 |
+
Name: contrib_nbextensions_help_item
|
3 |
+
Description: The contrib_nbextensions_help_item is a tiny extension that just adds an item in the notebook's help menu, pointing to the jupyter_contrib_nbextensions at readthedocs.
|
4 |
+
Link: README.md
|
5 |
+
Icon: icon.png
|
6 |
+
Main: main.js
|
7 |
+
Compatibility: 4.x, 5.x
|
.local/share/jupyter/nbextensions/datestamper/icon.png
ADDED
.local/share/jupyter/nbextensions/equation-numbering/info.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Type: IPython Notebook Extension
|
2 |
+
Name: Equation Auto Numbering
|
3 |
+
Description: This extension enables equation autonumbering and resetting the equation count.
|
4 |
+
Link: readme.md
|
5 |
+
Icon: icon.png
|
6 |
+
Main: main.js
|
7 |
+
Compatibility: 4.x, 5.x
|
.local/share/jupyter/nbextensions/equation-numbering/main.js
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Copyright (c) IPython-Contrib Team.
|
2 |
+
// Distributed under the terms of the Modified BSD License.
|
3 |
+
|
4 |
+
define([
|
5 |
+
'base/js/namespace',
|
6 |
+
'jquery',
|
7 |
+
'require',
|
8 |
+
'notebook/js/textcell',
|
9 |
+
'base/js/utils',
|
10 |
+
], function(Jupyter, $, requirejs, textcell, utils) {
|
11 |
+
"use strict";
|
12 |
+
|
13 |
+
var MathJax = window.MathJax;
|
14 |
+
|
15 |
+
var load_ipython_extension = function() {
|
16 |
+
var btn_grp = Jupyter.toolbar.add_buttons_group([
|
17 |
+
Jupyter.keyboard_manager.actions.register ({
|
18 |
+
help : 'Reset equation numbering',
|
19 |
+
icon : 'fa-sort-numeric-asc',
|
20 |
+
handler: function () {
|
21 |
+
MathJax.Hub.Queue(
|
22 |
+
["resetEquationNumbers", MathJax.InputJax.TeX],
|
23 |
+
["PreProcess", MathJax.Hub],
|
24 |
+
["Reprocess", MathJax.Hub]
|
25 |
+
);
|
26 |
+
$('#reset_numbering').blur();
|
27 |
+
}
|
28 |
+
}, 'reset-numbering', 'equation_numbering')
|
29 |
+
]);
|
30 |
+
$(btn_grp).find('.btn').attr('id', 'reset_numbering');
|
31 |
+
MathJax.Hub.Config({
|
32 |
+
TeX: { equationNumbers: { autoNumber: "AMS" } }
|
33 |
+
});
|
34 |
+
};
|
35 |
+
|
36 |
+
return {
|
37 |
+
load_ipython_extension : load_ipython_extension
|
38 |
+
};
|
39 |
+
});
|
.local/share/jupyter/nbextensions/execute_time/ExecuteTime.css
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.timing_area {
|
2 |
+
padding: 0 5px;
|
3 |
+
border: none;
|
4 |
+
border-top: 1px solid #CFCFCF;
|
5 |
+
font-size: 80%;
|
6 |
+
}
|
.local/share/jupyter/nbextensions/execute_time/ExecuteTime.js
ADDED
@@ -0,0 +1,349 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
// Copyright (C) 2014 Jean-Christophe Jaskula
|
3 |
+
// 2015 joshuacookebarnes@gmail.com
|
4 |
+
//
|
5 |
+
// Distributed under the terms of the BSD License.
|
6 |
+
// ---------------------------------------------------------------------------
|
7 |
+
//
|
8 |
+
// Execution timings:
|
9 |
+
// display when a cell was last executed, and how long it took to run
|
10 |
+
// A double click on the timing box makes it disappear
|
11 |
+
|
12 |
+
define([
|
13 |
+
'require',
|
14 |
+
'jquery',
|
15 |
+
'moment',
|
16 |
+
'base/js/namespace',
|
17 |
+
'base/js/events',
|
18 |
+
'notebook/js/codecell'
|
19 |
+
], function (
|
20 |
+
requirejs,
|
21 |
+
$,
|
22 |
+
moment,
|
23 |
+
Jupyter,
|
24 |
+
events,
|
25 |
+
codecell
|
26 |
+
) {
|
27 |
+
'use strict';
|
28 |
+
|
29 |
+
var mod_name = 'ExecuteTime';
|
30 |
+
var log_prefix = '[' + mod_name + ']';
|
31 |
+
|
32 |
+
var CodeCell = codecell.CodeCell;
|
33 |
+
|
34 |
+
// defaults, overridden by server's config
|
35 |
+
var options = {
|
36 |
+
clear_timings_on_clear_output: false,
|
37 |
+
clear_timings_on_kernel_restart: false,
|
38 |
+
default_kernel_to_utc: true,
|
39 |
+
display_absolute_format: 'HH:mm:ss YYYY-MM-DD',
|
40 |
+
display_absolute_timings: true,
|
41 |
+
display_in_utc: false,
|
42 |
+
display_right_aligned: false,
|
43 |
+
highlight: {
|
44 |
+
use: true,
|
45 |
+
color: '#00bb00',
|
46 |
+
},
|
47 |
+
relative_timing_update_period: 10,
|
48 |
+
template: {
|
49 |
+
executed: 'executed in ${duration}, finished ${end_time}',
|
50 |
+
queued: 'execution queued ${start_time}',
|
51 |
+
},
|
52 |
+
};
|
53 |
+
|
54 |
+
function patch_CodeCell_get_callbacks () {
|
55 |
+
console.log(log_prefix, 'patching CodeCell.prototype.get_callbacks');
|
56 |
+
var old_get_callbacks = CodeCell.prototype.get_callbacks;
|
57 |
+
CodeCell.prototype.get_callbacks = function () {
|
58 |
+
var callbacks = old_get_callbacks.apply(this, arguments);
|
59 |
+
|
60 |
+
var cell = this;
|
61 |
+
var prev_reply_callback = callbacks.shell.reply;
|
62 |
+
callbacks.shell.reply = function (msg) {
|
63 |
+
if (msg.msg_type === 'execute_reply') {
|
64 |
+
$.extend(true, cell.metadata, {
|
65 |
+
ExecuteTime: {
|
66 |
+
start_time: add_utc_offset(msg.metadata.started),
|
67 |
+
end_time: add_utc_offset(msg.header.date),
|
68 |
+
}
|
69 |
+
});
|
70 |
+
var timing_area = update_timing_area(cell);
|
71 |
+
if ($.ui !== undefined && options.highlight.use) {
|
72 |
+
timing_area.stop(true, true).show(0).effect('highlight', {color: options.highlight.color});
|
73 |
+
}
|
74 |
+
}
|
75 |
+
else {
|
76 |
+
console.log('msg_type', msg.msg_type);
|
77 |
+
}
|
78 |
+
return prev_reply_callback(msg);
|
79 |
+
};
|
80 |
+
return callbacks;
|
81 |
+
};
|
82 |
+
}
|
83 |
+
|
84 |
+
function patch_CodeCell_clear_output () {
|
85 |
+
console.log(log_prefix, 'Patching CodeCell.prototype.clear_output to clear timings also.');
|
86 |
+
var orig_clear_output = CodeCell.prototype.clear_output;
|
87 |
+
CodeCell.prototype.clear_output = function () {
|
88 |
+
var ret = orig_clear_output.apply(this, arguments);
|
89 |
+
clear_timing_data([this]);
|
90 |
+
return ret;
|
91 |
+
};
|
92 |
+
}
|
93 |
+
|
94 |
+
function toggle_timing_display (cells, vis) {
|
95 |
+
for (var i = 0; i < cells.length; i++) {
|
96 |
+
var cell = cells[i];
|
97 |
+
if (cell instanceof CodeCell) {
|
98 |
+
var ce = cell.element;
|
99 |
+
var timing_area = ce.find('.timing_area');
|
100 |
+
if (timing_area.length > 0) {
|
101 |
+
if (vis === undefined) {
|
102 |
+
vis = !timing_area.is(':visible');
|
103 |
+
}
|
104 |
+
timing_area.toggle(vis);
|
105 |
+
}
|
106 |
+
}
|
107 |
+
}
|
108 |
+
return vis;
|
109 |
+
}
|
110 |
+
|
111 |
+
function clear_timing_data (cells) {
|
112 |
+
cells.forEach(function (cell, idx, arr) {
|
113 |
+
delete cell.metadata.ExecuteTime;
|
114 |
+
cell.element.find('.timing_area').remove();
|
115 |
+
});
|
116 |
+
events.trigger('set_dirty.Notebook', {value: true});
|
117 |
+
}
|
118 |
+
|
119 |
+
function clear_timing_data_all () {
|
120 |
+
console.log(log_prefix, 'Clearing all timing data');
|
121 |
+
clear_timing_data(Jupyter.notebook.get_cells());
|
122 |
+
}
|
123 |
+
|
124 |
+
function create_menu () {
|
125 |
+
var timings_menu_item = $('<li/>')
|
126 |
+
.addClass('dropdown-submenu')
|
127 |
+
.append(
|
128 |
+
$('<a href="#">')
|
129 |
+
.text('Execution Timings')
|
130 |
+
.on('click', function (evt) { evt.preventDefault(); })
|
131 |
+
)
|
132 |
+
.appendTo($('#cell_menu'));
|
133 |
+
|
134 |
+
var timings_submenu = $('<ul/>')
|
135 |
+
.addClass('dropdown-menu')
|
136 |
+
.appendTo(timings_menu_item);
|
137 |
+
|
138 |
+
$('<li/>')
|
139 |
+
.attr('title', 'Toggle the timing box for the selected cell(s)')
|
140 |
+
.append(
|
141 |
+
$('<a href="#">')
|
142 |
+
.text('Toggle visibility (selected)')
|
143 |
+
.on('click', function (evt) {
|
144 |
+
evt.preventDefault();
|
145 |
+
toggle_timing_display(Jupyter.notebook.get_selected_cells());
|
146 |
+
})
|
147 |
+
)
|
148 |
+
.appendTo(timings_submenu);
|
149 |
+
|
150 |
+
$('<li/>')
|
151 |
+
.attr('title', 'Toggle the timing box for all cells')
|
152 |
+
.append(
|
153 |
+
$('<a href="#">')
|
154 |
+
.text('Toggle visibility (all)')
|
155 |
+
.on('click', function (evt) {
|
156 |
+
evt.preventDefault();
|
157 |
+
toggle_timing_display(Jupyter.notebook.get_cells());
|
158 |
+
})
|
159 |
+
)
|
160 |
+
.appendTo(timings_submenu);
|
161 |
+
|
162 |
+
$('<li/>')
|
163 |
+
.attr('title', 'Clear the selected cell(s) timing data')
|
164 |
+
.append(
|
165 |
+
$('<a href="#">')
|
166 |
+
.text('Clear (selected)')
|
167 |
+
.on('click', function (evt) {
|
168 |
+
evt.preventDefault();
|
169 |
+
clear_timing_data(Jupyter.notebook.get_selected_cells());
|
170 |
+
})
|
171 |
+
)
|
172 |
+
.appendTo(timings_submenu);
|
173 |
+
|
174 |
+
$('<li/>')
|
175 |
+
.attr('title', 'Clear the timing data from all cells')
|
176 |
+
.append(
|
177 |
+
$('<a href="#">')
|
178 |
+
.text('Clear (all)')
|
179 |
+
.on('click', function (evt) {
|
180 |
+
evt.preventDefault();
|
181 |
+
clear_timing_data(Jupyter.notebook.get_cells());
|
182 |
+
})
|
183 |
+
)
|
184 |
+
.appendTo(timings_submenu);
|
185 |
+
}
|
186 |
+
|
187 |
+
function excute_codecell_callback (evt, data) {
|
188 |
+
var cell = data.cell;
|
189 |
+
cell.metadata.ExecuteTime = {start_time: moment().toISOString()};
|
190 |
+
|
191 |
+
update_timing_area(cell);
|
192 |
+
}
|
193 |
+
|
194 |
+
function humanized_duration (duration_ms, item_count) {
|
195 |
+
if (duration_ms < 1000) { // < 1s, show ms directly
|
196 |
+
return Math.round(duration_ms) + 'ms';
|
197 |
+
}
|
198 |
+
|
199 |
+
var humanized = '';
|
200 |
+
|
201 |
+
var days = Math.floor(duration_ms / 86400000);
|
202 |
+
if (days) {
|
203 |
+
humanized += days + 'd ';
|
204 |
+
}
|
205 |
+
duration_ms %= 86400000;
|
206 |
+
|
207 |
+
var hours = Math.floor(duration_ms / 3600000);
|
208 |
+
if (days || hours) {
|
209 |
+
humanized += hours + 'h ';
|
210 |
+
}
|
211 |
+
duration_ms %= 3600000;
|
212 |
+
|
213 |
+
var mins = Math.floor(duration_ms / 60000);
|
214 |
+
if (days || hours || mins) {
|
215 |
+
humanized += mins + 'm';
|
216 |
+
}
|
217 |
+
duration_ms %= 60000;
|
218 |
+
|
219 |
+
var secs = duration_ms / 1000; // don't round!
|
220 |
+
if (!days) {
|
221 |
+
var decimals = (hours || mins > 1) ? 0 : (secs > 10 ? 1 : 2);
|
222 |
+
humanized += (humanized ? ' ' : '') + secs.toFixed(decimals) + 's';
|
223 |
+
}
|
224 |
+
|
225 |
+
return humanized;
|
226 |
+
}
|
227 |
+
|
228 |
+
// ISO8601 UTC offset is in format ±[hh]:[mm], ±[hh][mm], or ±[hh]
|
229 |
+
var rgx_has_timezone = new RegExp('Z|[\\-+\u2212]\\d\\d(?::?\\d\\d)?$');
|
230 |
+
function add_utc_offset (timestamp) {
|
231 |
+
if (options.default_kernel_to_utc && timestamp !== undefined && !rgx_has_timezone.test(timestamp)) {
|
232 |
+
return timestamp + 'Z';
|
233 |
+
}
|
234 |
+
return timestamp;
|
235 |
+
}
|
236 |
+
|
237 |
+
function format_moment (when) {
|
238 |
+
if (options.display_in_utc) {
|
239 |
+
when.utc();
|
240 |
+
}
|
241 |
+
if (options.display_absolute_timings) {
|
242 |
+
return when.format(options.display_absolute_format);
|
243 |
+
}
|
244 |
+
return when.fromNow();
|
245 |
+
}
|
246 |
+
|
247 |
+
function update_timing_area (cell) {
|
248 |
+
if (! (cell instanceof CodeCell) ||
|
249 |
+
!cell.metadata.ExecuteTime ||
|
250 |
+
!cell.metadata.ExecuteTime.start_time) {
|
251 |
+
return $();
|
252 |
+
}
|
253 |
+
|
254 |
+
var timing_area = cell.element.find('.timing_area');
|
255 |
+
if (timing_area.length < 1) {
|
256 |
+
timing_area = $('<div/>')
|
257 |
+
.addClass('timing_area' + (options.display_right_aligned ? ' text-right' : ''))
|
258 |
+
.on('dblclick', function (evt) { toggle_timing_display([cell]); })
|
259 |
+
.appendTo(cell.element.find('.input_area'));
|
260 |
+
}
|
261 |
+
|
262 |
+
var start_time = moment(cell.metadata.ExecuteTime.start_time),
|
263 |
+
end_time = cell.metadata.ExecuteTime.end_time;
|
264 |
+
var msg = options.template[end_time ? 'executed' : 'queued'];
|
265 |
+
msg = msg.replace('${start_time}', format_moment(start_time));
|
266 |
+
if (end_time) {
|
267 |
+
end_time = moment(end_time);
|
268 |
+
msg = msg.replace('${end_time}', format_moment(end_time));
|
269 |
+
var exec_time = -start_time.diff(end_time);
|
270 |
+
msg = msg.replace('${duration}', humanized_duration(exec_time));
|
271 |
+
}
|
272 |
+
timing_area.text(msg);
|
273 |
+
return timing_area;
|
274 |
+
}
|
275 |
+
|
276 |
+
function _update_all_timing_areas () {
|
277 |
+
Jupyter.notebook.get_cells().forEach(update_timing_area);
|
278 |
+
}
|
279 |
+
|
280 |
+
function update_all_timing_areas () {
|
281 |
+
console.debug(log_prefix, 'updating all timing areas');
|
282 |
+
_update_all_timing_areas();
|
283 |
+
}
|
284 |
+
|
285 |
+
function add_css(url) {
|
286 |
+
$('<link/>')
|
287 |
+
.attr({
|
288 |
+
rel: 'stylesheet',
|
289 |
+
href: requirejs.toUrl(url),
|
290 |
+
type: 'text/css'
|
291 |
+
})
|
292 |
+
.appendTo('head');
|
293 |
+
}
|
294 |
+
|
295 |
+
function load_jupyter_extension () {
|
296 |
+
// try to load jquery-ui
|
297 |
+
if ($.ui === undefined && options.highlight.use) {
|
298 |
+
requirejs(['jquery-ui'], function ($) {}, function (err) {
|
299 |
+
// try to load using the older, non-standard name (without hyphen)
|
300 |
+
requirejs(['jqueryui'], function ($) {}, function (err) {
|
301 |
+
console.log(log_prefix, 'couldn\'t find jquery-ui, so no animations');
|
302 |
+
});
|
303 |
+
});
|
304 |
+
}
|
305 |
+
|
306 |
+
add_css('./ExecuteTime.css');
|
307 |
+
|
308 |
+
Jupyter.notebook.config.loaded.then(function on_config_loaded () {
|
309 |
+
$.extend(true, options, Jupyter.notebook.config.data[mod_name]);
|
310 |
+
}, function on_config_load_error (reason) {
|
311 |
+
console.warn(log_prefix, 'Using defaults after error loading config:', reason);
|
312 |
+
}).then(function do_stuff_with_config () {
|
313 |
+
|
314 |
+
patch_CodeCell_get_callbacks();
|
315 |
+
events.on('execute.CodeCell', excute_codecell_callback);
|
316 |
+
|
317 |
+
create_menu();
|
318 |
+
|
319 |
+
// add any existing timing info
|
320 |
+
events.on("notebook_loaded.Notebook", update_all_timing_areas);
|
321 |
+
if (Jupyter.notebook !== undefined && Jupyter.notebook._fully_loaded) {
|
322 |
+
// notebook already loaded, so we missed the event, so update all
|
323 |
+
update_all_timing_areas();
|
324 |
+
}
|
325 |
+
|
326 |
+
// setup optional clear-data calls
|
327 |
+
if (options.clear_timings_on_clear_output) {
|
328 |
+
patch_CodeCell_clear_output();
|
329 |
+
}
|
330 |
+
if (options.clear_timings_on_kernel_restart) {
|
331 |
+
console.log(log_prefix, 'Binding kernel_restarting.Kernel event to clear timings.');
|
332 |
+
events.on('kernel_restarting.Kernel', clear_timing_data_all);
|
333 |
+
}
|
334 |
+
|
335 |
+
// if displaying relative times, update them at intervals
|
336 |
+
if (!options.display_absolute_timings) {
|
337 |
+
var period_ms = 1000 * Math.max(1, options.relative_timing_update_period);
|
338 |
+
setInterval(_update_all_timing_areas, period_ms);
|
339 |
+
}
|
340 |
+
}).catch(function on_error (reason) {
|
341 |
+
console.error(log_prefix, 'Error:', reason);
|
342 |
+
});
|
343 |
+
}
|
344 |
+
|
345 |
+
return {
|
346 |
+
load_jupyter_extension : load_jupyter_extension,
|
347 |
+
load_ipython_extension : load_jupyter_extension
|
348 |
+
};
|
349 |
+
});
|
.local/share/jupyter/nbextensions/execute_time/ExecuteTime.yaml
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Type: IPython Notebook Extension
|
2 |
+
Name: ExecuteTime
|
3 |
+
Description: Display when each cell has been executed and how long it took
|
4 |
+
Link: readme.md
|
5 |
+
Icon: icon.png
|
6 |
+
Main: ExecuteTime.js
|
7 |
+
Compatibility: 4.x, 5.x
|
8 |
+
Parameters:
|
9 |
+
|
10 |
+
- name: ExecuteTime.clear_timings_on_clear_output
|
11 |
+
description: |
|
12 |
+
When cells' outputs are cleared, also clear their timing data, e.g. when
|
13 |
+
using the "Kernel > Restart & Clear Output" menu item
|
14 |
+
input_type: checkbox
|
15 |
+
default: false
|
16 |
+
|
17 |
+
- name: ExecuteTime.clear_timings_on_kernel_restart
|
18 |
+
description: |
|
19 |
+
Clear all cells' execution timing data on any kernel restart event
|
20 |
+
input_type: checkbox
|
21 |
+
default: false
|
22 |
+
|
23 |
+
- name: ExecuteTime.display_absolute_timings
|
24 |
+
description: |
|
25 |
+
Display absolute timings for the start time of execution.
|
26 |
+
Setting false will display a relative timestamp like 'a few seconds ago'
|
27 |
+
default: true
|
28 |
+
input_type: checkbox
|
29 |
+
|
30 |
+
- name: ExecuteTime.display_absolute_format
|
31 |
+
description: |
|
32 |
+
The format to use when displaying absolute timings (see above)
|
33 |
+
default: 'YYYY-MM-DD HH:mm:ss'
|
34 |
+
input_type: text
|
35 |
+
|
36 |
+
- name: ExecuteTime.relative_timing_update_period
|
37 |
+
description: |
|
38 |
+
Seconds to wait between updating the relative timestamps, if using them
|
39 |
+
(see above)
|
40 |
+
default: 10
|
41 |
+
input_type: number
|
42 |
+
step: 1
|
43 |
+
min: 1
|
44 |
+
max: 600
|
45 |
+
|
46 |
+
- name: ExecuteTime.display_in_utc
|
47 |
+
description: |
|
48 |
+
Display times in UTC, rather than in the local timezone set by the browser
|
49 |
+
default: false
|
50 |
+
input_type: checkbox
|
51 |
+
|
52 |
+
- name: ExecuteTime.default_kernel_to_utc
|
53 |
+
description: |
|
54 |
+
For kernel timestamps which do not specify a timezone, assume UTC
|
55 |
+
default: true
|
56 |
+
input_type: checkbox
|
57 |
+
|
58 |
+
- name: ExecuteTime.display_right_aligned
|
59 |
+
description: |
|
60 |
+
Right-align the text in the timing area under each cell
|
61 |
+
default: false
|
62 |
+
input_type: checkbox
|
63 |
+
|
64 |
+
- name: ExecuteTime.highlight.use
|
65 |
+
description: |
|
66 |
+
Highlight the displayed execution time on completion of execution
|
67 |
+
default: true
|
68 |
+
input_type: checkbox
|
69 |
+
|
70 |
+
- name: ExecuteTime.highlight.color
|
71 |
+
description: |
|
72 |
+
Color to use for highlighting the displayed execution time
|
73 |
+
default: '#00BB00'
|
74 |
+
input_type: color
|
75 |
+
|
76 |
+
- name: ExecuteTime.template.executed
|
77 |
+
description: |
|
78 |
+
Template for the timing message for executed cells. See readme for
|
79 |
+
replacement tokens.
|
80 |
+
default: 'executed in ${duration}, finished ${end_time}'
|
81 |
+
input_type: text
|
82 |
+
|
83 |
+
- name: ExecuteTime.template.queued
|
84 |
+
description: |
|
85 |
+
Template for the timing message for queued cells. See readme for
|
86 |
+
replacement tokens.
|
87 |
+
default: 'execution queued ${start_time}'
|
88 |
+
input_type: text
|
.local/share/jupyter/nbextensions/execute_time/execution-timings-box.png
ADDED
.local/share/jupyter/nbextensions/execute_time/icon.png
ADDED
.local/share/jupyter/nbextensions/execute_time/readme.md
ADDED
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Execute Time
|
2 |
+
============
|
3 |
+
|
4 |
+
This extension displays when the last execution of a code cell occurred, and
|
5 |
+
how long it took.
|
6 |
+
|
7 |
+
Every executed code cell is extended with a new area, attached at the bottom of
|
8 |
+
the input area, that displays the time at which the user sent the cell to the
|
9 |
+
kernel for execution.
|
10 |
+
When the kernel finishes executing the cell, the area is updated with the
|
11 |
+
duration of the execution.
|
12 |
+
The timing information is stored in the cell metadata, and restored on notebook
|
13 |
+
load.
|
14 |
+
|
15 |
+
![](execution-timings-box.png)
|
16 |
+
|
17 |
+
|
18 |
+
Toggling display
|
19 |
+
----------------
|
20 |
+
|
21 |
+
The timing area can be hidden by double clicking on it, or using the
|
22 |
+
`Cell -> Toggle timings -> Selected`
|
23 |
+
menu item.
|
24 |
+
The menu item
|
25 |
+
`Cell -> Toggle timings -> All`
|
26 |
+
hides (shows) all the timing areas in the notebook, if the first cell is
|
27 |
+
currently shown (hidden).
|
28 |
+
|
29 |
+
![](execution-timings-menu.png)
|
30 |
+
|
31 |
+
|
32 |
+
Options
|
33 |
+
-------
|
34 |
+
|
35 |
+
The nbextension offers a few options for how to display and interpret
|
36 |
+
timestamps.
|
37 |
+
Options are stored in the `notebook` section of the server's nbconfig, under
|
38 |
+
the key `ExecuteTime`.
|
39 |
+
The easiest way to configure these is using the
|
40 |
+
[jupyter_nbextensions_configurator](https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator),
|
41 |
+
which if you got this nbextension in the usual way from
|
42 |
+
[jupyter_contrib_nbextensions](https://github.com/ipython-contrib/jupyter_contrib_nbextensions),
|
43 |
+
should also have been installed.
|
44 |
+
|
45 |
+
Alternatively, you can also configure them directly with a few lines of python.
|
46 |
+
For example, to alter the displayed message, use relative timestamps,
|
47 |
+
and set them to update every 5 seconds, we can use the following python
|
48 |
+
snippet:
|
49 |
+
|
50 |
+
```python
|
51 |
+
from notebook.services.config import ConfigManager
|
52 |
+
ConfigManager().update('notebook', {'ExecuteTime': {
|
53 |
+
'display_absolute_timestamps': False,
|
54 |
+
'relative_timing_update_period': 5,
|
55 |
+
'template': {
|
56 |
+
'executed': 'started ${start_time}, finished in ${duration}',
|
57 |
+
}
|
58 |
+
}})
|
59 |
+
```
|
60 |
+
|
61 |
+
The available options are:
|
62 |
+
|
63 |
+
* `ExecuteTime.clear_timings_on_clear_output`: When cells' outputs are cleared,
|
64 |
+
also clear their timing data, e.g. when using the
|
65 |
+
`Kernel > Restart & Clear Output` menu item
|
66 |
+
|
67 |
+
* `ExecuteTime.clear_timings_on_kernel_restart`: Clear all cells' execution
|
68 |
+
timing data on any kernel restart event
|
69 |
+
|
70 |
+
* `ExecuteTime.display_absolute_timings`: Display absolute timings for the
|
71 |
+
start/end time of execution. Setting this `false` will result in the display
|
72 |
+
of a relative timestamp like 'a few seconds ago' (see the moment.js function
|
73 |
+
[fromNow](https://momentjs.com/docs/#/displaying/fromnow/)
|
74 |
+
for details). Defaults to `true`.
|
75 |
+
|
76 |
+
* `ExecuteTime.display_absolute_format`: The format to use when displaying
|
77 |
+
absolute timings (see `ExecuteTime.display_absolute_timings`, above).
|
78 |
+
See the moment.js function
|
79 |
+
[format](https://momentjs.com/docs/#/displaying/format/)
|
80 |
+
for details of the template tokens available.
|
81 |
+
Defaults to `'YYYY-MM-DD HH:mm:ss'`.
|
82 |
+
|
83 |
+
* `ExecuteTime.relative_timing_update_period`: Seconds to wait between updating
|
84 |
+
the relative timestamps, if using them (see
|
85 |
+
`ExecuteTime.display_absolute_timings`, above).
|
86 |
+
Defaults to `10`.
|
87 |
+
|
88 |
+
* `ExecuteTime.display_in_utc`: Display times in UTC, rather than in the local
|
89 |
+
timezone set by the browser.
|
90 |
+
Defaults to `false`.
|
91 |
+
|
92 |
+
* `ExecuteTime.default_kernel_to_utc`: For kernel timestamps which do not
|
93 |
+
specify a timezone, assume UTC.
|
94 |
+
Defaults to `true`.
|
95 |
+
|
96 |
+
* `ExecuteTime.display_right_aligned`: Right-align the text in the timing area
|
97 |
+
under each cell.
|
98 |
+
Defaults to `false`.
|
99 |
+
|
100 |
+
* `ExecuteTime.highlight.use`: Highlight the displayed execution time on
|
101 |
+
completion of execution.
|
102 |
+
Defaults to `true`.
|
103 |
+
|
104 |
+
* `ExecuteTime.highlight.color`: Color to use for highlighting the displayed
|
105 |
+
execution time.
|
106 |
+
Defaults to `'#00BB00'`.
|
107 |
+
|
108 |
+
* `ExecuteTime.template.executed`: Template for the timing message for executed
|
109 |
+
cells. See readme for replacement tokens.
|
110 |
+
Defaults to `'executed in ${duration}, finished ${end_time}'`.
|
111 |
+
|
112 |
+
* `ExecuteTime.template.queued`: Template for the timing message for queued
|
113 |
+
cells. The template uses an ES2015-like syntax, but replaces only the exact
|
114 |
+
strings `${start_time}`, plus (if defined) `${end_time}` and `${duration}`.
|
115 |
+
Defaults to `'execution queued ${start_time}'`.
|
116 |
+
|
117 |
+
|
118 |
+
|
119 |
+
Limitations
|
120 |
+
-----------
|
121 |
+
|
122 |
+
|
123 |
+
### timezones
|
124 |
+
|
125 |
+
As discussed in
|
126 |
+
[ipython-contrib/jupyter_contrib_nbextensions#549](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/549),
|
127 |
+
[ipython-contrib/jupyter_contrib_nbextensions#904](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/904),
|
128 |
+
and
|
129 |
+
[jupyter/jupyter_client#143](https://github.com/jupyter/jupyter_client/issues/143),
|
130 |
+
although they are (now) supposed to, Jupyter kernels don't always specify a
|
131 |
+
timezone for their timestamps, which can cause problems when the
|
132 |
+
[moment.js](https://momentjs.com/)
|
133 |
+
library assumes the local timezone, rather than UTC, which is what most kernels
|
134 |
+
are actually using.
|
135 |
+
To help to address this, see the [options](#Options) above, which can be used
|
136 |
+
to assume UTC for unzoned timestamps.
|
137 |
+
|
138 |
+
|
139 |
+
### execution queues
|
140 |
+
|
141 |
+
For a reason I don't understand, when multiple cells are queued for execution,
|
142 |
+
the kernel doesn't send a reply immediately after finishing executing each
|
143 |
+
cell.
|
144 |
+
Some replies are delayed, and sent at the same time as later replies, meaning
|
145 |
+
that the output of a cell can be updated with its finished value, before the
|
146 |
+
notebook recieves the kernel execution reply.
|
147 |
+
For the same reason, you can see this in the fact that the star for an
|
148 |
+
executing cell can remain next to two cells at once, if several are queued to
|
149 |
+
execute together.
|
150 |
+
Since this extension uses the times in the kernel message (see internals,
|
151 |
+
below), and these remain correct, the timings displayed are still accurate,
|
152 |
+
but they may get displayed later due to this kernel issue.
|
153 |
+
|
154 |
+
|
155 |
+
Installation
|
156 |
+
------------
|
157 |
+
|
158 |
+
Install the master version of the jupyter_contrib_nbextensions repository as
|
159 |
+
explained in the docs at
|
160 |
+
[jupyter-contrib-nbextensions.readthedocs.io](https://jupyter-contrib-nbextensions.readthedocs.io/en/latest/install.html).
|
161 |
+
|
162 |
+
Then you can use the
|
163 |
+
[jupyter_nbextensions_configurator](https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator)
|
164 |
+
to enable/disable this extension for all notebooks.
|
165 |
+
|
166 |
+
Internals
|
167 |
+
---------
|
168 |
+
|
169 |
+
The execution start and end times are stored in the cell metadata as ISO8601
|
170 |
+
strings, for example:
|
171 |
+
|
172 |
+
```json
|
173 |
+
{
|
174 |
+
"ExecuteTime": {
|
175 |
+
"start_time": "2016-02-11T18:51:18.536796",
|
176 |
+
"end_time": "2016-02-11T18:51:35.806119"
|
177 |
+
}
|
178 |
+
}
|
179 |
+
```
|
180 |
+
|
181 |
+
The times in the timing areas are formatted using the
|
182 |
+
[moment.js](https://momentjs.com/) library (already included as part of
|
183 |
+
Jupyter), but the durations use a custom formatting function, as
|
184 |
+
I ([@jcb91](https://github.com/jcb91))
|
185 |
+
couldn't find an existing one that I liked.
|
186 |
+
|
187 |
+
The event `execute.CodeCell` is caught in order to create a start time, and add
|
188 |
+
the timing area with its 'Execution queued at' message.
|
189 |
+
The extension again uses [moment.js](https://momentjs.com/) for formatting this
|
190 |
+
as an ISO string time.
|
191 |
+
|
192 |
+
To determine the execution time, the extension patches the Jupyter class
|
193 |
+
prototype `CodeCell.prototype.get_callbacks` from `notebook/js/codecell.js`.
|
194 |
+
This patch then patches the `callbacks.shell.reply` function returned by the
|
195 |
+
original `CodeCell.prototype.get_callbacks`, wrapping it in a function which
|
196 |
+
reads the `msg.header.date` value from the kernel message, to provide the
|
197 |
+
execution end time.
|
198 |
+
This is more accurate than creating a new time, which can be affected by
|
199 |
+
client-side variability.
|
200 |
+
In addition, for accurate timings, the start time is also revised using
|
201 |
+
the `msg.metadata.started` value supplied in the callback, which can be very
|
202 |
+
different from the time the cell was queued for execution (as a result of
|
203 |
+
other cells already being executed).
|
204 |
+
The kernel reply message times are already ISO8601 strings, so no conversion is
|
205 |
+
necessary, although again, [moment.js](https://momentjs.com/) is used for
|
206 |
+
parsing and diff'ing them.
|
.local/share/jupyter/nbextensions/execution_dependencies/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
execution_dependencies
|
2 |
+
======================
|
3 |
+
|
4 |
+
Writing extensive notebooks can become very complicated since many cells act as stepping stones to produce intermediate results for later cells. Thus, it becomes tedious to
|
5 |
+
keep track of the cells that have to be run in order to run a certain cell. This extension simplifies handling the execution dependencies by introducing tag annotations to
|
6 |
+
identify each cell and indicate a dependency on others. This improves on the current state which requires remembering all dependencies by heart or annotating the cells in the comments.
|
7 |
+
|
8 |
+
If a cell with dependencies is run, the extension checks recursively for all dependencies of the cell, then executes them before executing the cell after all the dependencies have finished.
|
9 |
+
Dependencies are definitely executed and not only once per kernel session.
|
10 |
+
|
11 |
+
The two annotations are added to the tags of a cell and are as follows:
|
12 |
+
|
13 |
+
* add a hashmark (#) and an identification tag to the tags to identify a cell (e.g. #initializer-cell). The #identifiers must be unique among all cells.
|
14 |
+
* add an arrow (=>) and an identification tag to the tags to add a dependency on a certain cell (e.g. =>initializer-cell).
|
15 |
+
|
16 |
+
Based on these dependencies, the kernel will now execute the dependencies before the cell that depends on them. If the cell's dependencies have further dependencies, these will in turn
|
17 |
+
be executed before them. In conclusion, the kernel looks through the tree of dependencies of the cell executed by the user and executes its dependencies in their appropriate order,
|
18 |
+
then executes the cell.
|
19 |
+
|
20 |
+
A more extensive example is described below:
|
21 |
+
|
22 |
+
A cell A has the identifier #A.
|
23 |
+
|
24 |
+
| Cell A [tags: #A] |
|
25 |
+
| ------------- |
|
26 |
+
| Content Cell |
|
27 |
+
| Content Cell |
|
28 |
+
|
29 |
+
|
30 |
+
A cell B has the identifier #B and depends on A (=>A).
|
31 |
+
|
32 |
+
|
33 |
+
| Cell B [tags: #B, =>A] |
|
34 |
+
| ------------- |
|
35 |
+
| Content Cell |
|
36 |
+
| Content Cell |
|
37 |
+
|
38 |
+
If the user runs A, only A is executed, since it has no dependencies. On the other hand, if the user runs B, the kernel finds the dependency on A, and thus first runs A and then runs B.
|
39 |
+
|
40 |
+
Running a cell C that is dependent on B and on A as well, the kernel then first runs A and then runs B before running C, avoiding to run cell A twice.
|
41 |
+
|
42 |
+
|
43 |
+
If you are missing anything, open up an issue at the repository prepending [execute_dependencies] to the title.
|
.local/share/jupyter/nbextensions/execution_dependencies/execution_dependencies.js
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* execution_dependencies.js
|
3 |
+
* Introduce tag annotations to identify each cell and indicate a dependency on others.
|
4 |
+
* Upon running a cell, its dependencies are run first to prepare all dependencies.
|
5 |
+
* Then the cell triggered by the user is run as soon as all its dependencies are met.
|
6 |
+
*
|
7 |
+
*
|
8 |
+
* @version 0.1.0
|
9 |
+
* @author Benjamin Ellenberger, https://github.com/benelot
|
10 |
+
* @updated 2018-01-31
|
11 |
+
*
|
12 |
+
*
|
13 |
+
*/
|
14 |
+
define([
|
15 |
+
'jquery',
|
16 |
+
'base/js/dialog',
|
17 |
+
'base/js/namespace',
|
18 |
+
'notebook/js/codecell'
|
19 |
+
], function (
|
20 |
+
$,
|
21 |
+
dialog,
|
22 |
+
Jupyter,
|
23 |
+
codecell
|
24 |
+
) {
|
25 |
+
"use strict";
|
26 |
+
|
27 |
+
var CodeCell = codecell.CodeCell;
|
28 |
+
|
29 |
+
return {
|
30 |
+
load_ipython_extension: function () {
|
31 |
+
console.log('[execution_dependencies] patching CodeCell.execute');
|
32 |
+
var orig_execute = codecell.CodeCell.prototype.execute; // keep original cell execute function
|
33 |
+
CodeCell.prototype.execute = function (stop_on_error) {
|
34 |
+
var root_tags = this.metadata.tags || []; // get tags of the cell executed by the user (root cell)
|
35 |
+
if(root_tags.some(tag => /=>.*/.test(tag))) { // if the root cell contains any dependencies, resolve dependency tree
|
36 |
+
var root_cell = this;
|
37 |
+
var root_cell_id = root_cell.cell_id;
|
38 |
+
var cells_with_id = Jupyter.notebook.get_cells().filter(function (cell, idx, cells) { // ...get all cells which have at least one id (these are the only ones we could have in deps)
|
39 |
+
var tags = cell.metadata.tags || [];
|
40 |
+
return (cell === root_cell || tags.some(tag => /#.*/.test(tag)));
|
41 |
+
});
|
42 |
+
|
43 |
+
console.log('[execution_dependencies] collecting ids and dependencies...');
|
44 |
+
var cell_map = {}
|
45 |
+
var dep_graph = {}
|
46 |
+
cells_with_id.forEach(function (cell) { // ...get all identified cells (the ones that have at least one #tag)
|
47 |
+
var tags = cell.metadata.tags || [];
|
48 |
+
var cell_ids = tags.filter(tag => /#.*/.test(tag)).map(tag => tag.substring(1)); // ...get all identifiers of the current cell and drop the #
|
49 |
+
if(cell === root_cell){
|
50 |
+
if(cell_ids.length < 1) {
|
51 |
+
cell_ids.push(root_cell.cell_id); // ...use internal root cell id for internal usage
|
52 |
+
}
|
53 |
+
else {
|
54 |
+
root_cell_id = cell_ids[0]; // get any of the root cell ids
|
55 |
+
}
|
56 |
+
}
|
57 |
+
|
58 |
+
var dep_ids = tags.filter(tag => /=>.*/.test(tag)).map(tag => tag.substring(2)); // ...get all dependencies and drop the =>
|
59 |
+
|
60 |
+
cell_ids.forEach(function (id) {
|
61 |
+
//console.log('ID:', id, 'deps: ', dep_ids.toString())
|
62 |
+
cell_map[id] = cell;
|
63 |
+
dep_graph[id] = dep_ids;
|
64 |
+
|
65 |
+
});
|
66 |
+
});
|
67 |
+
|
68 |
+
if(dep_graph[root_cell_id].length > 0) {
|
69 |
+
console.log('[execution_dependencies] collecting depdendency graph in-degrees...');
|
70 |
+
var processing_queue = [root_cell_id];
|
71 |
+
var processed_nodes = 0;
|
72 |
+
var in_degree = {}; // ...collect in-degrees of nodes
|
73 |
+
while(processing_queue.length > 0 && processed_nodes < Object.keys(dep_graph).length) {// ...stay processing deps while the queue contains nodes and the processed nodes are below total node quantity
|
74 |
+
var id = processing_queue.shift(); // .....pop front of queue and front-push it to the processing order
|
75 |
+
//console.log("ID: ", id);
|
76 |
+
for(var i=0, dep_qty=dep_graph[id].length; i < dep_qty; i++) {
|
77 |
+
var dep = dep_graph[id][i];
|
78 |
+
// console.log(' dep: ', dep);
|
79 |
+
in_degree[id] = in_degree[id] || 0;
|
80 |
+
in_degree[dep] = in_degree[dep] === undefined ? 1 : ++in_degree[dep];
|
81 |
+
processing_queue.unshift(dep);
|
82 |
+
}
|
83 |
+
processed_nodes++;
|
84 |
+
}
|
85 |
+
|
86 |
+
console.log('[execution_dependencies] starting topological sort...');
|
87 |
+
processing_queue = [root_cell_id]; // ...add root node with in-degree 0 to queue (this excludes all disconnected subgraphs)
|
88 |
+
processed_nodes = 0; // ...number of processed nodes (to detect circular dependencies)
|
89 |
+
var processing_order = [];
|
90 |
+
while(processing_queue.length > 0 && processed_nodes < Object.keys(dep_graph).length) {// ...stay processing deps while the queue contains nodes and the processed nodes are below total node quantity
|
91 |
+
var id = processing_queue.shift(); // .....pop front of queue and front-push it to the processing order
|
92 |
+
processing_order.unshift(id);
|
93 |
+
//console.log("ID: ", id);
|
94 |
+
for(var i=0, dep_qty=dep_graph[id].length; i < dep_qty; i++) { // ......iterate over dependent nodes of current id and decrease their in-degree by 1
|
95 |
+
var dep = dep_graph[id][i];
|
96 |
+
// console.log(' dep: ', dep);
|
97 |
+
in_degree[dep]--;
|
98 |
+
if(in_degree[dep] == 0) { // ......queue dependency if in-degree is 0
|
99 |
+
processing_queue.unshift(dep);
|
100 |
+
}
|
101 |
+
}
|
102 |
+
processed_nodes++;
|
103 |
+
}
|
104 |
+
|
105 |
+
console.log('[execution_dependencies] checking for circular dependencies...');
|
106 |
+
if(processed_nodes > Object.keys(dep_graph).length) { // ...if more nodes where processed than the number of graph nodes, there is a circular dependency
|
107 |
+
dialog.modal({
|
108 |
+
title : 'Circular dependencies in the execute dependencies of this cell',
|
109 |
+
body : 'There is a circular dependency in this cell\'s execute dependencies. The cell will be run without dependencies. If this does not work, fix the dependencies and rerun the cell.',
|
110 |
+
buttons: {'OK': {'class' : 'btn-primary'}},
|
111 |
+
notebook: Jupyter.notebook,
|
112 |
+
keyboard_manager: Jupyter.keyboard_manager,
|
113 |
+
});
|
114 |
+
}
|
115 |
+
else if(!Jupyter.notebook.trusted) { // ...if the notebook is not trusted, we do not execute dependencies, but only print them out to the user
|
116 |
+
dialog.modal({
|
117 |
+
title : 'Execute dependencies in untrusted notebook',
|
118 |
+
body : 'This notebook is not trusted, so execute dependencies will not be automatically run. You can still run them manually, though. Run in order (the last one is the cell you wanted to execute): ' + processing_order,
|
119 |
+
buttons: {'OK': {'class' : 'btn-primary'}},
|
120 |
+
notebook: Jupyter.notebook,
|
121 |
+
keyboard_manager: Jupyter.keyboard_manager,
|
122 |
+
});
|
123 |
+
}
|
124 |
+
else{
|
125 |
+
processing_order.pop()
|
126 |
+
console.log('[execution_dependencies] executing dependency cells in order ', processing_order ,'...');
|
127 |
+
var dependency_cells = processing_order.map(id =>cell_map[id]); // ...get dependent cells by their id
|
128 |
+
//console.log("Execute cells..", dependency_cells)
|
129 |
+
dependency_cells.forEach(cell => orig_execute.call(cell, stop_on_error)); // ...execute all dependent cells in sequence using the original execute method
|
130 |
+
}
|
131 |
+
}
|
132 |
+
}
|
133 |
+
console.log('[execution_dependencies] executing requested cell...');
|
134 |
+
orig_execute.call(this, stop_on_error); // execute original cell execute function
|
135 |
+
};
|
136 |
+
console.log('[execution_dependencies] loaded');
|
137 |
+
}
|
138 |
+
};
|
139 |
+
});
|
.local/share/jupyter/nbextensions/exercise/history.md
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Exercise nbextension history
|
2 |
+
----------------------------
|
3 |
+
|
4 |
+
Update december 30, 2015:
|
5 |
+
(@jfbercher) Updated to jupyter notebook 4.1.x
|
6 |
+
|
7 |
+
Update december 22, 2015:
|
8 |
+
(@jfbercher)
|
9 |
+
Added the metadata solution_first to mark the beginning of an exercise. It is now possible to have several consecutive exercises.
|
10 |
+
|
11 |
+
October 21-27,2015:
|
12 |
+
(@jfbercher)
|
13 |
+
|
14 |
+
1- the extension now works with the multicell API, that is
|
15 |
+
- several cells can be selected either via the rubberband extension
|
16 |
+
- or via Shift-J (select next) or Shift-K (select previous) keyboard shortcuts
|
17 |
+
(probably Shit-up and down will work in a near future)
|
18 |
+
Note: previously, the extension required the selected cells to be marked with a "selected" key in metadata. This is no more necessary with the new API.
|
19 |
+
Then clicking on the toolbar button turns these cells into a "solution" which is hidden by default ** Do not forget to keep the Shift key pressed down while clicking on the menu button (otherwise selected cells will be lost)**
|
20 |
+
2- the "state" of solutions, hidden or shown, is saved and restored at reload/restart. We use the "solution" metadata to store the current state.
|
21 |
+
3- A small issue (infinite loop when a solution was defined at the bottom edge of the notebook have been corrected)
|
22 |
+
4- Added a keyboard shortcut (Alt-S with S for solution]
|
23 |
+
|
24 |
+
October-November 2014 (?):
|
25 |
+
|
26 |
+
Several versions (@juhasch)
|
.local/share/jupyter/nbextensions/exercise/icon.png
ADDED
.local/share/jupyter/nbextensions/exercise/image.gif
ADDED
.local/share/jupyter/nbextensions/exercise/main.js
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Copyright (c) IPython-Contrib Team.
|
2 |
+
// Distributed under the terms of the Modified BSD License.
|
3 |
+
|
4 |
+
// Hide or display solutions in a notebook
|
5 |
+
|
6 |
+
/*
|
7 |
+
December 6, 2017 @jcb91: use bootstrap 'hidden' class to play nicely with collapsible_headings
|
8 |
+
December 30, 2015: update to 4.1
|
9 |
+
Update december 22, 2015:
|
10 |
+
Added the metadata solution_first to mark the beginning of an exercise. It is now possible to have several consecutive exercises.
|
11 |
+
Update october 21-27,2015:
|
12 |
+
1- the extension now works with the multicell API, that is
|
13 |
+
- several cells can be selected either via the rubberband extension
|
14 |
+
- or via Shift-J (select next) or Shift-K (select previous) keyboard shortcuts
|
15 |
+
(probably Shit-up and down will work in a near future)
|
16 |
+
Note: previously, the extension required the selected cells to be marked with a "selected" key in metadata. This is no more necessary with the new API.
|
17 |
+
Then clicking on the toolbar button transforms these cells into a "solution" which is hidden by default
|
18 |
+
** Do not forget to keep the Shift key pressed down while clicking on the menu button
|
19 |
+
(otherwise selected cells will be lost)**
|
20 |
+
2- the "state" of solutions, hidden or shown, is saved and restored at reload/restart. We use the "solution" metadata to store the current state.
|
21 |
+
3- A small issue (infinite loop when a solution was defined at the bottom edge of the notebook have been corrected)
|
22 |
+
4- Added a keyboard shortcut (Alt-S) [S for solution]
|
23 |
+
*/
|
24 |
+
|
25 |
+
define([
|
26 |
+
'base/js/namespace',
|
27 |
+
'jquery',
|
28 |
+
'require',
|
29 |
+
'base/js/events',
|
30 |
+
], function(IPython, $, requirejs, events) {
|
31 |
+
"use strict";
|
32 |
+
|
33 |
+
var cfg = {
|
34 |
+
add_button: true,
|
35 |
+
use_hotkey: true,
|
36 |
+
hotkey: 'Alt-S',
|
37 |
+
};
|
38 |
+
|
39 |
+
/**
|
40 |
+
* handle click event
|
41 |
+
*
|
42 |
+
* @method click_solution_lock
|
43 |
+
* @param evt {Event} jquery event
|
44 |
+
*/
|
45 |
+
function click_solution_lock(evt) {
|
46 |
+
var cell = IPython.notebook.get_selected_cell();
|
47 |
+
var is_locked = cell.metadata.solution === 'hidden';
|
48 |
+
cell.metadata.solution = is_locked ? 'shown' : 'hidden';
|
49 |
+
element_set_locked(cell, !is_locked);
|
50 |
+
cell = IPython.notebook.get_next_cell(cell);
|
51 |
+
while (cell !== null && cell.metadata.solution !== undefined && !cell.metadata.solution_first) {
|
52 |
+
cell.element.toggleClass('hidden', !is_locked);
|
53 |
+
cell.metadata.solution = is_locked ? 'shown' : 'hidden';
|
54 |
+
cell = IPython.notebook.get_next_cell(cell);
|
55 |
+
}
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Create or Remove an exercise in selected cells
|
60 |
+
*
|
61 |
+
* @method create_remove_exercise
|
62 |
+
*
|
63 |
+
*/
|
64 |
+
function create_remove_exercise() {
|
65 |
+
var lcells = IPython.notebook.get_selected_cells();
|
66 |
+
// It is possible that no cell is selected
|
67 |
+
if (lcells.length < 1) {
|
68 |
+
alert("Exercise extension: \nPlease select some cells...");
|
69 |
+
return;
|
70 |
+
}
|
71 |
+
|
72 |
+
var cell = lcells[0];
|
73 |
+
if (cell.metadata.solution_first) {
|
74 |
+
remove_element(cell);
|
75 |
+
delete cell.metadata.solution_first;
|
76 |
+
while (cell !== null && cell.metadata.solution !== undefined && !cell.metadata.solution_first) {
|
77 |
+
delete cell.metadata.solution;
|
78 |
+
cell.element.removeClass('hidden');
|
79 |
+
cell = IPython.notebook.get_next_cell(cell);
|
80 |
+
}
|
81 |
+
}
|
82 |
+
else {
|
83 |
+
cell.metadata.solution_first = true;
|
84 |
+
cell.metadata.solution = 'hidden';
|
85 |
+
add_element(cell);
|
86 |
+
for (var k = 1; k < lcells.length; k++) {
|
87 |
+
cell = lcells[k];
|
88 |
+
cell.element.addClass('hidden');
|
89 |
+
cell.metadata.solution = 'hidden';
|
90 |
+
}
|
91 |
+
}
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Add a lock control to the given cell
|
96 |
+
*/
|
97 |
+
function add_element(cell) {
|
98 |
+
var ctrl = cell.element.find('.exercise');
|
99 |
+
if (ctrl.length > 0) return ctrl;
|
100 |
+
var locked = cell.metadata.solution === 'hidden';
|
101 |
+
ctrl = $('<div class="exercise fa">')
|
102 |
+
.prependTo(cell.element)
|
103 |
+
.on('click', click_solution_lock);
|
104 |
+
element_set_locked(cell, locked);
|
105 |
+
return ctrl;
|
106 |
+
}
|
107 |
+
|
108 |
+
function remove_element(cell) {
|
109 |
+
cell.element.find('.exercise').remove();
|
110 |
+
}
|
111 |
+
|
112 |
+
function element_set_locked(cell, locked) {
|
113 |
+
return cell.element.find('.exercise')
|
114 |
+
.toggleClass('fa-plus-square-o', locked)
|
115 |
+
.toggleClass('fa-minus-square-o', !locked);
|
116 |
+
}
|
117 |
+
|
118 |
+
function refresh_exercises() {
|
119 |
+
var in_exercise = false;
|
120 |
+
IPython.notebook.get_cells().forEach(function(cell) {
|
121 |
+
if (in_exercise && cell.metadata.solution !== undefined && !cell.metadata.solution_first) {
|
122 |
+
cell.element.toggleClass('hidden', cell.metadata.solution === 'hidden');
|
123 |
+
} else {
|
124 |
+
in_exercise = false;
|
125 |
+
}
|
126 |
+
if (!in_exercise && cell.metadata.solution !== undefined) {
|
127 |
+
in_exercise = true;
|
128 |
+
add_element(cell);
|
129 |
+
}
|
130 |
+
});
|
131 |
+
}
|
132 |
+
|
133 |
+
function load_ipython_extension() {
|
134 |
+
// add css
|
135 |
+
$('<link rel="stylesheet" type="text/css">')
|
136 |
+
.attr('href', requirejs.toUrl('./main.css'))
|
137 |
+
.appendTo('head');
|
138 |
+
|
139 |
+
// Hide/display existing solutions at startup
|
140 |
+
events.on('notebook_loaded.Notebook', refresh_exercises);
|
141 |
+
if (IPython.notebook._fully_loaded) refresh_exercises();
|
142 |
+
|
143 |
+
var action_name = IPython.keyboard_manager.actions.register({
|
144 |
+
help : 'Exercise: Create/Remove exercise',
|
145 |
+
help_index: 'ht',
|
146 |
+
icon : 'fa-mortar-board',
|
147 |
+
handler : create_remove_exercise
|
148 |
+
}, 'create_remove_exercise', 'exercise');
|
149 |
+
|
150 |
+
IPython.notebook.config.loaded.then(function() {
|
151 |
+
$.extend(true, cfg, IPython.notebook.config.data);
|
152 |
+
|
153 |
+
if (cfg.add_button) {
|
154 |
+
IPython.toolbar.add_buttons_group([action_name]);
|
155 |
+
}
|
156 |
+
if (cfg.use_hotkey && cfg.hotkey) {
|
157 |
+
var cmd_shrts = {};
|
158 |
+
cmd_shrts[cfg.hotkey] = action_name;
|
159 |
+
IPython.keyboard_manager.command_shortcuts.add_shortcuts(cmd_shrts);
|
160 |
+
}
|
161 |
+
}).catch(function(err) {
|
162 |
+
console.warn('[exercise] error:', err);
|
163 |
+
});
|
164 |
+
}
|
165 |
+
|
166 |
+
return {
|
167 |
+
load_ipython_extension: load_ipython_extension,
|
168 |
+
};
|
169 |
+
});
|
.local/share/jupyter/nbextensions/exercise2/icon.png
ADDED
.local/share/jupyter/nbextensions/exercise2/image.gif
ADDED
.local/share/jupyter/nbextensions/exercise2/main.css
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.exercise2 {
|
2 |
+
display: flex;
|
3 |
+
width: 100%;
|
4 |
+
flex-direction: row;
|
5 |
+
align-content: flex-end;
|
6 |
+
}
|
7 |
+
|
8 |
+
.onoffswitch {
|
9 |
+
display: inline;
|
10 |
+
position: relative; width: 167px;
|
11 |
+
margin-top:8px;
|
12 |
+
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
|
13 |
+
}
|
14 |
+
.onoffswitch-checkbox {
|
15 |
+
display: none;
|
16 |
+
}
|
17 |
+
.onoffswitch-label {
|
18 |
+
display: block; overflow: hidden; cursor: pointer;
|
19 |
+
border: 2px solid #999999; border-radius: 20px;
|
20 |
+
margin:0;
|
21 |
+
}
|
22 |
+
.onoffswitch-inner {
|
23 |
+
display: block; width: 200%; margin-left: -100%;
|
24 |
+
transition: margin 0.3s ease-in 0s;
|
25 |
+
}
|
26 |
+
.onoffswitch-inner:before, .onoffswitch-inner:after {
|
27 |
+
display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
|
28 |
+
font-size: 15px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
|
29 |
+
box-sizing: border-box;
|
30 |
+
}
|
31 |
+
.onoffswitch-inner:before {
|
32 |
+
content: "Hide Solution";
|
33 |
+
padding-left: 10px;
|
34 |
+
background-color: #34A7C1; color: #FFFFFF;
|
35 |
+
}
|
36 |
+
|
37 |
+
.onoffswitch-inner:after {
|
38 |
+
content: "Show Solution";
|
39 |
+
padding-right: 10px;
|
40 |
+
background-color: #73FA7E; color: #999999;
|
41 |
+
text-align: right;
|
42 |
+
}
|
43 |
+
.onoffswitch-switch {
|
44 |
+
display: block; width: 14px; margin: 6px;
|
45 |
+
padding-top: 0px;
|
46 |
+
background: #FFFFFF;
|
47 |
+
position: absolute; top: 0; bottom: 0;
|
48 |
+
text-align: center;
|
49 |
+
vertical-align: middle;
|
50 |
+
right: 133px;
|
51 |
+
border: 2px solid #999999; border-radius: 20px;
|
52 |
+
transition: all 0.25s ease-in 0s;
|
53 |
+
}
|
54 |
+
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
|
55 |
+
margin-left: 0;
|
56 |
+
}
|
57 |
+
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
|
58 |
+
right: 0px;
|
59 |
+
}
|
60 |
+
|
.local/share/jupyter/nbextensions/exercise2/main.js
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Copyright (c) IPython-Contrib Team.
|
2 |
+
// Distributed under the terms of the Modified BSD License.
|
3 |
+
|
4 |
+
// Hide or display solutions in a notebook
|
5 |
+
|
6 |
+
// dec 6, 2017 @jcb91: use bootstrap 'hidden' class to play nicely with collapsible_headings
|
7 |
+
// december 30, 2015: update to notebook 4.1.x
|
8 |
+
// updated on december 22, 2015 to allow consecutive exercises
|
9 |
+
// exercise2: built by @jfbercher from an earlier work by @junasch october 2015) - see readme.md
|
10 |
+
|
11 |
+
define([
|
12 |
+
'base/js/namespace',
|
13 |
+
'jquery',
|
14 |
+
'require',
|
15 |
+
'base/js/events',
|
16 |
+
], function(IPython, $, requirejs, events) {
|
17 |
+
"use strict";
|
18 |
+
|
19 |
+
var cfg = {
|
20 |
+
add_button: true,
|
21 |
+
use_hotkey: true,
|
22 |
+
hotkey: 'Alt-D',
|
23 |
+
};
|
24 |
+
|
25 |
+
/**
|
26 |
+
* handle click event
|
27 |
+
*
|
28 |
+
* @method click_solution_lock
|
29 |
+
* @param evt {Event} jquery event
|
30 |
+
*/
|
31 |
+
function click_solution_lock(evt) {
|
32 |
+
var cell = IPython.notebook.get_selected_cell();
|
33 |
+
var is_locked = cell.metadata.solution2 === 'hidden';
|
34 |
+
cell.metadata.solution2 = is_locked ? 'shown' : 'hidden';
|
35 |
+
element_set_locked(cell, !is_locked);
|
36 |
+
cell = IPython.notebook.get_next_cell(cell);
|
37 |
+
while (cell !== null && cell.metadata.solution2 !== undefined && !cell.metadata.solution2_first) {
|
38 |
+
cell.element.toggleClass('hidden', !is_locked);
|
39 |
+
cell.metadata.solution2 = is_locked ? 'shown' : 'hidden';
|
40 |
+
cell = IPython.notebook.get_next_cell(cell);
|
41 |
+
}
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Create or Remove an exercise in selected cells
|
46 |
+
*
|
47 |
+
* @method create_remove_exercise
|
48 |
+
*
|
49 |
+
*/
|
50 |
+
function create_remove_exercise() {
|
51 |
+
var lcells = IPython.notebook.get_selected_cells();
|
52 |
+
// It is possible that no cell is selected
|
53 |
+
if (lcells.length < 1) {
|
54 |
+
alert("Exercise extension: \nPlease select some cells...");
|
55 |
+
return;
|
56 |
+
}
|
57 |
+
|
58 |
+
var cell = lcells[0];
|
59 |
+
if (cell.metadata.solution2_first) {
|
60 |
+
remove_element(cell);
|
61 |
+
delete cell.metadata.solution2_first;
|
62 |
+
while (cell !== null && cell.metadata.solution2 !== undefined && !cell.metadata.solution2_first) {
|
63 |
+
delete cell.metadata.solution2;
|
64 |
+
cell.element.removeClass('hidden');
|
65 |
+
cell = IPython.notebook.get_next_cell(cell);
|
66 |
+
}
|
67 |
+
}
|
68 |
+
else {
|
69 |
+
cell.metadata.solution2_first = true;
|
70 |
+
cell.metadata.solution2 = 'hidden';
|
71 |
+
add_element(cell);
|
72 |
+
for (var k = 1; k < lcells.length; k++) {
|
73 |
+
cell = lcells[k];
|
74 |
+
cell.element.addClass('hidden');
|
75 |
+
cell.metadata.solution2 = 'hidden';
|
76 |
+
}
|
77 |
+
}
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Add a lock control to the given cell
|
82 |
+
*/
|
83 |
+
var cbx = 0;
|
84 |
+
function add_element(cell) {
|
85 |
+
var ctrl = cell.element.find('.exercise');
|
86 |
+
if (ctrl.length > 0) return ctrl;
|
87 |
+
var locked = cell.metadata.solution2 === 'hidden';
|
88 |
+
cell.element.css('flex-wrap', 'wrap');
|
89 |
+
cbx += 1;
|
90 |
+
ctrl = $([
|
91 |
+
'<div class="exercise exercise2">',
|
92 |
+
' <div class="prompt"></div>',
|
93 |
+
' <div class="onoffswitch">',
|
94 |
+
' <input class="onoffswitch-checkbox" type="checkbox" id="myCheck' + cbx + '">',
|
95 |
+
' <label class="onoffswitch-label" for="myCheck' + cbx + '">',
|
96 |
+
' <div class="onoffswitch-inner"></div>',
|
97 |
+
' <div class="onoffswitch-switch"></div>',
|
98 |
+
' </label>',
|
99 |
+
' </div>',
|
100 |
+
'</div>'
|
101 |
+
].join('\n'))
|
102 |
+
.appendTo(cell.element);
|
103 |
+
ctrl.find('input')
|
104 |
+
.on('click', click_solution_lock);
|
105 |
+
element_set_locked(cell, locked);
|
106 |
+
return ctrl;
|
107 |
+
}
|
108 |
+
|
109 |
+
function remove_element(cell) {
|
110 |
+
cell.element.find('.exercise').remove();
|
111 |
+
}
|
112 |
+
|
113 |
+
function element_set_locked(cell, locked) {
|
114 |
+
return cell.element.find('.exercise')
|
115 |
+
.prop('checked', !locked);
|
116 |
+
}
|
117 |
+
|
118 |
+
function refresh_exercises() {
|
119 |
+
var in_exercise = false;
|
120 |
+
IPython.notebook.get_cells().forEach(function(cell) {
|
121 |
+
if (in_exercise && cell.metadata.solution2 !== undefined && !cell.metadata.solution2_first) {
|
122 |
+
cell.element.toggleClass('hidden', cell.metadata.solution2 === 'hidden');
|
123 |
+
} else {
|
124 |
+
in_exercise = false;
|
125 |
+
}
|
126 |
+
if (!in_exercise && cell.metadata.solution2 !== undefined) {
|
127 |
+
in_exercise = true;
|
128 |
+
add_element(cell);
|
129 |
+
}
|
130 |
+
});
|
131 |
+
}
|
132 |
+
|
133 |
+
function load_ipython_extension() {
|
134 |
+
// add css
|
135 |
+
$('<link rel="stylesheet" type="text/css">')
|
136 |
+
.attr('href', requirejs.toUrl('./main.css'))
|
137 |
+
.appendTo('head');
|
138 |
+
|
139 |
+
// Hide/display existing solutions at startup
|
140 |
+
events.on('notebook_loaded.Notebook', refresh_exercises);
|
141 |
+
if (IPython.notebook._fully_loaded) refresh_exercises();
|
142 |
+
|
143 |
+
var action_name = IPython.keyboard_manager.actions.register({
|
144 |
+
help : 'Exercise2: Create/Remove exercise',
|
145 |
+
help_index: 'ht',
|
146 |
+
icon : 'fa-toggle-on',
|
147 |
+
handler : create_remove_exercise,
|
148 |
+
}, 'create_remove_exercise', 'exercise2');
|
149 |
+
|
150 |
+
return IPython.notebook.config.loaded.then(function() {
|
151 |
+
$.extend(true, cfg, IPython.notebook.config.data.exercise2);
|
152 |
+
|
153 |
+
if (cfg.add_button) {
|
154 |
+
IPython.toolbar.add_buttons_group([action_name]);
|
155 |
+
}
|
156 |
+
if (cfg.use_hotkey && cfg.hotkey) {
|
157 |
+
var cmd_shrts = {};
|
158 |
+
cmd_shrts[cfg.hotkey] = action_name;
|
159 |
+
IPython.keyboard_manager.command_shortcuts.add_shortcuts(cmd_shrts);
|
160 |
+
}
|
161 |
+
}).catch(function(err) {
|
162 |
+
console.warn('[exercise2] error:', err);
|
163 |
+
});
|
164 |
+
}
|
165 |
+
|
166 |
+
return {
|
167 |
+
load_ipython_extension: load_ipython_extension,
|
168 |
+
};
|
169 |
+
});
|
.local/share/jupyter/nbextensions/export_embedded/export_embedded.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Type: Jupyter Notebook Extension
|
2 |
+
Compatibility: 5.x
|
3 |
+
Main: main.js
|
4 |
+
Name: Export Embedded HTML
|
5 |
+
Description: Export to HTML with images embedded
|
6 |
+
Icon: icon.png
|
7 |
+
Link: readme.md
|
.local/share/jupyter/nbextensions/export_embedded/main.js
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// toggle display of all code cells' inputs
|
2 |
+
|
3 |
+
define([
|
4 |
+
'jquery',
|
5 |
+
'base/js/namespace',
|
6 |
+
'base/js/events'
|
7 |
+
], function(
|
8 |
+
$,
|
9 |
+
Jupyter,
|
10 |
+
events
|
11 |
+
) {
|
12 |
+
"use strict";
|
13 |
+
|
14 |
+
function initialize () {
|
15 |
+
}
|
16 |
+
|
17 |
+
var load_ipython_extension = function() {
|
18 |
+
|
19 |
+
var v = Jupyter.version.split(".")
|
20 |
+
if(Number(v[0])*10+ Number(v[1]) < 51)
|
21 |
+
{
|
22 |
+
console.log('Notebook version 5.1.0 or higher required for this extension')
|
23 |
+
return
|
24 |
+
}
|
25 |
+
|
26 |
+
/* Add an entry in the download menu */
|
27 |
+
var dwm = $("#download_menu")
|
28 |
+
var downloadEntry = $('<li id="download_html_embed"><a href="#">HTML Embedded (.html)</a></li>')
|
29 |
+
dwm.append(downloadEntry)
|
30 |
+
downloadEntry.click(function () {
|
31 |
+
Jupyter.menubar._nbconvert('html_embed', true);
|
32 |
+
});
|
33 |
+
|
34 |
+
/* Add also a Button, currently disabled */
|
35 |
+
/*
|
36 |
+
Jupyter.toolbar.add_buttons_group([
|
37 |
+
Jupyter.keyboard_manager.actions.register ({
|
38 |
+
help : 'Embedded HTML Export',
|
39 |
+
icon : 'fa-save',
|
40 |
+
handler: function() {
|
41 |
+
Jupyter.menubar._nbconvert('html_embed', true);
|
42 |
+
}
|
43 |
+
}, 'export-embedded-html', 'export_embedded')
|
44 |
+
]);
|
45 |
+
*/
|
46 |
+
if (Jupyter.notebook !== undefined && Jupyter.notebook._fully_loaded) {
|
47 |
+
// notebook_loaded.Notebook event has already happened
|
48 |
+
initialize();
|
49 |
+
}
|
50 |
+
events.on('notebook_loaded.Notebook', initialize);
|
51 |
+
};
|
52 |
+
|
53 |
+
return {
|
54 |
+
load_ipython_extension : load_ipython_extension
|
55 |
+
};
|
56 |
+
});
|
.local/share/jupyter/nbextensions/export_embedded/readme.md
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Export HTML With Embedded Images
|
2 |
+
================================
|
3 |
+
This extension allows exporting an embedded HTML by an additional download option in File -> Download -> HTML Embedded, (works like: jupyter nbconvert --to html_embed notebook.ipynb)
|
4 |
+
|
5 |
+
**Note**: This extension can so far only successfully read relative images paths in the markdown cells (e.g. `![](graphics/pic.png)`) when jupyter is started in the same folder (working directory) where the relative paths can be resolved!
|
6 |
+
|
7 |
+
|
.local/share/jupyter/nbextensions/freeze/config.yaml
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Type: IPython Notebook Extension
|
2 |
+
Name: Freeze
|
3 |
+
Description: Freeze cells (forbid editing and executing) or make them read-only
|
4 |
+
Link: readme.md
|
5 |
+
Icon: icon.png
|
6 |
+
Main: main.js
|
7 |
+
Compatibility: 4.x, 5.x
|
8 |
+
Parameters:
|
9 |
+
- name: Freeze.readonly_color
|
10 |
+
description: |
|
11 |
+
Color to use for read-only cell
|
12 |
+
default: '#fffef0'
|
13 |
+
input_type: color
|
14 |
+
|
15 |
+
- name: Freeze.frozen_color
|
16 |
+
description: |
|
17 |
+
Color to use for frozen cell
|
18 |
+
default: '#f0feff'
|
19 |
+
input_type: color
|
20 |
+
|
.local/share/jupyter/nbextensions/freeze/icon.png
ADDED
.local/share/jupyter/nbextensions/freeze/main.js
ADDED
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
define([
|
2 |
+
'base/js/namespace',
|
3 |
+
'base/js/events',
|
4 |
+
'notebook/js/codecell',
|
5 |
+
'notebook/js/textcell',
|
6 |
+
'jquery'
|
7 |
+
], function (
|
8 |
+
Jupyter,
|
9 |
+
events,
|
10 |
+
codecell,
|
11 |
+
textcell,
|
12 |
+
$
|
13 |
+
) {
|
14 |
+
'use strict';
|
15 |
+
|
16 |
+
var CodeCell = codecell.CodeCell;
|
17 |
+
var MarkdownCell = textcell.MarkdownCell;
|
18 |
+
|
19 |
+
var mod_name = 'Freeze';
|
20 |
+
var log_prefix = '[' + mod_name + ']';
|
21 |
+
|
22 |
+
// defaults, overridden by server's config
|
23 |
+
var options = {
|
24 |
+
readonly_color: '#fffef0',
|
25 |
+
frozen_color: '#f0feff'
|
26 |
+
};
|
27 |
+
|
28 |
+
function patch_MarkdownCell_unrender () {
|
29 |
+
console.log('[Freeze] patching MarkdownCell.prototype.unrender');
|
30 |
+
var old_unrender = MarkdownCell.prototype.unrender;
|
31 |
+
|
32 |
+
MarkdownCell.prototype.unrender = function () {
|
33 |
+
// console.log('[Freeze] patched unrender applied');
|
34 |
+
if (this.metadata.run_control === undefined ||
|
35 |
+
!this.metadata.run_control.frozen
|
36 |
+
) {
|
37 |
+
old_unrender.apply(this, arguments);
|
38 |
+
}
|
39 |
+
};
|
40 |
+
}
|
41 |
+
|
42 |
+
function patch_CodeCell_execute () {
|
43 |
+
console.log('[Freeze] patching CodeCell.prototype.execute');
|
44 |
+
var old_execute = CodeCell.prototype.execute;
|
45 |
+
|
46 |
+
CodeCell.prototype.execute = function () {
|
47 |
+
if (this.metadata.run_control === undefined ||
|
48 |
+
!this.metadata.run_control.frozen
|
49 |
+
) {
|
50 |
+
old_execute.apply(this, arguments);
|
51 |
+
}
|
52 |
+
};
|
53 |
+
}
|
54 |
+
|
55 |
+
// Migrate old metadata format to new notebook-defined metadata.editable
|
56 |
+
function migrate_state (cell) {
|
57 |
+
if (cell.metadata.run_control !== undefined) {
|
58 |
+
if (cell instanceof CodeCell || cell instanceof MarkdownCell) {
|
59 |
+
if (cell.metadata.run_control.read_only === true) {
|
60 |
+
cell.metadata.editable = false;
|
61 |
+
}
|
62 |
+
}
|
63 |
+
else {
|
64 |
+
// remove metadata irrelevant to non-code/markdown cells
|
65 |
+
delete cell.metadata.run_control.frozen;
|
66 |
+
}
|
67 |
+
// remove old key replaced by metadata.editable
|
68 |
+
delete cell.metadata.run_control.read_only;
|
69 |
+
// remove whole object if it's now empty
|
70 |
+
if (Object.keys(cell.metadata.run_control).length === 0) {
|
71 |
+
delete cell.metadata.run_control;
|
72 |
+
}
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
function get_state (cell) {
|
77 |
+
if (cell.metadata.editable === false && (cell instanceof CodeCell || cell instanceof MarkdownCell)) {
|
78 |
+
if (cell.metadata.run_control !== undefined && cell.metadata.run_control.frozen) {
|
79 |
+
return 'frozen';
|
80 |
+
}
|
81 |
+
return 'readonly';
|
82 |
+
}
|
83 |
+
return 'normal';
|
84 |
+
}
|
85 |
+
|
86 |
+
function set_state(cell, state) {
|
87 |
+
if (!(cell instanceof CodeCell || cell instanceof MarkdownCell)) {
|
88 |
+
return;
|
89 |
+
}
|
90 |
+
|
91 |
+
state = state || 'normal';
|
92 |
+
var bg;
|
93 |
+
switch (state) {
|
94 |
+
case 'normal':
|
95 |
+
cell.metadata.editable = true;
|
96 |
+
cell.metadata.deletable = true;
|
97 |
+
if (cell.metadata.run_control !== undefined) {
|
98 |
+
delete cell.metadata.run_control.frozen;
|
99 |
+
}
|
100 |
+
bg = "";
|
101 |
+
break;
|
102 |
+
case 'read_only':
|
103 |
+
case 'readonly':
|
104 |
+
cell.metadata.editable = false;
|
105 |
+
cell.metadata.deletable = false;
|
106 |
+
if (cell.metadata.run_control !== undefined) {
|
107 |
+
delete cell.metadata.run_control.frozen;
|
108 |
+
}
|
109 |
+
bg = options.readonly_color;
|
110 |
+
break;
|
111 |
+
case 'frozen':
|
112 |
+
cell.metadata.editable = false;
|
113 |
+
cell.metadata.deletable = false;
|
114 |
+
$.extend(true, cell.metadata, {run_control: {frozen: true}});
|
115 |
+
bg = options.frozen_color;
|
116 |
+
break;
|
117 |
+
}
|
118 |
+
// remove whole object if it's now empty
|
119 |
+
if (cell.metadata.run_control !== undefined && Object.keys(cell.metadata.run_control).length === 0) {
|
120 |
+
delete cell.metadata.run_control;
|
121 |
+
}
|
122 |
+
cell.code_mirror.setOption('readOnly', !cell.metadata.editable);
|
123 |
+
var prompt = cell.element.find('div.input_area');
|
124 |
+
prompt.css("background-color", bg);
|
125 |
+
}
|
126 |
+
|
127 |
+
function set_state_selected (state) {
|
128 |
+
var cells = Jupyter.notebook.get_selected_cells();
|
129 |
+
for (var i = 0; i < cells.length; i++) {
|
130 |
+
set_state(cells[i], state);
|
131 |
+
}
|
132 |
+
}
|
133 |
+
|
134 |
+
function button_callback(state) {
|
135 |
+
set_state_selected(state);
|
136 |
+
var dirty_state = {value: true};
|
137 |
+
events.trigger("set_dirty.Notebook", dirty_state);
|
138 |
+
}
|
139 |
+
|
140 |
+
function make_normal_selected () {
|
141 |
+
button_callback('normal');
|
142 |
+
}
|
143 |
+
|
144 |
+
function make_read_only_selected () {
|
145 |
+
button_callback('read_only');
|
146 |
+
}
|
147 |
+
|
148 |
+
function make_frozen_selected () {
|
149 |
+
button_callback('frozen');
|
150 |
+
}
|
151 |
+
|
152 |
+
function initialize_states () {
|
153 |
+
var cells = Jupyter.notebook.get_cells();
|
154 |
+
for (var i = 0; i < cells.length; i++) {
|
155 |
+
var cell = cells[i];
|
156 |
+
migrate_state(cell);
|
157 |
+
var state = get_state(cell);
|
158 |
+
set_state(cell, state);
|
159 |
+
}
|
160 |
+
}
|
161 |
+
|
162 |
+
function load_extension () {
|
163 |
+
Jupyter.toolbar.add_buttons_group([
|
164 |
+
Jupyter.keyboard_manager.actions.register ({
|
165 |
+
help : 'lift restrictions from selected cells',
|
166 |
+
icon : 'fa-unlock-alt',
|
167 |
+
handler : make_normal_selected
|
168 |
+
}, 'make-cells-normal', mod_name),
|
169 |
+
Jupyter.keyboard_manager.actions.register({
|
170 |
+
help : 'make selected cells read-only',
|
171 |
+
icon: 'fa-lock',
|
172 |
+
handler : make_read_only_selected
|
173 |
+
}, 'make-cells-read-only', mod_name),
|
174 |
+
Jupyter.keyboard_manager.actions.register({
|
175 |
+
help : 'freeze selected cells',
|
176 |
+
icon : 'fa-asterisk',
|
177 |
+
handler : make_frozen_selected
|
178 |
+
}, 'freeze-cells', mod_name)
|
179 |
+
]);
|
180 |
+
|
181 |
+
patch_CodeCell_execute();
|
182 |
+
patch_MarkdownCell_unrender();
|
183 |
+
|
184 |
+
Jupyter.notebook.config.loaded.then(function on_config_loaded () {
|
185 |
+
$.extend(true, options, Jupyter.notebook.config.data[mod_name]);
|
186 |
+
}, function on_config_load_error (reason) {
|
187 |
+
console.warn(log_prefix, 'Using defaults after error loading config:', reason);
|
188 |
+
}).then(function do_stuff_with_config () {
|
189 |
+
events.on("notebook_loaded.Notebook", initialize_states);
|
190 |
+
if (Jupyter.notebook !== undefined && Jupyter.notebook._fully_loaded) {
|
191 |
+
// notebook already loaded, so we missed the event, so update all
|
192 |
+
initialize_states();
|
193 |
+
}
|
194 |
+
}).catch(function on_error (reason) {
|
195 |
+
console.error(log_prefix, 'Error:', reason);
|
196 |
+
});
|
197 |
+
}
|
198 |
+
|
199 |
+
return {
|
200 |
+
get_state : get_state,
|
201 |
+
set_state : set_state,
|
202 |
+
load_jupyter_extension : load_extension,
|
203 |
+
load_ipython_extension : load_extension
|
204 |
+
};
|
205 |
+
});
|
.local/share/jupyter/nbextensions/freeze/readme.md
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Freeze
|
2 |
+
|
3 |
+
This extension allows to make cells read-only or frozen. It provides three buttons:
|
4 |
+
* unlock
|
5 |
+
* read-only
|
6 |
+
* frozen
|
7 |
+
|
8 |
+
|
9 |
+
For **code-cells**:<br>
|
10 |
+
_read-only_: it can be executed, but its input cannot be changed.<br>
|
11 |
+
_frozen_: It cannot be either altered or executed.
|
12 |
+
|
13 |
+
For **markdown-cells**:<br>
|
14 |
+
_read-only_: It's input can be viewed by double-clicking on it, but cannot be changed.<br>
|
15 |
+
_frozen_: Input cannot be viewed by double-clicking.
|
16 |
+
|
17 |
+
To change the state of a selected cell, press the corresponding button.
|
18 |
+
|
19 |
+
The individual cell's state is stored in its metadata and is applied to the cell if the extension is loaded.
|
20 |
+
|
21 |
+
## Internals
|
22 |
+
|
23 |
+
The _read-only_ state is stored in the `cell.metadata.editable` attribute. Cells are editable by default.
|
24 |
+
The _frozen_ state is stored in the `cell.metadata.run_control.frozen`attribute.
|
.local/share/jupyter/nbextensions/gist_it/gist_it.yaml
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Type: IPython Notebook Extension
|
2 |
+
Compatibility: 3.x, 4.x, 5.x
|
3 |
+
Name: Gist-it
|
4 |
+
Main: main.js
|
5 |
+
Description: Adds a button to publish the current notebook as a gist. See the readme for description of the authentication options and relevant parameters.
|
6 |
+
Link: readme.md
|
7 |
+
Icon: icon.png
|
8 |
+
Parameters:
|
9 |
+
- name: gist_it_personal_access_token
|
10 |
+
description: Github personal access token. To write gists on a user's behalf, you need a token with the <a href="https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/">gist OAuth scope</a>.
|
11 |
+
input_type: text
|
12 |
+
- name: gist_it_default_to_public
|
13 |
+
description: Gists default to public. If using a personal access token, gists will default to private. Set this to have them default to being public instead.
|
14 |
+
input_type: checkbox
|
15 |
+
- name: github_endpoint
|
16 |
+
description: Github endpoint. Defaults to 'github.com'. Change this to publish to your enterprise github endpoint.
|
17 |
+
input_type: text
|
18 |
+
default: 'github.com'
|
.local/share/jupyter/nbextensions/gist_it/icon.png
ADDED
.local/share/jupyter/nbextensions/gist_it/main.js
ADDED
@@ -0,0 +1,481 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
*
|
3 |
+
// Avoid server side code :
|
4 |
+
// https://github.com/ipython/ipython/issues/2780
|
5 |
+
*
|
6 |
+
* This essentially boils down to the following:
|
7 |
+
* Github authentication requires some server-side code for any 'app' which
|
8 |
+
* wants to authenticate over the Github API.
|
9 |
+
* When registering an app with Github, Github provides the app with what they
|
10 |
+
* call a 'client secret'.
|
11 |
+
* The client secret is then incorporated into the app, and the app sends it to
|
12 |
+
* Github as part of the authentication process, thus proving to Github's
|
13 |
+
* servers that the communicating app was written by someone with appropriate
|
14 |
+
* credentials.
|
15 |
+
*
|
16 |
+
* The issue with writing a single Github app for Gist-ing notebooks, is that
|
17 |
+
* it would need to include such a client secret. Since this would be part of
|
18 |
+
* the extension source code, anyone could use the client secret, potentially
|
19 |
+
* gaining the permissions that any given user has granted to the app.
|
20 |
+
*
|
21 |
+
* As a result, we only support:
|
22 |
+
* - anonymous (non-authenticated) API usage
|
23 |
+
* - client-side authentication using a personal access token
|
24 |
+
* (see https://github.com/settings/tokens)
|
25 |
+
*/
|
26 |
+
|
27 |
+
define([
|
28 |
+
'jquery',
|
29 |
+
'base/js/namespace',
|
30 |
+
'base/js/dialog'
|
31 |
+
], function (
|
32 |
+
$,
|
33 |
+
Jupyter,
|
34 |
+
dialog
|
35 |
+
) {
|
36 |
+
"use strict";
|
37 |
+
|
38 |
+
// define default values for config parameters
|
39 |
+
var params = {
|
40 |
+
gist_it_default_to_public: false,
|
41 |
+
gist_it_personal_access_token: '',
|
42 |
+
github_endpoint: 'github.com'
|
43 |
+
};
|
44 |
+
|
45 |
+
var initialize = function () {
|
46 |
+
update_params();
|
47 |
+
Jupyter.toolbar.add_buttons_group([
|
48 |
+
Jupyter.keyboard_manager.actions.register ({
|
49 |
+
help : 'Create/Edit Gist of Notebook',
|
50 |
+
icon : 'fa-github',
|
51 |
+
handler: show_gist_editor_modal
|
52 |
+
}, 'create-gist-from-notebook', 'gist_it')
|
53 |
+
]);
|
54 |
+
};
|
55 |
+
|
56 |
+
// update params with any specified in the server's config file
|
57 |
+
var update_params = function() {
|
58 |
+
var config = Jupyter.notebook.config;
|
59 |
+
for (var key in params) {
|
60 |
+
if (config.data.hasOwnProperty(key))
|
61 |
+
params[key] = config.data[key];
|
62 |
+
}
|
63 |
+
default_metadata.data.public = Boolean(config.data.gist_it_default_to_public);
|
64 |
+
};
|
65 |
+
|
66 |
+
var default_metadata = {
|
67 |
+
id: '',
|
68 |
+
data: {
|
69 |
+
description: Jupyter.notebook.notebook_path,
|
70 |
+
public: false
|
71 |
+
}
|
72 |
+
};
|
73 |
+
|
74 |
+
function ensure_default_metadata () {
|
75 |
+
Jupyter.notebook.metadata.gist = $.extend(
|
76 |
+
true, // deep-copy
|
77 |
+
default_metadata, //defaults
|
78 |
+
Jupyter.notebook.metadata.gist // overrides
|
79 |
+
);
|
80 |
+
}
|
81 |
+
|
82 |
+
var add_auth_token = function add_auth_token (xhr) {
|
83 |
+
var token = '';
|
84 |
+
if (params.gist_it_personal_access_token !== '') {
|
85 |
+
token = params.gist_it_personal_access_token;
|
86 |
+
}
|
87 |
+
if (token !== '') {
|
88 |
+
xhr.setRequestHeader("Authorization", "token " + token);
|
89 |
+
}
|
90 |
+
};
|
91 |
+
|
92 |
+
function build_alert(alert_class) {
|
93 |
+
return $('<div/>')
|
94 |
+
.addClass('alert alert-dismissable')
|
95 |
+
.addClass(alert_class)
|
96 |
+
.append(
|
97 |
+
$('<button class="close" type="button" data-dismiss="alert" aria-label="Close"/>')
|
98 |
+
.append($('<span aria-hidden="true"/>').html('×'))
|
99 |
+
);
|
100 |
+
}
|
101 |
+
|
102 |
+
function gist_error (jqXHR, textStatus, errorThrown) {
|
103 |
+
console.log('github ajax error:', jqXHR, textStatus, errorThrown);
|
104 |
+
var alert = build_alert('alert-danger')
|
105 |
+
.hide()
|
106 |
+
.append(
|
107 |
+
$('<p/>').text('The ajax request to Github went wrong:')
|
108 |
+
)
|
109 |
+
.append(
|
110 |
+
$('<pre/>').text(jqXHR.responseJSON ? JSON.stringify(jqXHR.responseJSON, null, 2) : errorThrown)
|
111 |
+
);
|
112 |
+
$('#gist_modal').find('.modal-body').append(alert);
|
113 |
+
alert.slideDown('fast');
|
114 |
+
}
|
115 |
+
|
116 |
+
function gist_success (response, textStatus, jqXHR) {
|
117 |
+
// if (Jupyter.notebook.metadata.gist.id === response.id) return;
|
118 |
+
|
119 |
+
Jupyter.notebook.metadata.gist.id = response.id;
|
120 |
+
Jupyter.notebook.metadata._draft = $.extend(
|
121 |
+
true, // deep copy
|
122 |
+
Jupyter.notebook.metadata._draft, // defaults
|
123 |
+
{nbviewer_url: response.html_url} // overrides
|
124 |
+
);
|
125 |
+
|
126 |
+
var d = new Date();
|
127 |
+
var msg_head = d.toLocaleString() + ': Gist ';
|
128 |
+
var msg_tail = response.history.length === 1 ? ' published' : ' updated to revision ' + response.history.length;
|
129 |
+
var alert = build_alert('alert-success')
|
130 |
+
.hide()
|
131 |
+
.append(msg_head)
|
132 |
+
.append(
|
133 |
+
$('<a/>')
|
134 |
+
.attr('href', response.html_url)
|
135 |
+
.attr('target', '_blank')
|
136 |
+
.text(response.id)
|
137 |
+
)
|
138 |
+
.append(msg_tail);
|
139 |
+
$('#gist_modal').find('.modal-body').append(alert);
|
140 |
+
alert.slideDown('fast');
|
141 |
+
}
|
142 |
+
|
143 |
+
function get_github_endpoint() {
|
144 |
+
return params.github_endpoint !== '' ? params.github_endpoint : 'github.com';
|
145 |
+
}
|
146 |
+
|
147 |
+
function get_api_endpoint() {
|
148 |
+
const github_endpoint = get_github_endpoint();
|
149 |
+
if (github_endpoint === 'github.com') {
|
150 |
+
return 'https://api.'+ github_endpoint;
|
151 |
+
} else {
|
152 |
+
// Github Enterprise
|
153 |
+
// https://developer.github.com/enterprise/2.18/v3/enterprise-admin/#endpoint-urls
|
154 |
+
return 'https://' + github_endpoint + '/api/v3'
|
155 |
+
}
|
156 |
+
}
|
157 |
+
|
158 |
+
function gist_id_updated_callback(gist_editor) {
|
159 |
+
if (gist_editor === undefined) gist_editor = $('#gist_editor');
|
160 |
+
|
161 |
+
var id_input = gist_editor.find('#gist_id');
|
162 |
+
var id = id_input.val();
|
163 |
+
|
164 |
+
var help_block = gist_editor.find('#gist_id ~ .help-block');
|
165 |
+
var help_block_base_text = 'Set the gist id to update an existing gist, ' +
|
166 |
+
'or leave blank to create a new one.';
|
167 |
+
|
168 |
+
var gist_it_button = $('#gist_modal').find('.btn-primary');
|
169 |
+
|
170 |
+
id_input.parent()
|
171 |
+
.removeClass('has-success has-error has-warning')
|
172 |
+
.find('#gist_id ~ .form-control-feedback > i.fa')
|
173 |
+
.removeClass('fa-pencil-square fa-exclamation-circle fa-question-circle');
|
174 |
+
|
175 |
+
if (id === '') {
|
176 |
+
$('#gist_id ~ .form-control-feedback > i.fa')
|
177 |
+
.addClass('fa-plus-circle');
|
178 |
+
help_block.html(
|
179 |
+
'<p>' + help_block_base_text + '</p>' +
|
180 |
+
'<p><i class="fa fa-plus-circle"></i> a new gist will be created</p>'
|
181 |
+
);
|
182 |
+
gist_it_button.prop('disabled', false);
|
183 |
+
}
|
184 |
+
else {
|
185 |
+
$('#gist_id ~ .form-control-feedback > i.fa')
|
186 |
+
.addClass('fa-circle-o-notch fa-spin');
|
187 |
+
// List commits as a way of checking whether the gist exists.
|
188 |
+
// Listing commits appears to give the most concise response.
|
189 |
+
|
190 |
+
$.ajax({
|
191 |
+
url: get_api_endpoint() +'/gists/' + id + '/commits',
|
192 |
+
dataType: 'json',
|
193 |
+
beforeSend: add_auth_token,
|
194 |
+
error: function(jqXHR, textStatus, errorThrown) {
|
195 |
+
jqXHR.errorThrown = errorThrown;
|
196 |
+
},
|
197 |
+
complete: function(jqXHR, textStatus) {
|
198 |
+
var success = textStatus === 'success';
|
199 |
+
var error = !success && jqXHR.status === 404 && jqXHR.responseJSON !== undefined;
|
200 |
+
var warning = !success && !error;
|
201 |
+
|
202 |
+
var help_block_html = '<p>' + help_block_base_text + '</p>';
|
203 |
+
|
204 |
+
gist_it_button.prop('disabled', error);
|
205 |
+
if (success) {
|
206 |
+
var single = (jqXHR.responseJSON.length === 1);
|
207 |
+
help_block_html += '<p>' +
|
208 |
+
'<i class="fa fa-pencil-square"></i>' +
|
209 |
+
' gist ' +
|
210 |
+
'<a href="https://'+ get_github_endpoint() + '/gist/' + id +
|
211 |
+
'" target="_blank">' + id + '</a> will be updated' +
|
212 |
+
' (' + jqXHR.responseJSON.length +
|
213 |
+
' revision' + (single ? '' : 's') +
|
214 |
+
' exist' + (single ? 's' : '') + ' so far)' +
|
215 |
+
'</p>';
|
216 |
+
}
|
217 |
+
else if (error) {
|
218 |
+
help_block_html += '<p>' +
|
219 |
+
'<i class="fa fa-exclamation-circle"></i>' +
|
220 |
+
' no gist exists with the specified id (given current access token)'+
|
221 |
+
'</p>';
|
222 |
+
}
|
223 |
+
else {
|
224 |
+
help_block_html += '<p>' +
|
225 |
+
'<i class="fa fa-question-circle"></i>' +
|
226 |
+
' can\'t list commits for the specified gist id - you may have problems updating it!' +
|
227 |
+
'</p>';
|
228 |
+
help_block_html += '<p>The ajax request to Github went wrong:<p/>' +
|
229 |
+
'<pre>';
|
230 |
+
if (jqXHR.responseJSON) {
|
231 |
+
help_block_html += JSON.stringify(jqXHR.responseJSON, null, 2);
|
232 |
+
}
|
233 |
+
else {
|
234 |
+
help_block_html += jqXHR.errorThrown || textStatus;
|
235 |
+
}
|
236 |
+
help_block_html += '</pre>';
|
237 |
+
console.log('non-404 github ajax error:', jqXHR, textStatus);
|
238 |
+
}
|
239 |
+
help_block.html(help_block_html);
|
240 |
+
|
241 |
+
id_input.parent()
|
242 |
+
.toggleClass('has-success', success)
|
243 |
+
.toggleClass('has-error', error)
|
244 |
+
.toggleClass('has-warning', warning)
|
245 |
+
.find('#gist_id ~ .form-control-feedback > i.fa')
|
246 |
+
.removeClass('fa-circle-o-notch fa-spin')
|
247 |
+
.toggleClass('fa-pencil-square', success)
|
248 |
+
.toggleClass('fa-exclamation-circle', error)
|
249 |
+
.toggleClass('fa-question-circle', warning);
|
250 |
+
}
|
251 |
+
});
|
252 |
+
}
|
253 |
+
}
|
254 |
+
|
255 |
+
function update_gist_editor (gist_editor) {
|
256 |
+
if (gist_editor === undefined) gist_editor = $('#gist_editor');
|
257 |
+
|
258 |
+
var id_input = gist_editor.find('#gist_id');
|
259 |
+
|
260 |
+
var have_auth = params.gist_it_personal_access_token !== '';
|
261 |
+
var id = '';
|
262 |
+
var is_public = true;
|
263 |
+
if (have_auth) {
|
264 |
+
id = Jupyter.notebook.metadata.gist.id;
|
265 |
+
is_public = Jupyter.notebook.metadata.gist.data.public;
|
266 |
+
id_input.val(id);
|
267 |
+
}
|
268 |
+
id_input.closest('.form-group').toggle(have_auth);
|
269 |
+
|
270 |
+
gist_editor.find('#gist_public')
|
271 |
+
.prop('checked', is_public)
|
272 |
+
.prop('readonly', !have_auth);
|
273 |
+
|
274 |
+
gist_editor.find('#gist_description')
|
275 |
+
.val(Jupyter.notebook.metadata.gist.data.description);
|
276 |
+
|
277 |
+
if (have_auth) {
|
278 |
+
gist_id_updated_callback(gist_editor);
|
279 |
+
}
|
280 |
+
}
|
281 |
+
|
282 |
+
function build_gist_editor () {
|
283 |
+
ensure_default_metadata();
|
284 |
+
|
285 |
+
var gist_editor = $('#gist_editor');
|
286 |
+
|
287 |
+
if (gist_editor.length > 0) return gist_editor;
|
288 |
+
|
289 |
+
gist_editor = $('<div/>').attr('id', 'gist_editor').append(controls);
|
290 |
+
|
291 |
+
var id = params.gist_it_personal_access_token !== '' ? Jupyter.notebook.metadata.gist.id : '';
|
292 |
+
var controls = $('<form/>')
|
293 |
+
.appendTo(gist_editor)
|
294 |
+
.addClass('form-horizontal');
|
295 |
+
|
296 |
+
$('<div/>')
|
297 |
+
.addClass('has-feedback')
|
298 |
+
.hide()
|
299 |
+
.appendTo(controls)
|
300 |
+
.append(
|
301 |
+
$('<label/>')
|
302 |
+
.attr('for', 'gist_id')
|
303 |
+
.text('Gist id')
|
304 |
+
)
|
305 |
+
.append(
|
306 |
+
$('<input/>')
|
307 |
+
.addClass('form-control')
|
308 |
+
.attr('id', 'gist_id')
|
309 |
+
.val(Jupyter.notebook.metadata.gist.id)
|
310 |
+
)
|
311 |
+
.append(
|
312 |
+
$('<span/>')
|
313 |
+
.addClass('form-control-feedback')
|
314 |
+
.append(
|
315 |
+
$('<i/>')
|
316 |
+
.addClass('fa fa-lg')
|
317 |
+
)
|
318 |
+
)
|
319 |
+
.append(
|
320 |
+
$('<span/>')
|
321 |
+
.addClass('help-block')
|
322 |
+
);
|
323 |
+
$('<div/>')
|
324 |
+
.appendTo(controls)
|
325 |
+
.append(
|
326 |
+
$('<div/>')
|
327 |
+
.addClass('checkbox')
|
328 |
+
.append(
|
329 |
+
$('<label>')
|
330 |
+
.text('Make the gist public')
|
331 |
+
.prepend(
|
332 |
+
$('<input/>')
|
333 |
+
.attr('type', 'checkbox')
|
334 |
+
.attr('id', 'gist_public')
|
335 |
+
.prop('checked', Jupyter.notebook.metadata.gist.data.public)
|
336 |
+
.prop('readonly', id === '')
|
337 |
+
)
|
338 |
+
)
|
339 |
+
)
|
340 |
+
.append(
|
341 |
+
$('<label/>')
|
342 |
+
.attr('for', 'gist_public')
|
343 |
+
.text('public')
|
344 |
+
);
|
345 |
+
$('<div/>')
|
346 |
+
.appendTo(controls)
|
347 |
+
.append(
|
348 |
+
$('<label/>')
|
349 |
+
.attr('for', 'gist_description')
|
350 |
+
.text('description')
|
351 |
+
)
|
352 |
+
.append(
|
353 |
+
$('<input/>')
|
354 |
+
.addClass('form-control')
|
355 |
+
.attr('id', 'gist_description')
|
356 |
+
.attr('type', 'textarea')
|
357 |
+
.val(Jupyter.notebook.metadata.gist.data.description)
|
358 |
+
);
|
359 |
+
|
360 |
+
var form_groups = controls.children('div').addClass('form-group');
|
361 |
+
form_groups
|
362 |
+
.children('label')
|
363 |
+
.addClass('col-sm-2 control-label')
|
364 |
+
.css('padding-right', '1em');
|
365 |
+
form_groups
|
366 |
+
.each(function (index, elem) {
|
367 |
+
$('<div/>')
|
368 |
+
.appendTo(elem)
|
369 |
+
.addClass('col-sm-10')
|
370 |
+
.append($(elem).children(':not(label)'));
|
371 |
+
});
|
372 |
+
|
373 |
+
update_gist_editor(gist_editor);
|
374 |
+
|
375 |
+
// bind events for id changing
|
376 |
+
var id_input = gist_editor.find('#gist_id');
|
377 |
+
// Save current value of element
|
378 |
+
id_input.data('oldVal', id_input.val());
|
379 |
+
// Look for changes in the value
|
380 |
+
id_input.bind("change click keyup input paste", function(event) {
|
381 |
+
// If value has changed...
|
382 |
+
if (id_input.data('oldVal') !== id_input.val()) {
|
383 |
+
// Updated stored value
|
384 |
+
id_input.data('oldVal', id_input.val());
|
385 |
+
// Do action
|
386 |
+
gist_id_updated_callback(gist_editor);
|
387 |
+
}
|
388 |
+
});
|
389 |
+
|
390 |
+
return gist_editor;
|
391 |
+
}
|
392 |
+
|
393 |
+
function show_gist_editor_modal () {
|
394 |
+
var modal;
|
395 |
+
modal = dialog.modal({
|
396 |
+
show: false,
|
397 |
+
title: 'Share on Github',
|
398 |
+
notebook: Jupyter.notebook,
|
399 |
+
keyboard_manager: Jupyter.notebook.keyboard_manager,
|
400 |
+
body: build_gist_editor(),
|
401 |
+
buttons: {
|
402 |
+
' Gist it!': {
|
403 |
+
class : 'btn-primary',
|
404 |
+
click: function() {
|
405 |
+
modal.find('.btn').prop('disabled', true);
|
406 |
+
var new_data = {
|
407 |
+
public: $('#gist_public').prop('checked'),
|
408 |
+
description: $('#gist_description').val()
|
409 |
+
};
|
410 |
+
$.extend(
|
411 |
+
true,
|
412 |
+
Jupyter.notebook.metadata.gist.data,
|
413 |
+
new_data
|
414 |
+
);
|
415 |
+
// prevent the modal from closing. See github.com/twbs/bootstrap/issues/1202
|
416 |
+
modal.data('bs.modal').isShown = false;
|
417 |
+
var spinner = modal.find('.btn-primary .fa-github').addClass('fa-spin');
|
418 |
+
make_gist(function (jqXHR, textStatus) {
|
419 |
+
modal.find('.btn').prop('disabled', false);
|
420 |
+
// allow the modal to close again. See github.com/twbs/bootstrap/issues/1202
|
421 |
+
modal.data('bs.modal').isShown = true;
|
422 |
+
spinner.removeClass('fa-spin');
|
423 |
+
});
|
424 |
+
}
|
425 |
+
},
|
426 |
+
done: {}
|
427 |
+
}
|
428 |
+
})
|
429 |
+
.attr('id', 'gist_modal')
|
430 |
+
.on('shown.bs.modal', function (evt) {
|
431 |
+
var err = modal.find('#gist_id').parent().hasClass('has-error');
|
432 |
+
modal.find('.btn-primary').prop('disabled', err);
|
433 |
+
});
|
434 |
+
|
435 |
+
modal.find('.btn-primary').prepend(
|
436 |
+
$('<i/>')
|
437 |
+
.addClass('fa fa-lg fa-github')
|
438 |
+
);
|
439 |
+
|
440 |
+
modal.modal('show');
|
441 |
+
}
|
442 |
+
|
443 |
+
var make_gist = function make_gist (complete_callback) {
|
444 |
+
ensure_default_metadata();
|
445 |
+
|
446 |
+
var data = $.extend(
|
447 |
+
true, // deep-copy
|
448 |
+
{ files: {} }, // defaults
|
449 |
+
Jupyter.notebook.metadata.gist.data // overrides
|
450 |
+
);
|
451 |
+
var filename = Jupyter.notebook.notebook_name;
|
452 |
+
data.files[filename] = {
|
453 |
+
content: JSON.stringify(Jupyter.notebook.toJSON(), null, 2)
|
454 |
+
};
|
455 |
+
|
456 |
+
var id_input = $('#gist_id');
|
457 |
+
var id = params.gist_it_personal_access_token !== '' ? id_input.val() : '';
|
458 |
+
var method = id ? 'PATCH' : 'POST';
|
459 |
+
|
460 |
+
// Create/edit the Gist
|
461 |
+
$.ajax({
|
462 |
+
url: get_api_endpoint() +'/gists' + (id ? '/' + id : ''),
|
463 |
+
type: method,
|
464 |
+
dataType: 'json',
|
465 |
+
data: JSON.stringify(data),
|
466 |
+
beforeSend: add_auth_token,
|
467 |
+
success: gist_success,
|
468 |
+
error: gist_error,
|
469 |
+
complete: complete_callback
|
470 |
+
});
|
471 |
+
};
|
472 |
+
|
473 |
+
function load_jupyter_extension () {
|
474 |
+
return Jupyter.notebook.config.loaded.then(initialize);
|
475 |
+
}
|
476 |
+
|
477 |
+
return {
|
478 |
+
load_jupyter_extension: load_jupyter_extension,
|
479 |
+
load_ipython_extension: load_jupyter_extension
|
480 |
+
};
|
481 |
+
});
|
.local/share/jupyter/nbextensions/go_to_current_running_cell/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Go to Running Cell
|
2 |
+
==================
|
3 |
+
|
4 |
+
This is an extension allows you to jump to the current running cell. You can also activate this functionality automatically, i.e., your view is always scolling to the current cell.
|
5 |
+
|
6 |
+
Button: A button with eye icon that you can go to the first running cell.
|
7 |
+
![button](anchor.png)
|
8 |
+
|
9 |
+
Keyboard shortcuts:
|
10 |
+
-------------------
|
11 |
+
__*Alt-I*__ (Jump to first running cell)
|
12 |
+
__*Meta-[*__ (Follow executing cell On)
|
13 |
+
__*Meta-]*__(Follow executing cell Off)
|
14 |
+
|
15 |
+
Demo
|
16 |
+
----
|
17 |
+
### Jump to first running cell
|
18 |
+
![button](jump_to_cell.gif)
|
19 |
+
|
20 |
+
### Follow executing cell
|
21 |
+
|
22 |
+
![button](auto_focus.gif)
|
.local/share/jupyter/nbextensions/go_to_current_running_cell/eye.png
ADDED
.local/share/jupyter/nbextensions/go_to_current_running_cell/go_to_current_running_cell.yaml
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Type: Jupyter Notebook Extension
|
2 |
+
Name: Go to Current Running Cells
|
3 |
+
Description: Go to Running cell and always scroll into current running cell view
|
4 |
+
Link: README.md
|
5 |
+
Main: main.js
|
6 |
+
Compatibility: 4.x, 5.x
|
7 |
+
|
8 |
+
Parameters:
|
9 |
+
- name: is_follow_cell
|
10 |
+
description: Activate follow executing cells, default behavior is false.
|
11 |
+
input_type: checkbox
|
12 |
+
- name: go_to_running_cell_shortcut
|
13 |
+
description: Go to first running cell
|
14 |
+
input_type: input
|
15 |
+
default: Alt-I
|
16 |
+
- name: follow_cell_on_shortcut
|
17 |
+
description: Enable following running cell
|
18 |
+
input_type: input
|
19 |
+
default: Alt-;
|
20 |
+
- name: follow_cell_off_shortcut
|
21 |
+
description: Disable following running cell
|
22 |
+
input_type: input
|
23 |
+
default: Alt-'
|
24 |
+
- name: button_icon
|
25 |
+
description: Button for go to first running cell
|
26 |
+
default: fa-anchor
|