(requirejs.specified('base/js/namespace') ? define : function (deps, callback) { // if here, the Jupyter namespace hasn't been specified to be loaded. // This means that we're probably embedded in a page, so we need to make // our definition with a specific module name "use strict"; return define('nbextensions/collapsible_headings/main', deps, callback); })(['jquery', 'require'], function ($, requirejs) { "use strict"; var mod_name = 'collapsible_headings'; var log_prefix = '[' + mod_name + ']'; var action_names = { // set on registration insert_above: '', insert_below: '', collapse: '', uncollapse: '', select: '' }; var select_reveals = true; // used as a flag to prevent selecting a heading section from also opening it // define default values for config parameters var params = { add_button : false, add_all_cells_button: false, add_insert_header_buttons: false, use_toggle_controls : true, make_toggle_controls_buttons : false, size_toggle_controls_by_level : true, toggle_open_icon : 'fa-caret-down', toggle_closed_icon : 'fa-caret-right', toggle_color : '#aaaaaa', use_shortcuts : true, shortcuts: { collapse: 'left', collapse_all: 'ctrl-shift-left', uncollapse: 'right', uncollapse_all: 'ctrl-shift-right', select: 'shift-right', insert_above: 'shift-a', insert_below: 'shift-b', }, show_section_brackets : false, section_bracket_width : 10, show_ellipsis : true, select_reveals : true, collapse_to_match_toc: false, indent_px: 8, }; // ------------------------------------------------------------------------ // Jupyter is used when we're in a live notebook, but in non-live notebook // settings, it remains undefined. // It is declared here to allow us to keep logic for live/nonlive functions // together. var Jupyter; // similarly, in a live notebook, events is the Jupyter global events // object, but in a non-live notebook, we must construct our own version var events; try { events = requirejs('base/js/events'); } catch (err) { // in non-live notebook, there's no events structure, so we make our own if (window.events === undefined) { var Events = function () {}; window.events = $([new Events()]); } events = window.events; } // global flag denoting whether we're in a live notebook or exported html. // In a live notebook we operate on Cell instances, in exported html we // operate on jQuery collections of '.cell' elements var live_notebook = false; // Some functions providing things akin to Jupyter.notebook methods, but // which can work using jQuery collections in place of Cell instances. /** * Return all cells in the notebook (or cell elements if notebook not live) */ function _get_cells () { return live_notebook ? Jupyter.notebook.get_cells() : $('#notebook-container > .cell'); } /** * Return cell at index index (or cell element if notebook not live) */ function _get_cell_at_index (index) { return live_notebook ? Jupyter.notebook.get_cell(index) : $('.cell').eq(index); } /** * Return the index of the given cell (or cell element if notebook not live) */ function _find_cell_index (cell) { return live_notebook ? Jupyter.notebook.find_cell_index(cell) : $(cell).index(); } // ------------------------------------------------------------------------ /** * Return the level of nbcell. * The cell level is an integer in the range 1-7 inclusive * * @param {Object} cell Cell instance or jQuery collection of '.cell' elements * @return {Integer} cell level */ function get_cell_level (cell) { // headings can have a level up to 6, so 7 is used for a non-heading var level = 7; if (cell === undefined) { return level; } if (live_notebook) { if ((typeof(cell) === 'object') && (cell.cell_type === 'markdown')) { level = cell.get_text().match(/^#*/)[0].length || level; } } else { // the jQuery pseudo-selector :header is useful for us, but is // implemented in javascript rather than standard css selectors, // which get implemented in native browser code. // So we get best performance by using css-native first, then filtering var only_child_header = $(cell).find( '.inner_cell > .rendered_html > :only-child' ).filter(':header'); if (only_child_header.length > 0) { level = Number(only_child_header[0].tagName.substring(1)); } } return Math.min(level, 7); // we rely on 7 being max } /** * Check if a cell is a heading cell. * * @param {Object} cell Cell instance or jQuery collection of '.cell' elements * @return {Boolean} */ function is_heading (cell) { return get_cell_level(cell) < 7; } /** * Check if a heading cell is collapsed. * * Should in general return false on non-heading cells, but this is * dependent on metadata/css classes, so don't rely on it. * * @param {Object} cell Cell instance or jQuery collection of '.cell' elements * @return {Boolean} */ function _is_collapsed (heading_cell) { if (live_notebook) { return heading_cell.metadata.heading_collapsed === true; } return $(heading_cell).hasClass('collapsible_headings_collapsed'); } /** * Alter cell so that _is_collapsed called on it will return set_collapsed */ function _set_collapsed (heading_cell, set_collapsed) { set_collapsed = set_collapsed !== undefined ? set_collapsed : true; if (live_notebook) { if (set_collapsed) { heading_cell.metadata.heading_collapsed = true; } else { delete heading_cell.metadata.heading_collapsed; } } else { $(heading_cell).toggleClass('collapsible_headings_collapsed', set_collapsed); } return set_collapsed; } /** * Check if a cell is a collapsed heading cell. * * @param {Object} cell Cell instance or jQuery collection of '.cell' elements * @return {Boolean} */ function is_collapsed_heading (cell) { return is_heading(cell) && _is_collapsed(cell); } /** * Uncollapse any headings which are hiding the cell at index * * @param {Integer} index - index of cell to reveal */ function reveal_cell_by_index (index) { // Restrict the search to cells that are of the same level and lower // than the currently selected cell by index. var ref_cell = _get_cell_at_index(index); // ref_cell may be null, if we've attempted to extend selection beyond // the existing cells if (!ref_cell) { return; } var pivot_level = get_cell_level(ref_cell); var cells = _get_cells(); while (index > 0 && pivot_level > 1) { index--; var cell = cells[index]; var cell_level = get_cell_level(cell); if (cell_level < pivot_level) { if (is_collapsed_heading(cell)) { toggle_heading(cell); } pivot_level = cell_level; } } } /** * Add or remove collapsed/uncollapsed classes & metadata to match the * cell's status as a non-heading or collapsed/uncollapsed heading * * @param {Object} cell Cell instance or jQuery collection of '.cell' elements * @return {undefined} */ function update_heading_cell_status (cell) { var level = get_cell_level(cell); var cell_is_heading = level < 7; var cell_elt = live_notebook ? cell.element : $(cell); var cht = cell_elt.find('.input_prompt > .collapsible_headings_toggle'); if (cell_is_heading) { var collapsed = _is_collapsed(cell); cell_elt.toggleClass('collapsible_headings_collapsed', collapsed); cell_elt.toggleClass('collapsible_headings_ellipsis', params.show_ellipsis); if (params.use_toggle_controls) { if (cht.length < 1) { cht = $('
') .addClass('collapsible_headings_toggle') .css('color', params.toggle_color) .append('
') .appendTo(cell_elt.find('.input_prompt')); var clickable = cht.find('i'); if (params.make_toggle_controls_buttons) { cht.addClass('btn btn-default'); clickable = cht; } if (live_notebook) { clickable.on('click', function () { toggle_heading(cell); }); } else { // in non-live notebook, cell isn;t editable, so make it clickable also var only_child_header = cell_elt.find( '.inner_cell > .rendered_html > :only-child' ).filter(':header'); clickable.add(only_child_header) .css('cursor', 'pointer') .on('click', function (evt) { // evt.target is what was clicked, not what the handler was attached to if (!$(evt.target).hasClass('anchor-link')) { toggle_heading(cell); } }); } } // Update the cell's toggle control classes var hwrap = cht.children(); hwrap.find('.fa') .toggleClass(params.toggle_closed_icon, collapsed) .toggleClass(params.toggle_open_icon, !collapsed); if (params.size_toggle_controls_by_level) { for (var hh = 1; hh < 7; hh++) { hwrap.toggleClass('h' + hh, hh == level); } } } } else { _set_collapsed(cell, false); cell_elt.removeClass('collapsible_headings_collapsed'); cht.remove(); } } /** * find the closest header cell to input cell * * @param {Object} cell Cell instance or jQuery collection of '.cell' elements * @param {Function} a function to filter which header cells can be * returned. Should take a notebook cell/jquer element as * input (depending on whether we're in a live notebook), * and return true if the given cell is acceptable. * @return {Object | undefined} */ function find_header_cell (cell, test_func) { var index = _find_cell_index(cell); for (; index >= 0; index--) { cell = _get_cell_at_index(index); if (is_heading(cell) && (test_func === undefined || test_func(cell))) { return cell; } } return undefined; } /** * Select the section enclosed by the given heading cell. * * Only callable from a live notebook, so require no special cell handling * * @param {Object} head_cell Cell instance or jQuery collection of '.cell' elements * @return {undefined} */ function select_heading_section(head_cell, extend) { var head_lvl = get_cell_level(head_cell); var ncells = Jupyter.notebook.ncells(); var head_ind = _find_cell_index(head_cell); var tail_ind; for (tail_ind = head_ind; tail_ind + 1 < ncells; tail_ind++) { if (get_cell_level(_get_cell_at_index(tail_ind + 1)) <= head_lvl) { break; } } select_reveals = params.select_reveals; if (extend) { var ank_ind = Jupyter.notebook.get_anchor_index(); if (ank_ind <= head_ind) { // keep current anchor, extend to head Jupyter.notebook.select(tail_ind, false); select_reveals = true; return; } else if (ank_ind >= tail_ind) { // keep current anchor, extend to tail Jupyter.notebook.select(head_ind, false); select_reveals = true; return; } // head_ind < ank_ind < tail_ind i.e. anchor is inside section } // move_anchor to header cell Jupyter.notebook.select(head_ind, true); // don't move anchor, i.e. extend, to tail cell Jupyter.notebook.select(tail_ind, false); select_reveals = true; } /** * Return all of the cell _elements _which are part of the section headed by * the given cell * * @param {Object} head_cell Cell instance or jQuery collection of '.cell' elements */ function get_jquery_bracket_section (head_cell) { var head_lvl = get_cell_level(head_cell); var cells = _get_cells(); var cell_elements = $(live_notebook ? head_cell.element : head_cell); for (var ii = _find_cell_index(head_cell); ii < cells.length; ii++) { var cell = live_notebook ? cells[ii] : cells.eq(ii); if (get_cell_level(cell) <= head_lvl) { break; } cell_elements = cell_elements.add(live_notebook ? cell.element : cell); } return cell_elements; } /** * Callback function attached to the bracket-containing div, should toggle * the relevant heading */ var bracket_callback_timeout_id; function bracket_callback (evt) { // prevent bubbling, otherwise when closing a section, the cell gets // selected & re-revealed after being hidden evt.preventDefault(); evt.stopPropagation(); // evt.target is what was clicked, not what the handler was attached to var bracket = $(evt.target); var bracket_level = Number(bracket.attr('data-bracket-level')); if (bracket_level) { var bracket_cell = live_notebook ? bracket.closest('.cell').data('cell') : bracket.closest('.cell'); var header_cell = find_header_cell(bracket_cell, function (cell) { return get_cell_level(cell) == bracket_level; }); switch (evt.type) { case 'dblclick': clearTimeout(bracket_callback_timeout_id); bracket_callback_timeout_id = undefined; toggle_heading(header_cell); break; case 'click': if (live_notebook && (bracket_callback_timeout_id === undefined)) { bracket_callback_timeout_id = setTimeout(function () { select_heading_section(header_cell, evt.shiftKey); bracket_callback_timeout_id = undefined; }, 300); } break; case 'mouseenter': case 'mouseleave': var in_section = get_jquery_bracket_section(header_cell) .find('.chb div[data-bracket-level=' + bracket_level + ']'); $('.chb div').not(in_section).removeClass('chb-hover'); in_section.toggleClass('chb-hover', evt.type === 'mouseenter'); break; } } return false; } /** * Update the hidden/collapsed status of all the cells under * - the notebook, if param cell === undefined * - the heading which contains the specified cell (if cell !== undefined, * but is also not a heading) * - the specified heading cell (if specified cell is a heading) * * @param {Object} cell Cell instance or jQuery collection of '.cell' elements * @return {undefined} */ function update_collapsed_headings (cell) { var index = 0; var section_level = 0; var show = true; if (cell !== undefined && (cell = find_header_cell(cell)) !== undefined) { index = _find_cell_index(cell) + 1; section_level = get_cell_level(cell); show = !_is_collapsed(cell); } var hide_above = 7; var brackets_open = {}; var max_open = 0; // count max number open at one time to calc padding for (var cells = _get_cells(); index < cells.length; index++) { cell = cells[index]; var cell_elt = live_notebook ? cell.element : $(cell); var level = get_cell_level(cell); if (level <= section_level) { break; } if (show && level <= hide_above) { cell_elt.slideDown('fast'); hide_above = is_collapsed_heading(cell) ? level : 7; if (live_notebook) { delete cell.metadata.hidden; } } else { cell_elt.slideUp('fast'); if (live_notebook) { cell.metadata.hidden = true; } continue; } if (params.show_section_brackets) { var chb = cell_elt.find('.chb').empty(); if (chb.length < 1) { chb = $('
') .addClass('chb') .on('click dblclick', bracket_callback) .appendTo(cell_elt); } var num_open = 0; // count number of brackets currently open for (var jj = 1; jj < 7; jj++) { if (brackets_open[jj] && level <= jj) { brackets_open[jj].addClass('chb-end'); // closing, add class delete brackets_open[jj]; // closed } var opening = level == jj; if (brackets_open[jj] || opening) { num_open++; brackets_open[jj] = $('
') .on('mouseenter mouseleave', bracket_callback) .attr('data-bracket-level', jj) .appendTo(chb); // add bracket element if (opening) { // opening, add class brackets_open[jj].addClass('chb-start'); } } } max_open = Math.max(num_open, max_open); } } if (params.show_section_brackets) { // close any remaining for (var ii in brackets_open) { brackets_open[ii].addClass('chb-end'); } // adjust padding to fit in brackets var bwidth = params.section_bracket_width; var dwidth = max_open * (2 + bwidth); $('#notebook-container').css('padding-right', (16 + dwidth) + 'px'); $('.chb') .css('right', '-' + (3 + dwidth) + 'px') .find('div') .css('width', bwidth); } } /** * Hide/reveal all cells in the section headed by cell. * * @param {Object} cell Cell instance or jQuery collection of '.cell' elements */ function toggle_heading (cell, set_collapsed, trigger_event) { if (is_heading(cell)) { if (set_collapsed === undefined) { set_collapsed = !_is_collapsed(cell); } _set_collapsed(cell, set_collapsed); update_heading_cell_status(cell); update_collapsed_headings(params.show_section_brackets ? undefined : cell); console.log(log_prefix, set_collapsed ? 'collapsed' : 'expanded', 'cell', _find_cell_index(cell)); if (trigger_event !== false) { events.trigger((set_collapsed ? '' : 'un') + 'collapse.CollapsibleHeading', {cell: cell}); } } } /** * Return a promise which resolves when the Notebook class methods have * been appropriately patched. * Patches methods * - Notebook.select * - Notebook.undelete * * @return {Promise} */ function patch_Notebook () { return new Promise(function (resolve, reject) { requirejs(['notebook/js/notebook'], function on_success (notebook) { console.debug(log_prefix, 'patching Notebook.protoype'); // we have to patch select, since the select.Cell event is only fired // by cell click events, not by the notebook select method var orig_notebook_select = notebook.Notebook.prototype.select; notebook.Notebook.prototype.select = function (index, moveanchor) { if (select_reveals) { reveal_cell_by_index(index); } return orig_notebook_select.apply(this, arguments); }; resolve(); }, reject); }).catch(function on_reject (reason) { console.warn(log_prefix, 'error patching Notebook.protoype:', reason); }); } /** * Return a promise which resolves when the TextCell class methods have * been appropriately patched. * * Patches TextCell.set_text to update headings. * This is useful for undelete and copy/paste of cells, which don't fire * markdown. * * @return {Promise} */ function patch_TextCell () { return new Promise(function (resolve, reject) { requirejs(['notebook/js/textcell'], function on_success (textcell) { console.debug(log_prefix, 'patching TextCell.protoype'); var orig_set_text = textcell.TextCell.prototype.set_text; textcell.TextCell.prototype.set_text = function (text) { var ret = orig_set_text.apply(this, arguments); if (Jupyter.notebook._fully_loaded) { update_heading_cell_status(this); update_collapsed_headings(); } return ret; }; resolve(); }, reject); }).catch(function on_reject (reason) { console.warn(log_prefix, 'error patching TextCell.protoype:', reason); }); } /** * Return a promise which resolves when the Tooltip class methods have * been appropriately patched. * * For notebook 4.x, cells had css position:static, and changing them to * relative to get heading brackets working broke the tooltip position * calculation. In order to fix this, we patch the 4.x Tooltip._show * method to temporarily reapply position:static while the tooltip * position is calculated & the animation queued, before revertign to the * css-appled position:relative. * For notebook 5.x, cells are already position:relative, so the patch is * unecessary. * * @return {Promise} */ function patch_Tooltip () { if (Number(Jupyter.version[0]) >= 5) { return Promise.resolve(); } return new Promise(function (resolve, reject) { requirejs(['notebook/js/tooltip'], function on_success (tooltip) { console.debug(log_prefix, 'patching Tooltip.prototype'); var orig_tooltip__show = tooltip.Tooltip.prototype._show; tooltip.Tooltip.prototype._show = function (reply) { var $cell = $(this.code_mirror.getWrapperElement()).closest('.cell'); $cell.css('position', 'static'); var ret = orig_tooltip__show.apply(this, arguments); $cell.css('position', ''); return ret; }; resolve(); }, reject); }).catch(function on_reject (reason) { console.warn(log_prefix, 'error patching Tooltip.prototype:', reason); }); } /** * Return a promise which resolves when the appropriate Jupyter actions * have been patched correctly. * * We patch the up/down arrow actions to skip selecting cells which are * hidden by a collapsed heading * * @return {Promise} */ function patch_actions () { return new Promise(function (resolve, reject) { requirejs(['notebook/js/tooltip'], function on_success (tooltip) { console.debug(log_prefix, 'patching Jupyter up/down actions'); var kbm = Jupyter.keyboard_manager; var action_up = kbm.actions.get("jupyter-notebook:select-previous-cell"); var orig_up_handler = action_up.handler; action_up.handler = function (env) { for (var index = env.notebook.get_selected_index() - 1; (index !== null) && (index >= 0); index--) { if (env.notebook.get_cell(index).element.is(':visible')) { env.notebook.select(index); env.notebook.focus_cell(); return; } } return orig_up_handler.apply(this, arguments); }; var action_down = kbm.actions.get("jupyter-notebook:select-next-cell"); var orig_down_handler = action_down.handler; action_down.handler = function (env) { var ncells = env.notebook.ncells(); for (var index = env.notebook.get_selected_index() + 1; (index !== null) && (index < ncells); index++) { if (env.notebook.get_cell(index).element.is(':visible')) { env.notebook.select(index); env.notebook.focus_cell(); return; } } return orig_down_handler.apply(this, arguments); }; resolve(); }, reject); }).catch(function on_reject (reason) { console.warn(log_prefix, 'error patching Jupyter up/down actions:', reason); }); } /** * register actions to collapse and uncollapse the selected heading cell */ function register_new_actions () { action_names.collapse = Jupyter.keyboard_manager.actions.register({ handler : function (env) { var cell = env.notebook.get_selected_cell(); var is_h = is_heading(cell); if (is_h && !_is_collapsed(cell)) { toggle_heading(cell, true); return; } var filter_func; if (is_h) { var lvl = get_cell_level(cell); filter_func = function (c) { return get_cell_level(c) < lvl; }; } cell = find_header_cell(cell, filter_func); if (cell !== undefined) { Jupyter.notebook.select(Jupyter.notebook.find_cell_index(cell)); cell.focus_cell(); } }, help : "Collapse the selected heading cell's section", icon : params.toggle_closed_icon, help_index: 'c1' }, 'collapse_heading', mod_name ); action_names.collapse_all = Jupyter.keyboard_manager.actions.register({ handler : function (env) { env.notebook.get_cells().forEach(function (c, idx, arr) { toggle_heading(c, true); }); var cell = env.notebook.get_selected_cell(); if (cell.element.is(':hidden')) { cell = find_header_cell(cell, function (c) { return c.element.is(':visible'); }); if (cell !== undefined) { Jupyter.notebook.select(Jupyter.notebook.find_cell_index(cell)); cell.focus_cell(); } } }, help : "Collapse all heading cells' sections", icon : params.toggle_closed_icon, help_index: 'c2' }, 'collapse_all_headings', mod_name ); action_names.uncollapse = Jupyter.keyboard_manager.actions.register({ handler : function (env) { var cell = env.notebook.get_selected_cell(); if (is_heading(cell)) { toggle_heading(cell, false); } else { var ncells = env.notebook.ncells(); for (var ii = env.notebook.find_cell_index(cell); ii < ncells; ii++) { cell = env.notebook.get_cell(ii); if (is_heading(cell)) { env.notebook.select(ii); cell.focus_cell(); break; } } } }, help : "Un-collapse (expand) the selected heading cell's section", icon : params.toggle_open_icon, help_index: 'c3' }, 'uncollapse_heading', mod_name ); action_names.uncollapse_all = Jupyter.keyboard_manager.actions.register({ handler : function (env) { env.notebook.get_cells().forEach(function (c, idx, arr) { toggle_heading(c, false); }); env.notebook.get_selected_cell().focus_cell(); }, help : "Un-collapse (expand) all heading cells' sections", icon : params.toggle_open_icon, help_index: 'c4' }, 'uncollapse_all_headings', mod_name ); action_names.toggle = Jupyter.keyboard_manager.actions.register ({ handler: function () { var heading_cell = find_header_cell(Jupyter.notebook.get_selected_cell(), function (cell) { return cell.element.is(':visible') && !_is_collapsed(cell); }); if (is_heading(heading_cell)) { toggle_heading(heading_cell, true); Jupyter.notebook.select(Jupyter.notebook.find_cell_index(heading_cell)); } }, help : "Toggle closest heading's collapsed status", icon : 'fa-angle-double-up', }, 'toggle_collapse_heading', mod_name ); action_names.toggle_all = Jupyter.keyboard_manager.actions.register ({ handler: function () { var cells = Jupyter.notebook.get_cells(); for (var ii = 0; ii < cells.length; ii++) { if (is_heading(cells[ii])) { Jupyter.keyboard_manager.actions.call(action_names[ is_collapsed_heading(cells[ii]) ? 'uncollapse_all' : 'collapse_all']); return; } } }, help : 'Collapse/uncollapse all headings based on the status of the first', icon : 'fa-angle-double-up', }, 'toggle_collapse_all_headings', mod_name ); action_names.select = Jupyter.keyboard_manager.actions.register({ handler : function (env) { var cell = env.notebook.get_selected_cell(); if (is_heading(cell)) { select_heading_section(cell, true); } }, help : "Select all cells in the selected heading cell's section", help_index: 'c3' }, 'select_heading_section', mod_name ); action_names.insert_above = Jupyter.keyboard_manager.actions.register({ handler : function (env) { insert_heading_cell(true); }, help : "Insert a heading cell above the selected cell", help_index: 'c4', icon: 'fa-caret-up' }, 'insert_heading_above', mod_name ); action_names.insert_below = Jupyter.keyboard_manager.actions.register({ handler : function (env) { insert_heading_cell(false); }, help : "Insert a heading cell below the selected cell's section", help_index: 'c5', icon: 'fa-caret-down' }, 'insert_heading_below', mod_name ); } function imitate_hash_click ($element) { var site = $('#site'); var adjust = $element.offset().top - site.offset().top; site.animate({scrollTop: site.scrollTop() + adjust}); } /** * Insert a new heading cell either above or below the current section. * only works in a live notebook. */ function insert_heading_cell (above) { var selected_cell = Jupyter.notebook.get_selected_cell(); var ref_cell = find_header_cell(selected_cell) || selected_cell; var level = get_cell_level(ref_cell); level = (level == 7) ? 1 : level; // default to biggest level (1) if (above) { // if above, insert just above selected cell, but keep ref_cell's level ref_cell = selected_cell; } var index = ref_cell.element.index(); if (!above) { // below requires special handling, as we really want to put it // below the currently selected heading's *content* var cells = _get_cells(); for (index=index + 1; index < cells.length; index++) { if (get_cell_level(cells[index]) <= level) { break; } } // if we make it here, index will be == cells.length, which is ok // as it gets the new cell inserted at the bottom of the notebook } // we don't want our newly-inserted cell to trigger opening of headings var cached_select_reveals = select_reveals; select_reveals = false; var new_cell = Jupyter.notebook.insert_cell_above('markdown', index); var new_text = 'New heading'; new_cell.set_text(new_text); new_cell.set_heading_level(level); new_cell.code_mirror.setSelection({line:0, ch: level + 1}, {line:0, ch: level + 1 + new_text.length}); Jupyter.notebook.select(index, true); // restore cached setting select_reveals = cached_select_reveals; Jupyter.notebook.focus_cell(); Jupyter.notebook.edit_mode(); } function refresh_all_headings () { var cells = _get_cells(); for (var ii=0; ii < cells.length; ii++) { update_heading_cell_status(cells[ii]); } update_collapsed_headings(); } function set_collapsible_headings_options (options) { // options may be undefined here, but it's still handled ok by $.extend $.extend(true, params, options); // bind/unbind toc-collapse handler events[params.collapse_to_match_toc ? 'on' : 'off']('collapse.Toc uncollapse.Toc', callback_toc_collapse); // add css for indents if (params.indent_px !== 0) { var lines = []; for (var hh = 1; hh <= 6; hh++) { lines.push( '.collapsible_headings_toggle .h' + hh + ' { margin-right: ' + ((6 - hh) * params.indent_px) + 'px; }' ); } $('