Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions src/jupyter_contrib_nbextensions/nbextensions/toc2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Description and main features

The toc2 extension enables to collect all running headers and display them in a floating window, as a sidebar or with a navigation menu. The extension is also draggable, resizable, collapsable, dockable and features automatic numerotation with unique links ids, and an optional toc cell. Finally, the toc can preserved when exporting to html.
The toc2 extension enables to collect all running headers and display them in a floating window, as a sidebar or with a navigation menu. The extension is also draggable, resizable, collapsable, dockable and features automatic numerotation with unique links ids, and an optional toc cell. Sections of currently selected/edited or running cells are highlighted in the toc. Finally, the toc can preserved when exporting to html.

#### First demo:
![](demo.gif)
Expand Down Expand Up @@ -43,18 +43,26 @@ An exporter is also available. It is now possible to export to html with toc by
```
jupyter nbconvert --to html_toc FILE.ipynb
```
For the first template (toc), the files toc2.js and main.css (originally located in <python site-packages>/jupyter_contrib_nbextensions/nbextensions/toc2) must reside in the same directory as intended for the html file. In the second template, these files are linked to the ipython-contrib/jupyter_contrib_nbextensions github website. Export configuration (parameters) shall be edited directly in the template files (in templates directory <python site-packages>/jupyter_contrib_nbextensions/templates). An option "Save as HTML (with toc)" is also provided in the File menu and enable to directly convert the actual notebook. This option requires the IPython kernel and is not present with other kernels.
For the first template (toc), the files toc2.js and main.css (originally located in `<python site-packages>/jupyter_contrib_nbextensions/nbextensions/toc2`)
must reside in the same directory as intended for the html file.
In the second template, these files are linked to the
`ipython-contrib/jupyter_contrib_nbextensions` github website.
Export configuration (parameters) shall be edited directly in the template
files (in templates directory `<python site-packages>/jupyter_contrib_nbextensions/templates`).
An option "Save as HTML (with toc)" is also provided in the File menu and
enables the direct conversion of the actual notebook.
This option requires the IPython kernel and is not present with other kernels.


## Testing
- At loading of the notebook, configuration and initial rendering of the table of contents were fired on the event "notebook_loaded.Notebook". It happens that the extension is sometimes loaded *after* this event. I now rely on a direct rendering at load and on a combination of "notebook_loaded.Notebook" and "kernel_ready.Kernel".

- This extension also includes a quick workaround as described in https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/429
- This extension also includes a quick workaround as described in [#429](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/429)

## History

- This extension was adapted by minrk https://github.com/minrk/ipython_extensions
from https://gist.github.com/magican/5574556
- This extension was adapted by minrk in [minrk/ipython_extensions](https://github.com/minrk/ipython_extensions)
from [gist.github.com/magican/5574556](https://gist.github.com/magican/5574556)
- Added to the ipython-contrib/jupyter_contrib_nbextensions repo by @JanSchulz
- @juhasch, automatic update on markdown rendering,
- @JanSchulz, enable maths in headers links
Expand All @@ -66,4 +74,8 @@ from https://gist.github.com/magican/5574556
- @jfbercher april 29, 2016. Triggered by @cqcn1991, cf [discussion here](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/532), add a sidebar option. The floating toc window can be dragged and docked as a left sidebar. The sidebar can be dragged out as a floating window. These different states are stored and restored when reloading the notebook. Add html export capability via templates toc.tpl and toc2.tpl (see above).
- @jfbercher may 04, 2016. Added a "Save as HTML with toc" menu. Added a new "Navigate" menu with presents the contents of the toc. Changed default styling for links in tocs.
- @jfbercher july 28, 2016. A dedicated exporter was added. It is now possible to export to html with toc by `jupyter nbconvert --to html_toc FILE.ipynb`
- @jfbercher septemeber 21, 2016. Fixed empty size of navigation menu (if no resize had occur). Changed system/notebook configuration parameters storing, loading and merging.
- @jfbercher september 21, 2016. Fixed empty size of navigation menu (if no resize had occur). Changed system/notebook configuration parameters storing, loading and merging.
- @jfbercher november 16, 2016.
- Fixed saving issue due to a race condition in loading/writing metadata; see issues [#1882](https://github.com/jupyter/notebook/issues/1882#issuecomment-260671282) and [#762](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/762)
- As suggested by @dinya in [#791](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/791), added highlighting of the section that contains the currently edited/selected/executing cell. Colors can be customized by changing `.toc-item-highlight-select` and `.toc-item-highlight-execute` classes in css.
-[update nov 23]. As suggested by @jcb91, the highlight colors can now be configured via the nbextensions--configurator, instead of changing the css.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion src/jupyter_contrib_nbextensions/nbextensions/toc2/main.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/*extracted from https://gist.github.com/magican/5574556*/


#toc-level0 li > a:hover { display: block; background-color: #DAA520}
/*#toc-level0 li > a:hover { display: block; background-color: #DAA520}*/

#toc-level0 a {color: #333333; text-decoration: none;}
#navigate_menu li a:hover {background-color: #f1f1f1}

Expand Down Expand Up @@ -163,6 +164,10 @@
color: black;
}

/*.toc-item-highlight-select {background-color: Gold}
.toc-item-highlight-execute {background-color: red}
.toc-item-highlight-execute.toc-item-highlight-select {background-color: Gold} */

.lev1 {margin-left: 80px}
.lev2 {margin-left: 100px}
.lev3 {margin-left: 120px}
Expand Down
151 changes: 109 additions & 42 deletions src/jupyter_contrib_nbextensions/nbextensions/toc2/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
// 'base/js/utils', "nbextensions/toc2/toc2"], function(require, $, IPython, configmod, utils, toc2) {

define(["require", "jquery", "base/js/namespace", 'services/config',
'base/js/utils', "nbextensions/toc2/toc2"], function(require, $, IPython, configmod, utils, toc2 ) {
'base/js/utils', 'notebook/js/codecell', "nbextensions/toc2/toc2"], function(require, $, IPython, configmod, utils, codecell, toc2 ) {

var Notebook = require('notebook/js/notebook').Notebook
"use strict";


Expand All @@ -20,7 +21,11 @@ define(["require", "jquery", "base/js/namespace", 'services/config',
'toc_window_display':false,
"toc_section_display": "block",
'sideBar':true,
'navigate_menu':true}
'navigate_menu':true,
'colors': {'hoover_highlight': '#DAA520',
'selected_highlight': '#FFD700',
'running_highlight': '#FF0000'}
}

//.....................global variables....

Expand All @@ -44,6 +49,7 @@ define(["require", "jquery", "base/js/namespace", 'services/config',
function read_config(cfg, callback) { // read after nb is loaded
// create config object to load parameters
var base_url = utils.get_body_data("baseUrl");
var initial_cfg = $.extend(true, {}, cfg);
var config = new configmod.ConfigSection('notebook', { base_url: base_url });
config.loaded.then(function(){
// config may be specified at system level or at document level.
Expand All @@ -53,7 +59,14 @@ define(["require", "jquery", "base/js/namespace", 'services/config',
// and save in nb metadata (then can be modified per document)
cfg = IPython.notebook.metadata.toc = $.extend(true, cfg,
IPython.notebook.metadata.toc);

// excepted colors that are taken globally (if defined)
cfg.colors = IPython.notebook.metadata.toc.colors = $.extend(true, {}, initial_cfg.colors);
try
{cfg.colors = IPython.notebook.metadata.toc.colors = $.extend(true, cfg.colors, config.data.toc2.colors); }
catch(e) {}
// create highlights style section in document
create_highlights_css()
// call callbacks
callback && callback();
st.config_loaded = true;
})
Expand Down Expand Up @@ -99,48 +112,102 @@ define(["require", "jquery", "base/js/namespace", 'services/config',
};


var load_ipython_extension = function () {
load_css(); //console.log("Loading css")
toc_button(); //console.log("Adding toc_button")

// read configuration, then call toc
cfg = read_config(cfg,
function(){table_of_contents(cfg,st);} // called after config is stable
);

// render toc for each markdown cell modification
$([IPython.events]).on("rendered.MarkdownCell",
function(){
table_of_contents(cfg,st);
});
console.log("toc2 initialized")

// add a save as HTML with toc included
addSaveAsWithToc();

// render toc on load
$([IPython.events]).on("notebook_loaded.Notebook", function(){
table_of_contents(cfg,st);
console.log("toc2 initialized (via notebook_loaded)")
})

// render toc if kernel_ready and add/remove a menu
$([IPython.events]).on("kernel_ready.Kernel", function(){
console.log("kernel_ready.Kernel")
table_of_contents(cfg,st);
console.log("toc2 initialized (via kernel_ready)")
// If kernel has been restarted, or changed, check if save_html_with_toc has to be included or removed
var IPythonKernel=(IPython.notebook.kernel.name == "python2" || IPython.notebook.kernel.name == "python3")
if (!IPythonKernel) {
$('#save_html_with_toc').remove()
}
else{
if ($('#save_html_with_toc').length==0) addSaveAsWithToc();
}
});
function create_highlights_css() {
var sheet = document.createElement('style')
sheet.innerHTML = `#toc-level0 li > a:hover { display: block; background-color: ${cfg.colors.hoover_highlight} }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backtick is not supported in android browser or internet explorer (see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals#Browser_compatibility), so it might be a good idea to use a more basic string concatenation operation here, as otherwise it'll presumably be a syntax error, causing the whole nbextension to crash & burn, rather than failing mor gracefully.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it is nicer to use template strings, but indeed it might crash the extension in IE/Android. I change this to simple concatenation.

.toc-item-highlight-select {background-color: ${cfg.colors.selected_highlight}}
.toc-item-highlight-execute {background-color: ${cfg.colors.running_highlight}}
.toc-item-highlight-execute.toc-item-highlight-select {background-color: ${cfg.colors.selected_highlight}} `
document.body.appendChild(sheet);
}


var CodeCell = codecell.CodeCell;

function patch_CodeCell_get_callbacks() {

var previous_get_callbacks = CodeCell.prototype.get_callbacks;
CodeCell.prototype.get_callbacks = function() {
var that = this;
var callbacks = previous_get_callbacks.apply(this, arguments);
var prev_reply_callback = callbacks.shell.reply;
callbacks.shell.reply = function(msg) {
if (msg.msg_type === 'execute_reply') {
setTimeout(function(){
$(toc).find('.toc-item-highlight-execute').removeClass('toc-item-highlight-execute')
rehighlight_running_cells() // re-highlight running cells
}, 100);
var c = IPython.notebook.get_selected_cell();
highlight_toc_item({ type: 'selected' }, { cell: c })
}
return prev_reply_callback(msg);
};
return callbacks;
};
}


function excute_codecell_callback(evt, data) {
var cell = data.cell;
highlight_toc_item(evt, data);
}

function rehighlight_running_cells() {
$.each($('.running'), // re-highlight running cells
function(idx, elt) {
highlight_toc_item({ type: "execute" }, $(elt).data())
}
)
}


var toc_init = function() {
// read configuration, then call toc
cfg = read_config(cfg, function() { table_of_contents(cfg, st); }); // called after config is stable
// event: render toc for each markdown cell modification
$([IPython.events]).on("rendered.MarkdownCell",
function(evt, data) {
table_of_contents(cfg, st); // recompute the toc
rehighlight_running_cells() // re-highlight running cells
highlight_toc_item(evt, data); // and of course the one currently rendered
});
// event: on cell selection, highlight the corresponding item
$([IPython.events]).on('select.Cell', highlight_toc_item)
// event: if kernel_ready (kernel change/restart): add/remove a menu item
$([IPython.events]).on("kernel_ready.Kernel", function() {
addSaveAsWithToc();
})
// add a save as HTML with toc included
addSaveAsWithToc();
//
// Highlight cell on execution
patch_CodeCell_get_callbacks()
$([Jupyter.events]).on('execute.CodeCell', excute_codecell_callback);
}



var load_ipython_extension = function() {
load_css(); //console.log("Loading css")
toc_button(); //console.log("Adding toc_button")

// Wait for the notebook to be fully loaded
if (Jupyter.notebook._fully_loaded) {
// this tests if the notebook is fully loaded
console.log("[toc2] Notebook fully loaded -- toc2 initialized ")
toc_init();
} else {
console.log("[toc2] Waiting for notebook availability")
$([Jupyter.events]).on("notebook_loaded.Notebook", function() {
console.log("[toc2] toc2 initialized (via notebook_loaded)")
toc_init();
})
}

};



return {
load_ipython_extension : load_ipython_extension,
toggle_toc : toggle_toc,
Expand Down
93 changes: 67 additions & 26 deletions src/jupyter_contrib_nbextensions/nbextensions/toc2/toc2.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@ var liveNotebook = !(typeof IPython == "undefined")
return ary.slice(0, h_idx+1);
}


var make_link = function (h, num_lbl) {
var make_link = function(h, num_lbl) {
var a = $("<a/>");
a.attr("href", '#' + h.attr('id'));
// get the text *excluding* the link text, whatever it may be
var hclone = h.clone();
if( num_lbl ){ hclone.prepend(num_lbl); }
if (num_lbl) { hclone.prepend(num_lbl); }
hclone.children().last().remove(); // remove the last child (that is the automatic anchor)
hclone.find("a[name]").remove(); //remove all named anchors
hclone.find("a[name]").remove(); //remove all named anchors
a.html(hclone.html());
a.on('click',function(){setTimeout(function(){ $.ajax()}, 100) }) //workaround for https://github.com/jupyter/notebook/issues/699
//as suggested by @jhamrick
//console.log("h",h.children)
a.on('click', function() {
setTimeout(function() { $.ajax() }, 100); //workaround for https://github.com/jupyter/notebook/issues/699
if (liveNotebook) {
IPython.notebook.get_selected_cell().unselect(); //unselect current cell
var new_selected_cell = $("[id='" + h.attr('id') + "']").parents('.unselected').switchClass('unselected', 'selected')
new_selected_cell.data('cell').selected = true;
var cell = new_selected_cell.data('cell') // IPython.notebook.get_selected_cell()
highlight_toc_item("toc_link_click", {cell: cell})
}
})
return a;
};
};


var make_link_originalid = function (h, num_lbl) {
Expand All @@ -51,27 +57,57 @@ var liveNotebook = !(typeof IPython == "undefined")
}
return d;
};



function highlight_toc_item(evt, data) {
var c = data.cell.element; //
if (c) {
var ll = $(c).find(':header')
if (ll.length == 0) {
var ll = $(c).prevAll().find(':header')
}
var elt = ll[ll.length - 1]
if (elt) {
var highlighted_item = $(toc).find('a[href="#' + elt.id + '"]')
if (evt.type == "execute") {
// remove the selected class and add execute class
// il the cell is selected again, it will be highligted as selected+running
highlighted_item.removeClass('toc-item-highlight-select').addClass('toc-item-highlight-execute')
//console.log("->>> highlighted_item class",highlighted_item.attr('class'))
} else {
$(toc).find('.toc-item-highlight-select').removeClass('toc-item-highlight-select')
highlighted_item.addClass('toc-item-highlight-select')
}
}
}
}


// extra download as html with toc menu (needs IPython kernel)
function addSaveAsWithToc() {
function addSaveAsWithToc() {
var saveAsWithToc = $('#save_html_with_toc').length == 0
var IPythonKernel = (IPython.notebook.kernel.name == "python2" || IPython.notebook.kernel.name == "python3")
var IPythonKernel = IPython.notebook.metadata.kernelspec.language == "python"
if (IPythonKernel) {
$('#save_checkpoint').after("<li id='save_html_with_toc'/>")
$('#save_html_with_toc').append($('<a/>').text('Save as HTML (with toc)').attr("href", "#"))
$('#save_html_with_toc').click(function() {
var IPythonKernel = (IPython.notebook.kernel.name == "python2" || IPython.notebook.kernel.name == "python3")
if (IPythonKernel) {
var code = "!jupyter nbconvert '" + IPython.notebook.notebook_name + "' --template toc2"
console.log(code)
IPython.notebook.kernel.execute(code)
} else {
alert("Sorry; this only works with a IPython kernel");
$('#save_html_with_toc').remove();
}
})
if ($('#save_html_with_toc').length == 0) {
$('#save_checkpoint').after("<li id='save_html_with_toc'/>")
$('#save_html_with_toc').append($('<a/>').text('Save as HTML (with toc)').attr("href", "#"))
$('#save_html_with_toc').click(function() {
var IPythonKernel = IPython.notebook.metadata.kernelspec.language == "python"
if (IPythonKernel) {
var code = "!jupyter nbconvert '" + IPython.notebook.notebook_name + "' --template toc2"
console.log(code)
IPython.notebook.kernel.execute(code)
} else {
alert("Sorry; this only works with a IPython kernel");
$('#save_html_with_toc').remove();
}
})
}
} else {
if ($('#save_html_with_toc').length > 0) $('#save_html_with_toc').remove()
}
}
}



var create_navigate_menu = function(callback) {
Expand All @@ -89,10 +125,15 @@ var liveNotebook = !(typeof IPython == "undefined")
IPython.notebook.metadata.toc.nav_menu = {};
$([IPython.events]).on("before_save.Notebook",
function(){
if(IPython.notebook.metadata.toc.nav_menu){
try
{
IPython.notebook.metadata.toc.nav_menu['width'] = $('#Navigate_menu').css('width')
IPython.notebook.metadata.toc.nav_menu['height'] = $('#Navigate_menu').css('height')
}
catch(e)
{
console.log("[toc2] Error in metadata (navigation menu) - Proceeding",e)
}
})
}

Expand Down
Loading