diff --git a/.local/share/jupyter/nbextensions/code_prettify/README_2to3.md b/.local/share/jupyter/nbextensions/code_prettify/README_2to3.md new file mode 100644 index 0000000000000000000000000000000000000000..a5e1b4b3075d456f0e6a6a8f7e8d82aeff388867 --- /dev/null +++ b/.local/share/jupyter/nbextensions/code_prettify/README_2to3.md @@ -0,0 +1,139 @@ +A 2to3 converter +================ + +This nbextension converts python2 code in notebook code cells to python3 code. + +Under the hood, it uses a call to the current notebook kernel to reformat the +code. +The conversion run by the kernel uses Python's standard-library [lib2to3] +module. + +The nbextension provides + +- a toolbar button (configurable to be added or not) + +- a keyboard shortcut for reformatting the current code-cell (default shortcut + is `Ctrl-M`, can also be configured not to add the keyboard shortcut). + +- a keyboard shortcut for reformatting the whole notebook (default shortcut + is `Ctrl-Shift-M`, can also be configured not to add the keyboard shortcut). + +Syntax needs to be correct, but the nbextension may be able to point out basic +syntax errors. + +![](demo_2to3.gif) + + +Options +------- + +All options are provided by the [KerneExecOnCells library] - see the +[internals] section below for details. +There are a few nbextension-wide options, configurable using the +[jupyter_nbextensions_configurator] or by editing the `notebook` section config +file directly. +The options are as follows: + +- `2to3.add_toolbar_button`: + Whether to add a toolbar button to transform the selected cell(s). + Defaults to `true`. + +- `2to3.button_icon`: + A font-awesome class defining the icon used for the toolbar button and + actions. See [fontawesome] for available icon classes. + Defaults to `fa-legal`. + +- `2to3.button_label`: + Toolbar button label text. Also used in the actions' help text. + Defaults to `Convert Python 2 to 3`. + +- `2to3.register_hotkey`: + Whether to register hotkeys to transform the selected cell(s)/whole notebook. + Defaults to `true`. + +- `2to3.hotkeys.process_all`: + Hotkey to use to transform all the code cells in the notebook. + Defaults to `Ctrl-Shift-L`. + +- `2to3.hotkeys.process_selected`: + Hotkey to use to transform the selected cell(s). + Defaults to `Ctrl-L`. + +- `2to3.show_alerts_for_not_supported_kernel`: + Whether to show alerts if the kernel is not supported. + Defaults to `false`. + +- `2to3.show_alerts_for_errors`: + Whether to show alerts for errors in the kernel calls. + Defaults to `true`. + +- `2to3.kernel_config_map_json`: + The value of this key is a string which can be parsed into a json object + giving the config for each kernel language. + + The following give the per-kernel options of the parsed json, using the + language key `python `: + + * `2to3.kernel_config_map_json.python.library`: + String to execute in the kernel in order to load any necessary kernel + libraries. + + * `2to3.kernel_config_map_json.python.replacements_json_to_kernel`: + a list of pairs of strings, used as arguments to javascript's + `String.replace(from, to)` to translate from a json string into a valid + representation of the same string in the kernel language. Since json + strings are particularly simple, this can often (as with the python + language) be left as the default, an empty list. + + * `2to3.kernel_config_map_json.python.prefix` and + `2to3.kernel_config_map_json.python.postfix`: + Strings added as bookends to the kernel string (translated from the json + string using the replacements above) to make up the kernel prettifier call + kernel's prettifier libraries. + + * `2to3.kernel_config_map_json.python.trim_formatted_text`: + Whether to trim whitespace from the transformed cell text. Since jupyter + cells don't usually have leading or trailing whitespace, the default + behaviour is to trim the transformed text, in order to prevent the + transform adding extra newlines at the end (a common behaviour for source + files, where having a trailing newline is often considered good practice). + + +Internals +--------- + +Under the hood, this nbextension uses the [KerneExecOnCells library], a shared +library for creating Jupyter nbextensions which transform code cell text using +calls to the active kernel. + +See the [shared README] for the internal model used by the nbextension. + +History +------- + +The project was forked by [@EWouters] from [@jfbercher]'s [code_prettify], +retaining most of the code. + +It has since been altered to use the [KerneExecOnCells library], a shared +library for creating Jupyter nbextensions which transform code cell text using +calls to the active kernel. + +The 2to3 conversion's kernel-side python code is based on [2to3_nb.py] by +[@takluyver] and [@fperez]. + +It could be extended to use the [futurize] functions so it can convert both +ways. + +[2to3_nb.py]: https://gist.github.com/takluyver/c8839593c615bb2f6e80 +[@EWouters]: https://github.com/EWouters +[@fperez]: https://github.com/fperez +[@jfbercher]: https://github.com/jfbercher +[@takluyver]: https://github.com/takluyver +[code_prettify]: https://github.com/jfbercher/code_prettify +[futurize]: http://python-future.org/automatic_conversion.html +[fontawesome]: https://fontawesome.com/icons +[internals]: #Internals +[jupyter_nbextensions_configurator]: https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator +[KerneExecOnCells library]: README.md +[lib2to3]: https://docs.python.org/3/library/2to3.html#module-lib2to3 +[shared README]: README.md diff --git a/.local/share/jupyter/nbextensions/code_prettify/README_code_prettify.md b/.local/share/jupyter/nbextensions/code_prettify/README_code_prettify.md new file mode 100644 index 0000000000000000000000000000000000000000..d0e346437c01b404aad5ddc33165a748adb99dd7 --- /dev/null +++ b/.local/share/jupyter/nbextensions/code_prettify/README_code_prettify.md @@ -0,0 +1,300 @@ +A Code Prettifier +================= + +This nbextension reformats/prettifies code in notebook code cells. + +Under the hood, it uses a call to the current notebook kernel to reformat the +code. +Thus the actual prettifier package has to be callable from the current kernel +language. + +With an appropriately-configured prettifier for the kernel in use, the +nbextension provides + +- a toolbar button (configurable to be added or not) + +- a keyboard shortcut for reformatting the current code-cell (default shortcut + is `Ctrl-L`, can also be configured not to add the keyboard shortcut). + +- a keyboard shortcut for reformatting the whole notebook (default shortcut + is `Ctrl-Shift-L`, can also be configured not to add the keyboard shortcut). + +Syntax shall be correct. The nbextension may also point out basic syntax errors. + +![](demo-py.gif) +![](demo-R.gif) +![](demo-jv.gif) + + +Compatible Kernels +------------------ + +Example implementations are provided for prettifiers for ipython, ir and +ijavascript kernels which should work out of the box (assuming availability of +the relevant kernel-specific [prerequisites] mentioned below), but the +kernel-specific prettifier calls are configurable, so the model is applicable +to essentially any kernel language and prettifier library. + +Other languages may be added as defaults in the future, but given that there +are more than 50 [kernels] available for Jupyter, it is not easily possible to +support all of them out of the box, unless people with experience in the +relevant kernels have the time to contribute code. For information on how the +reformatting takes place, and how to adapt it for your particular +kernel/prettifier, see the [options] and [internals] sections below. +If you implement a language that isn't yet provided by default, please submit a +PR or let us know to add it to the repo :) + +Under the hood, this nbextension's functionality is provided by the +[KerneExecOnCells library], a shared library for creating Jupyter nbextensions +which transform code cell text using calls to the active kernel. + + +Prerequisites +------------- + +Of course, you must have the necessary kernel-specific packages installed for +the prettifier call to work: + +- for the default python implementation, the [yapf] module is required: + + pip install yapf + + Others you might consider using include [autopep8] - see [README_autopep8.md]. + +- for R, the default implementation uses the [formatR] and [jsonlite] packages: + + ```r + install.packages(c("formatR", "jsonlite"), repos="http://cran.rstudio.com") + ``` + +- for [ijavascript], the [js-beautify] package is used: + (*Under linux, in the root of your user tree = ~*) + + npm install js-beautify + + Under Windows, you may then need to set the `NODE_PATH` environment variable + (see [this question on stackoverflow]) to it to `%AppData%\npm\node_modules` + (Windows 7/8/10). + To be done with it once and for all, add this as a System variable in the + Advanced tab of the System Properties dialog. + + +Options +------- + +All options are provided by the [KerneExecOnCells library]. - see the +[internals] section below for details. +There are a few nbextension-wide options, configurable using the +[jupyter_nbextensions_configurator] or by editing the `notebook` section config +file directly. +The options are as follows: + +- `code_prettify.add_toolbar_button`: + Whether to add a toolbar button to transform the selected cell(s). + Defaults to `true`. + +- `code_prettify.button_icon`: + A font-awesome class defining the icon used for the toolbar button and + actions. See [fontawesome] for available icon classes. + Defaults to `fa-legal`. + +- `code_prettify.button_label`: + Toolbar button label text. Also used in the actions' help text. + Defaults to `Code prettify`. + +- `code_prettify.register_hotkey`: + Whether to register hotkeys to transform the selected cell(s)/whole notebook. + Defaults to `true`. + +- `code_prettify.hotkeys.process_all`: + Hotkey to use to transform all the code cells in the notebook. + Defaults to `Ctrl-Shift-L`. + +- `code_prettify.hotkeys.process_selected`: + Hotkey to use to transform the selected cell(s). + Defaults to `Ctrl-L`. + +- `code_prettify.show_alerts_for_not_supported_kernel`: + Whether to show alerts if the kernel is not supported. + Defaults to `false`. + +- `code_prettify.show_alerts_for_errors`: + Whether to show alerts for errors in the kernel calls. + Defaults to `true`. + +- `code_prettify.kernel_config_map_json`: + The value of this key is a string which can be parsed into a json object + giving the config for each kernel language. + + The following give the per-kernel options of the parsed json, using the + language key `python `: + + * `code_prettify.kernel_config_map_json.python.library`: + String to execute in the kernel in order to load any necessary kernel + libraries. + + * `code_prettify.kernel_config_map_json.python.replacements_json_to_kernel`: + a list of pairs of strings, used as arguments to javascript's + `String.replace(from, to)` to translate from a json string into a valid + representation of the same string in the kernel language. Since json + strings are particularly simple, this can often (as with the python + language) be left as the default, an empty list. + + * `code_prettify.kernel_config_map_json.python.prefix` and + `code_prettify.kernel_config_map_json.python.postfix`: + Strings added as bookends to the kernel string (translated from the json + string using the replacements above) to make up the kernel prettifier call + kernel's prettifier libraries. + + * `code_prettify.kernel_config_map_json.python.trim_formatted_text`: + Whether to trim whitespace from the transformed cell text. Since jupyter + cells don't usually have leading or trailing whitespace, the default + behaviour is to trim the transformed text, in order to prevent the + transform adding extra newlines at the end (a common behaviour for source + files, where having a trailing newline is often considered good practice). + + +Internals +--------- + +Under the hood, this nbextension uses the [KerneExecOnCells library], a shared +library for creating Jupyter nbextensions which transform code cell text using +calls to the active kernel. + +The model is essentially: + +1. The cell text is grabbed by client-side javascript, then turned into a json + string using javascript `JSON.stringify`. Since json-compatible strings are + a particularly simple string format, which is compatible with many other + programming languages without much modification (e.g. a valid json string + is also a valid string in python 3, and also in python 2 when prefixed with + a `u`), and easily converted for use in others (because of its simplicity). + +2. Optional regex replacements are used to translate the json-format string + into a valid kernel string. Python, R and javascript don't require this + step, but other languages may do, so it's implemented for flexibility + using the per-kernel config key `replacements_json_to_kernel`, which is a + list of pairs of arguments to javascript `String.replace`. + +3. The kernel-specific prettifier call is then composed from + `kernel_config.prefix` + `kernel_text_string` + `kernel_config.postfix` and + sent to the kernel for execution. This kernel call is expected to get the + formatted cell text _printed_ as a json-compatible string. Since most + kernel languages have json packages, this should hopefully be easy to + arrange. The reason for the printing text rather than simply displaying it, + is that it prevents us having to translate from a kernel string + representing a json string. + +4. The callback for the kernel execution in client-side javascript parses the + printed json-format string, optionally trims trailing whitespace according + to the `trim_formatted_text` key (which defaults to `true`) in the + per-kernel config, and then sets the cell text using the result. + +The process is probably best illustrated using an example for the python +implementation: + +1. **At nbextension load**, the `code_prettify.kernel_config_map_json` config + option is parsed to give the json object + + ```json + { + "python": { + "library": "import json\nimport yapf.yapflib.yapf_api", + "prefix": "print(json.dumps(yapf.yapflib.yapf_api.FormatCode(u", + "postfix": ")[0]))" + } + } + ``` + + (other kernel languages are omitted for clarity). + +2. **On kernel becoming ready**, the nbextension looks up the config for the + kernel's language (in our example, this is the `python` key of the kernel + config json object above). It then sends the kernel config's `library` + string to the kernel for execution. Thus the python implementation above + executes + + ```python + import json + import yapf.yapflib.yapf_api + ``` + +3. **On requesting a cell be prettified** which can happen by clicking the + toolbar, or with a (configurable) hotkey, the following happens: + + Say the cell to be formatted contains the following ugly python code: + + ```python + msg= 'hello '+"world" + print ( + msg ) + ``` + + Then the result of the `JSON.stringify` call will be a string containing + + ```json + "msg= 'hello '+\"world\"\nprint (\n msg )" + ``` + + (note the opening and closing quotes). Concatenating this with the prefix & + postfix strings from the python kernel config above, gives us the kernel + code to execute. The call sent to the python kernel is therefore + + ```python + print(json.dumps(yapf.yapflib.yapf_api.FormatCode(u"msg= 'hello '+\"world\"\nprint (\n msg )")[0])) + ``` + +4. What gets 'printed' by the kernel (i.e. returned to the javascript stream + callback) is the following json-format string: + + ```json + "msg = 'hello ' + \"world\"\nprint(msg)\n" + ``` + + The default is to trim whitepace from the returned prettified text, which + results in the final prettified python code for the cell: + + ```python + msg = 'hello ' + "world" + print(msg) + ``` + + +History +------- + +- [@jfbercher], august 14, 2016, first version, named `yapf_ext` +- [@jfbercher], august 19, 2016, second version `code_prettify` + - introduced support for R and javascript. + - changed extension name from `yapf_ext` to `code_prettify` +- [@jcb91], december 2016 + - made addition of toolbar button & hotkey configurable + - reworked to avoid regex replacements for conversion to/from kernel string + formats, in favour of json-string interchange + - made kernel-specific prettifier calls configurable, allowing support for + different prettifiers & arbitrary kernels + - improved documentation +- [@jfbercher], december 2016-january 2017 + - added a configurable shortkey to reflow the whole notebook + - extracted most of the code to build a general library of functions, + `kernel_exec_on_cell.js`, which can be used for all nbextensions which + needs to exec some code (via the current kernel) on the text from cells. + + +[@jcb91]: https://github.com/jcb91 +[@jfbercher]: https://github.com/jfbercher +[autopep8]: https://github.com/hhatto/autopep8 +[formatR]: https://yihui.name/formatr +[fontawesome]: https://fontawesome.com/icons +[ijavascript]: https://n-riesco.github.io/ijavascript +[internals]: #Internals +[js-beautify]: https://github.com/beautify-web/js-beautify +[jsonlite]: https://github.com/jeroen/jsonlite +[jupyter_nbextensions_configurator]: https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator +[KerneExecOnCells library]: README.md +[kernels]: https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages +[options]: #Options +[prerequisites]: #Prerequisites +[README_autopep8.md]: README_autopep8.md +[this question on stackoverflow]: https://stackoverflow.com/questions/9587665/nodejs-cannot-find-installed-module-on-windows +[yapf]: https://github.com/google/yapf diff --git a/.local/share/jupyter/nbextensions/code_prettify/demo-py.gif b/.local/share/jupyter/nbextensions/code_prettify/demo-py.gif new file mode 100644 index 0000000000000000000000000000000000000000..2e8d85c1a7b2cd13021b9ec84a71c34dced8f066 Binary files /dev/null and b/.local/share/jupyter/nbextensions/code_prettify/demo-py.gif differ diff --git a/.local/share/jupyter/nbextensions/codefolding/codefolding_firstline_folded.png b/.local/share/jupyter/nbextensions/codefolding/codefolding_firstline_folded.png new file mode 100644 index 0000000000000000000000000000000000000000..4ce9908a53d8777c9261978c7518a2ef86e6713d Binary files /dev/null and b/.local/share/jupyter/nbextensions/codefolding/codefolding_firstline_folded.png differ diff --git a/.local/share/jupyter/nbextensions/codefolding/codefolding_firstline_unfolded.png b/.local/share/jupyter/nbextensions/codefolding/codefolding_firstline_unfolded.png new file mode 100644 index 0000000000000000000000000000000000000000..f421e006b8d2e366515b851547cfd342a77cc7e8 Binary files /dev/null and b/.local/share/jupyter/nbextensions/codefolding/codefolding_firstline_unfolded.png differ diff --git a/.local/share/jupyter/nbextensions/codefolding/codefolding_indent_folded_1.png b/.local/share/jupyter/nbextensions/codefolding/codefolding_indent_folded_1.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b98b7d905b5482e7647aa13fee3df7b5cbfdae Binary files /dev/null and b/.local/share/jupyter/nbextensions/codefolding/codefolding_indent_folded_1.png differ diff --git a/.local/share/jupyter/nbextensions/codefolding/edit.js b/.local/share/jupyter/nbextensions/codefolding/edit.js new file mode 100644 index 0000000000000000000000000000000000000000..5edc30ab63bee640d28e28ddb3a7d8fd1abf0dfa --- /dev/null +++ b/.local/share/jupyter/nbextensions/codefolding/edit.js @@ -0,0 +1,4 @@ +define(['./main'], function (codefolding) { + "use strict"; + return codefolding; +}); diff --git a/.local/share/jupyter/nbextensions/codefolding/foldgutter.css b/.local/share/jupyter/nbextensions/codefolding/foldgutter.css new file mode 100644 index 0000000000000000000000000000000000000000..e0eee3265d694b9c9080b449bb7428e300b1eef7 --- /dev/null +++ b/.local/share/jupyter/nbextensions/codefolding/foldgutter.css @@ -0,0 +1,5 @@ +.CodeMirror-foldgutter { + width: .9em; +} + + diff --git a/.local/share/jupyter/nbextensions/codemirror_mode_extensions/codemirror_mode_extensions.yaml b/.local/share/jupyter/nbextensions/codemirror_mode_extensions/codemirror_mode_extensions.yaml new file mode 100644 index 0000000000000000000000000000000000000000..527864633bfac9559eb9d37b67259fa0bb832ae2 --- /dev/null +++ b/.local/share/jupyter/nbextensions/codemirror_mode_extensions/codemirror_mode_extensions.yaml @@ -0,0 +1,7 @@ +Type: Jupyter Notebook Extension +Name: CodeMirror mode extensions +Description: | + Extends some CodeMirror modes with extra features. Currently just adds + support for comment/uncomment and folding for octave/MATLAB mode. +Main: main.js +Compatibility: 4.x diff --git a/.local/share/jupyter/nbextensions/collapsible_headings/icon.png b/.local/share/jupyter/nbextensions/collapsible_headings/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ee07c9e6a0cafcd3fda79d54974b4919f8abf9fc Binary files /dev/null and b/.local/share/jupyter/nbextensions/collapsible_headings/icon.png differ diff --git a/.local/share/jupyter/nbextensions/collapsible_headings/main.css b/.local/share/jupyter/nbextensions/collapsible_headings/main.css new file mode 100644 index 0000000000000000000000000000000000000000..82be64f5b3fba57ec2de2235ea840a71c87d05ca --- /dev/null +++ b/.local/share/jupyter/nbextensions/collapsible_headings/main.css @@ -0,0 +1,130 @@ +.collapsible_headings_toggle .h1 { + font-size: 185.7%; + margin: 0.538em 0 0 0; + line-height: 1.0; +} +.collapsible_headings_toggle .h2 { + font-size: 157.1%; + margin: 0.636em 0 0 0; + line-height: 1.0; +} +.collapsible_headings_toggle .h3 { + font-size: 128.6%; + margin: 0.777em 0 0 0; + line-height: 1.0; +} +.collapsible_headings_toggle .h4, +.collapsible_headings_toggle .h5, +.collapsible_headings_toggle .h6 { + font-size: 100%; + margin: 1em 0 0 0; + line-height: 1.0; +} + +.collapsible_headings_toggle.btn .h1, +.collapsible_headings_toggle.btn .h2, +.collapsible_headings_toggle.btn .h3, +.collapsible_headings_toggle.btn .h4, +.collapsible_headings_toggle.btn .h5, +.collapsible_headings_toggle.btn .h6 { + margin-top: 0; +} + +.collapsible_headings_toggle .fa { + transition: transform 400ms; + + /* don't support IE filter, since can't rotate 360 */ + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -o-transform: rotate(360deg); + transform: rotate(360deg); +} + +.collapsible_headings_collapsed .fa { + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + -o-transform: none; + transform: none; +} + +/* bracket rules */ + +div.cell { + position: relative; +} + +.chb { + position: absolute; + top: -1px; + bottom: -1px; + left: calc(100% + 3px); + display: flex; + flex-direction: row-reverse; + justify-content: flex-start; + align-items: stretch; +} + +.chb div { + margin-left: 2px; + width: 5px; + border-color: #aaa; + border-left-color: transparent; + border-style: solid; + border-width: 0 2px 0 2px; +} + +.collapsible_headings_collapsed .chb .chb-start { + border-width: 5px 2px 2px 4px; +} + +.chb div:hover, +.chb .chb-hover, +.jupyter-soft-selected .chb div{ + border-color: #42A5F5; + border-left-color: transparent; + border-width: 0 3px 0 0; +} + +.chb .chb-start { + border-top-width: 1px; + margin-top: 2px; +} + +.chb .chb-end { + border-bottom-width: 1px; + margin-bottom: 2px; +} + +.chb-start div:hover, .chb .chb-start.chb-hover, .jupyter-soft-selected .chb .chb-start { + border-top-width: 2px; +} + +.chb-end div:hover, .chb .chb-end.chb-hover, .jupyter-soft-selected .chb .chb-end { + border-bottom-width: 2px; +} + +/* ellipsis rules */ +.collapsible_headings_ellipsis .rendered_html h1, +.collapsible_headings_ellipsis .rendered_html h2, +.collapsible_headings_ellipsis .rendered_html h3, +.collapsible_headings_ellipsis .rendered_html h4, +.collapsible_headings_ellipsis .rendered_html h5, +.collapsible_headings_ellipsis .rendered_html h6 { + position: relative; + padding-right: 2em; +} + +.collapsible_headings_collapsed.collapsible_headings_ellipsis .rendered_html h1:after, +.collapsible_headings_collapsed.collapsible_headings_ellipsis .rendered_html h2:after, +.collapsible_headings_collapsed.collapsible_headings_ellipsis .rendered_html h3:after, +.collapsible_headings_collapsed.collapsible_headings_ellipsis .rendered_html h4:after, +.collapsible_headings_collapsed.collapsible_headings_ellipsis .rendered_html h5:after, +.collapsible_headings_collapsed.collapsible_headings_ellipsis .rendered_html h6:after { + position: absolute; + right: 0; + bottom: 0; + content: "[\002026]"; + color: #aaa; +} \ No newline at end of file diff --git a/.local/share/jupyter/nbextensions/collapsible_headings/main.js b/.local/share/jupyter/nbextensions/collapsible_headings/main.js new file mode 100644 index 0000000000000000000000000000000000000000..a4e224fb0e2da3b6db1105b8bc47329a97c759a2 --- /dev/null +++ b/.local/share/jupyter/nbextensions/collapsible_headings/main.js @@ -0,0 +1,1092 @@ +(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; }' + ); + } + $('