diff --git a/extensions/toc2.py b/extensions/toc2.py new file mode 100644 index 000000000..5c5dd0cd6 --- /dev/null +++ b/extensions/toc2.py @@ -0,0 +1,48 @@ +"""Toc2 Exporter class""" + +#----------------------------------------------------------------------------- +# Copyright (c) 2016, the IPython IPython-Contrib Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from traitlets.config import Config +from traitlets.config import Config +from nbconvert.exporters.html import HTMLExporter + +#----------------------------------------------------------------------------- +# Classes +#----------------------------------------------------------------------------- + +class TocExporter(HTMLExporter): + """ + Exports to an html document, embedding toc2 extension (.html) + """ + + def _file_extension_default(self): + return '.html' + + def _template_file_default(self): + return 'toc3' + + output_mimetype = 'text/html' + + def _raw_mimetypes_default(self): + return ['text/markdown', 'text/html', ''] + + @property + def default_config(self): + import jupyter_core.paths + import os + c = Config({'ExtractOutputPreprocessor':{'enabled':True}}) + c.merge(super(TocExporter,self).default_config) + + user_templates = os.path.join(jupyter_core.paths.jupyter_data_dir(), 'templates') + c.TemplateExporter.template_path = [ + '.', user_templates ] + return c \ No newline at end of file diff --git a/nbextensions/usability/toc2/README.md b/nbextensions/usability/toc2/README.md index bcc4c5feb..cb5dbc52d 100644 --- a/nbextensions/usability/toc2/README.md +++ b/nbextensions/usability/toc2/README.md @@ -2,11 +2,15 @@ ## Description and main features -This extension adds a small button in the main toolbar, which enables to collect all running headers in the notebook and display them in a floating window. +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. -![](icon.png) +#### First demo: +![](demo.gif) -The table of contents is automatically updated when modifications occur in the notebook. The toc window can be moved and resized, the table of contents can be collapsed or the window can be completely hidden. The position, dimensions, and states (that is 'collapsed' and 'hidden' states) are remembered (actually stored in the notebook's metadata) and restored on the next session. The floating window also provides two links in its header for further functionalities: +#### Second demo: +![](demo2.gif) + +The table of contents is automatically updated when modifications occur in the notebook. The toc window can be moved and resized. It can be docked as a sidebar or dragged from the sidebar into a floating window. The table of contents can be collapsed or the window can be completely hidden. The navigation menu can be enabled/disabled via the nbextensions configuration utility. It can also be resized. The position, dimensions, and states (that is 'collapsed' and 'hidden' states) are remembered (actually stored in the notebook's metadata) and restored on the next session. The toc window also provides two links in its header for further functionalities: - the "n" link toggles automatic numerotation of all header lines - the "t" link toggles a toc cell in the notebook, which contains the actual table of contents, possibly with the numerotation of the different sections. @@ -16,11 +20,30 @@ The state of these two toggles is memorized and restored on reload. ![](image.png) ## Configuration -The initial configuration can be given using the IPython-contrib nbextensions facility. The maximum depth of headers to display on toc can be precised there (with a default of 4), as well as the state of the toc cell (default: false, ie not present) and the numbering of headers (true by default). The differents states and position of the floating window have reasonable defaults and can be modfied per notebook). - - +The initial configuration can be given using the IPython-contrib nbextensions facility. It includes: + +- The toc initial mode (floating or sidebar) +- The maximum depth of headers to display on toc (with a default of 6) +- The state of the toc cell (default: false, ie not present) +- The numbering of headers (true by default). + +The differents states and position of the floating window have reasonable defaults and can be modfied per notebook). + +## Export +It is possible to export (most of) table of contents functionalities to html. The idea is to link a relevant part of the javascript +extension and the css, and add a small script in the html file. This is done using a template by +``` +jupyter nbconvert FILE.ipynb --template toc +``` +or +``` +jupyter nbconvert FILE.ipynb --template toc2 +``` +For the first template (toc), the files toc2.js and main.css (originally located in JUPYTER_DATA_DIR/nbextensions/usability/toc2) must reside in the same directory as intended for the html file. In the second template, these files are linked to the IPython-notebook-extensions github website. Export configuration (parameters) shall be edited directly in the template files (in templates directory JUPYTER_DATA_DIR/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. + + # Testing -- At loading of the notebook, configuration and initial rendering of the table of contents were fired on the event "notebook_loaded.Notebook". Curiously, it happened that this event was either not always fired or detected. Thus I rely here on a combination of "notebook_loaded.Notebook" and "kernel_ready.Kernel" instead. +- 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/IPython-notebook-extensions/issues/429 @@ -33,6 +56,8 @@ from https://gist.github.com/magican/5574556 - @JanSchulz, enable maths in headers links - @jfbercher december 06, 2015 -- Big update: automatic numbering, toc cell, window dragging, configuration parameters - @jfbercher december 24, 2015 -- nested numbering in toc-window, following the fix by [@paulovn](https://github.com/minrk/ipython_extensions/pull/53) in @minrk's repo. December 30-31, updated config in toc2.yaml to enable choosing the initial visible state of toc_window via a checkbox ; and now resizable. -- @slonik-az february 13, 2016. rewritten toc numberings (more robust version), fixed problems with skipped heading levels, some code cleanup +- @slonik-az february 13, 2016. Rewritten toc numberings (more robust version), fixed problems with skipped heading levels, some code cleanup - @jfbercher february 21, 2016. Fixed some issues when resizing the toc window. Now avoid overflows, clip the text and add a scrollbar. - @jfbercher february 22, 2016. Add current toc number to headings anchors. This enable to get unique anchors for recurring headings with the same text. An anchor with the original ID is still created and can be used (but toc uses the new ones!). It is also possible to directly add an html anchor within the heading text. This is taken into account when building toc links (see comments in code). +- @jfbercher april 29, 2016. Triggered by @cqcn1991, cf [discussion here](https://github.com/ipython-contrib/IPython-notebook-extensions/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. diff --git a/nbextensions/usability/toc2/demo.gif b/nbextensions/usability/toc2/demo.gif new file mode 100644 index 000000000..5ea0bdb1d Binary files /dev/null and b/nbextensions/usability/toc2/demo.gif differ diff --git a/nbextensions/usability/toc2/demo2.gif b/nbextensions/usability/toc2/demo2.gif new file mode 100644 index 000000000..302fc5420 Binary files /dev/null and b/nbextensions/usability/toc2/demo2.gif differ diff --git a/nbextensions/usability/toc2/main.css b/nbextensions/usability/toc2/main.css index 96eebed92..ae2005d6a 100644 --- a/nbextensions/usability/toc2/main.css +++ b/nbextensions/usability/toc2/main.css @@ -1,18 +1,70 @@ /*extracted from https://gist.github.com/magican/5574556*/ -#toc { + +#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} + +/*#Navigate_menu { + list-style-type: none; + max-width: 500px; + min-width: 100px; + width: 250px; + overflow: auto; +} + +#navigate_menu { + overflow: hidden; +} + +#Navigate_menu { + list-style-type: none; + overflow: auto; +}*/ + +#navigate_menu { + /*display: block;*/ + list-style-type: none; + max-width: 800px; + min-width: 100px; + width: 250px; + overflow: auto; +} + + +#navigate_menu a { + list-style-type: none; + color: #333333; + text-decoration: none; +} + +#navigate_menu li { + padding-left: 2px; + clear: both; + list-style-type: none; +} + +#navigate_menu-level0 {padding-left: 10px;} +#navigate_menu-level0 ul {padding-left: 10px;} + + +.toc { max-height: 500px; padding: 0px; overflow-y: auto; + font-weight: normal; + color: #333333; + white-space: nowrap; + overflow-x: auto; } -#toc ol.toc-item { +.toc ol.toc-item { counter-reset: item; list-style: none; padding: 0.1em; } -#toc ol.toc-item li { +.toc ol.toc-item li { display: block; } @@ -22,7 +74,7 @@ margin: 0; } -#toc ol.toc-item li:before { +.toc ol.toc-item li:before { font-size: 90%; font-family: Georgia, Times New Roman, Times, serif; counter-increment: item; @@ -30,8 +82,8 @@ } -#toc-wrapper { - position: fixed; +.float-wrapper { + position: fixed !important; top: 120px; max-width:600px; right: 20px; @@ -44,10 +96,29 @@ overflow: hidden; } +.sidebar-wrapper { + height: 100%; + left: 5px; + padding: 10px; + position: fixed !important; + width: 212px; + max-width: 28%; + background-color: #fff; + border-style: solid; + border-color: #eeeeee; + opacity: .99; + overflow: hidden; +} + + +.col-md-9 { + overflow:hidden; + margin-left: 14%; + width: 80%} + #toc-wrapper.closed { min-width: 100px; width: auto; - height:auto; transition: width; } #toc-wrapper:hover{ @@ -100,4 +171,3 @@ .lev6 {margin-left: 180px} .lev7 {margin-left: 200px} .lev8 {margin-left: 220px} - diff --git a/nbextensions/usability/toc2/main.js b/nbextensions/usability/toc2/main.js index 89f6c851d..ae7d0a5e9 100644 --- a/nbextensions/usability/toc2/main.js +++ b/nbextensions/usability/toc2/main.js @@ -3,414 +3,97 @@ // See the history of contributions in README.md +//define(["require", "jquery", "base/js/namespace", 'services/config', +// 'base/js/utils', "nbextensions/usability/toc2/toc2"], function(require, $, IPython, configmod, utils, toc2) { + define(["require", "jquery", "base/js/namespace", 'services/config', - 'base/js/utils'], function (require, $, IPython, configmod, utils) { + 'base/js/utils', "nbextensions/usability/toc2/toc2"], function(require, $, IPython, configmod, utils, toc2 ) { "use strict"; + // ...........Parameters configuration...................... // define default values for config parameters if they were not present in general settings (notebook.json) - var cfg={'toc_threshold':6, - 'toc_number_sections':true, + var cfg={'threshold':6, + 'number_sections':true, 'toc_cell':false, - 'toc_window_display':false} - var threshold = cfg['toc_threshold'] - var toc_cell=cfg['toc_cell']; - var number_sections = cfg['toc_number_sections']; - - function read_config() { // read after nb is loaded - // create config object to load parameters - var base_url = utils.get_body_data("baseUrl"); - var config = new configmod.ConfigSection('notebook', {base_url: base_url}); - - // update params with any specified in the server's config file - var update_params = function() { - for (var key in cfg) { - if (config.data.hasOwnProperty(key)){ - cfg[key] = config.data[key]; -} - } - IPython.notebook.metadata.toc = cfg; //save in present nb metadata (then can be modified per document) - threshold = cfg['toc_threshold']; - toc_cell=cfg['toc_cell']; - number_sections = cfg['toc_number_sections']; - //$('#toc-wrapper').css('display',cfg['toc-wrapper_display']) //ensure display is done as noted in config - $('#toc-wrapper').css('display',cfg['toc_window_display'] ? 'block' : 'none') //ensure display is done as noted in config - }; + 'toc_window_display':false, + "toc_section_display": "block", + 'sideBar':true, + 'navigate_menu':true} - // config may be specified at system level or at document level. - if (IPython.notebook.metadata.toc !== undefined){ //configuration saved in nb - console.log("config stored in nb") - cfg = IPython.notebook.metadata.toc; - threshold = cfg['toc_threshold']; - toc_cell=cfg['toc_cell']; - number_sections = cfg['toc_number_sections']; - } else { - console.log("config stored in system") - config.load(); - config.loaded.then(function() {update_params() }) - } - config_loaded = true; -} //.....................global variables.... - var rendering_toc_cell = false; - var config_loaded = false; - var extension_initialized=false; + var liveNotebook = !(typeof IPython == "undefined") -//......... utilitary functions............ + var st={} + st.rendering_toc_cell = false; + st.config_loaded = false; + st.extension_initialized=false; - function incr_lbl(ary, h_idx){//increment heading label w/ h_idx (zero based) - ary[h_idx]++; - for(var j= h_idx+1; j < ary.length; j++){ ary[j]= 0; } - return ary.slice(0, h_idx+1); - } + st.nbcontainer_marginleft = $('#notebook-container').css('margin-left') + st.nbcontainer_marginright = $('#notebook-container').css('margin-right') + st.nbcontainer_width = $('#notebook-container').css('width') + st.oldTocHeight = undefined + st.cell_toc = undefined; + st.toc_index=0; - var make_link = function (h, num_lbl) { - var 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); } - hclone.children().last().remove(); // remove the last child (that is the automatic anchor) - 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) - return a; - }; + 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 config = new configmod.ConfigSection('notebook', {base_url: base_url}); - var make_link_originalid = function (h, num_lbl) { - var a = $(""); - a.attr("href", '#' + h.attr('saveid')); - // get the text *excluding* the link text, whatever it may be - var hclone = h.clone(); - 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 - a.html(hclone.html()); - a.on('click',function(){setTimeout(function(){ $.ajax()}, 100) }) //workaround for https://github.com/jupyter/notebook/issues/699 - return a; + // update params with any specified in the server's config file + var update_params = function(cfg) { + for (var key in cfg) { + if (config.data.hasOwnProperty(key)){ + cfg[key] = config.data[key]; } - - var ol_depth = function (element) { - // get depth of nested ol - var d = 0; - while (element.prop("tagName").toLowerCase() == 'ol') { - d += 1; - element = element.parent(); - } - return d; - }; - - var create_toc_div = function () { - var toc_wrapper = $('
') - .append( - $("
") - .addClass("header") - .text("Contents ") - .append( - $("") - .attr("href", "#") - .addClass("hide-btn") - .attr('title', 'Hide ToC') - .text("[-]") - .click( function(){ - $('#toc').slideToggle({'complete': function(){ - IPython.notebook.metadata.toc['toc_section_display']=$('#toc').css('display'); - IPython.notebook.set_dirty();}} - ); - $('#toc-wrapper').toggleClass('closed'); - if ($('#toc-wrapper').hasClass('closed')){ - $('#toc-wrapper').css({height: 40}); - $('#toc-wrapper .hide-btn') - .text('[+]') - .attr('title', 'Show ToC'); - } else { - $('#toc-wrapper').css({height: IPython.notebook.metadata.toc_position['height']}); - $('#toc').css({height: IPython.notebook.metadata.toc_position['height']}); - $('#toc-wrapper .hide-btn') - .text('[-]') - .attr('title', 'Hide ToC'); - } - return false; - }) - ).append( - $("") - .attr("href", "#") - .addClass("reload-btn") - .text(" \u21BB") - .attr('title', 'Reload ToC') - .click( function(){ - table_of_contents(); - return false; - }) - ).append( - $("") - .html("  ") - ).append( - $("") - .attr("href", "#") - .addClass("number_sections-btn") - .text("n") - .attr('title', 'Number text sections') - .click( function(){ - number_sections=!(number_sections); - IPython.notebook.metadata.toc['toc_number_sections']=number_sections; - IPython.notebook.set_dirty(); - table_of_contents(); - return false; - }) - ).append( - $("") - .html("  ") - ).append( - $("") - .attr("href", "#") - .addClass("toc_cell_sections-btn") - .html("t") - .attr('title', 'Add a toc section in Notebook') - .click( function(){ - toc_cell=!(toc_cell); - IPython.notebook.metadata.toc['toc_cell']=toc_cell; - IPython.notebook.set_dirty(); - table_of_contents(); - return false; - }) - ) - ).append( - $("
").attr("id", "toc") - ) - - $("body").append(toc_wrapper); - - // enable dragging and save position on stop moving - $('#toc-wrapper').draggable({ - start : function(event, ui) { - $(this).width($(this).width()); - }, - stop : function (event,ui){ // on save, store toc position - var oldHeight = IPython.notebook.metadata['toc_position']['height']; - IPython.notebook.metadata['toc_position']={ - 'left':$('#toc-wrapper').css('left'), - 'top':$('#toc-wrapper').css('top'), - 'width':$('#toc-wrapper').css('width'), - 'right':$('#toc-wrapper').css('right')}; - if (!$('#toc-wrapper').hasClass('closed')){ - IPython.notebook.metadata['toc_position']['height']=$('#toc-wrapper').css('height'); - } - else { - IPython.notebook.metadata['toc_position']['height']=oldHeight; + } + if (typeof cfg.sideBar == "undefined") { + console.log("Updating sidebar") + cfg.sideBar=true; } - IPython.notebook.set_dirty(); - }, - }); - - $('#toc-wrapper').resizable({ - start : function(event, ui) { - $(this).width($(this).width()); - }, - stop : function (event,ui){ // on save, store toc position - IPython.notebook.metadata['toc_position']={ - 'left':$('#toc-wrapper').css('left'), - 'top':$('#toc-wrapper').css('top'), - 'width':$('#toc-wrapper').css('width'), - 'height':$('#toc-wrapper').css('height'), - 'right':$('#toc-wrapper').css('right')}; - $('#toc').css('height', $('#toc-wrapper').height()-30) - IPython.notebook.set_dirty(); - }, - }); - + }; - // restore toc position at load - if (IPython.notebook.metadata['toc_position'] !== undefined){ - $('#toc-wrapper').css(IPython.notebook.metadata['toc_position']); -} - // Ensure position is fixed - $('#toc-wrapper').css('position', 'fixed'); - // Restore toc display - if (IPython.notebook.metadata.toc !== undefined) { - if (IPython.notebook.metadata.toc['toc_section_display']!==undefined) { - $('#toc').css('display',IPython.notebook.metadata.toc['toc_section_display']) - $('#toc').css('height', $('#toc-wrapper').height()-30) - if (IPython.notebook.metadata.toc['toc_section_display']=='none'){ - $('#toc-wrapper').addClass('closed'); - $('#toc-wrapper').css({height: 40}); - $('#toc-wrapper .hide-btn') - .text('[+]') - .attr('title', 'Show ToC'); + // config may be specified at system level or at document level. + // (1) loads system config + // (2) updates it with what is in nb + console.log("loading config stored in system") + config.load(); + config.loaded.then(function() { + update_params(cfg); + if (typeof IPython.notebook.metadata.toc != "undefined"){ + console.log("loading config stored in nb") + for (var key in cfg) { + if (typeof IPython.notebook.metadata.toc[key] != "undefined"){ + cfg[key] = IPython.notebook.metadata.toc[key] } + } } - if (IPython.notebook.metadata.toc['toc_window_display']!==undefined) { - console.log("******Restoring toc display"); - $('#toc-wrapper').css('display',IPython.notebook.metadata.toc['toc_window_display'] ? 'block' : 'none'); - //$('#toc').css('overflow','auto') - } - } - - // if toc-wrapper is undefined (first run(?), then hide it) - if ($('#toc-wrapper').css('display')==undefined) $('#toc-wrapper').css('display',"none") //block - }; - -// var table_of_contents = function (threshold) { //small bug because being called on an event, the passed threshold was actually an event -// now threshold is a global variable -var table_of_contents = function () { - if(rendering_toc_cell) { // if toc_cell is rendering, do not call table_of_contents, - rendering_toc_cell=false; // otherwise it will loop - return} - - var toc_wrapper = $("#toc-wrapper"); - var toc_index=0; - if (toc_wrapper.length === 0) { - create_toc_div(); - } - var segments = []; - var ul = $("