Skip to content

Commit

Permalink
Merge pull request #795 from jfbercher/toc2
Browse files Browse the repository at this point in the history
[toc2] Highlight toc headings for sections with selected/edited/running cells; fix save issue #762
  • Loading branch information
jcb91 authored Nov 24, 2016
2 parents 0cdb295 + 0f4c8dc commit bd65972
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 72 deletions.
8 changes: 6 additions & 2 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 @@ -74,4 +74,8 @@ This option requires the IPython kernel and is not present with other kernels.
- @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
152 changes: 110 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': {'hover_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,103 @@ 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.hover_highlight + " }\n" +
".toc-item-highlight-select {background-color: " + cfg.colors.selected_highlight + "}\n" +
".toc-item-highlight-execute {background-color: " + cfg.colors.running_highlight + "}\n" +
".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
13 changes: 12 additions & 1 deletion src/jupyter_contrib_nbextensions/nbextensions/toc2/toc2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,16 @@ Parameters:
description: Display Table of Contents as a navigation menu
input_type: checkbox
default: true

- name: toc2.colors.hover_highlight
input_type: color
description: Hover color in toc
default: "#DAA520"
- name: toc2.colors.selected_highlight
input_type: color
description: Color of sections with selected elements
default: "#FFD700"
- name: toc2.colors.running_highlight
input_type: color
description: Color of sections with running cells
default: "#FF0000"

0 comments on commit bd65972

Please sign in to comment.