JavaScript Notebook Extensions.ipynb
613 lines
| 18.2 KiB
| text/plain
|
TextLexer
Brian E. Granger
|
r17485 | { | ||
Min RK
|
r18669 | "cells": [ | ||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"# Embrasing web standards" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "One of the main reasons we developed the current notebook as a web application \n", | ||
"is to embrace the available-everywhere web technology. \n", | ||||
Min RK
|
r18669 | "\n", | ||
Ben Rousch
|
r21628 | "Being a pure web application using only HTML, Javascript and CSS, the Notebook can access \n", | ||
"all of the web technology improvements for free. Thus, as browsers support for different \n", | ||||
"media extend, the notebook web app should be compatible without modification. \n", | ||||
Min RK
|
r18669 | "\n", | ||
Ben Rousch
|
r21628 | "This is also true with performance of the User Interface as the speed of the Javascript \n", | ||
"VM increases. " | ||||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"The other advantage of using only web technology is that the code of the interface is fully accessible to the end user, and modifiable live.\n", | ||||
"Even if this task is not always easy, we strive to keep our code as accessible and reusable as possible.\n", | ||||
Ben Rousch
|
r21628 | "This should allow - with minimum effort - development of small extensions that customize the behavior of the web interface. " | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "## Tampering with the Notebook app" | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "The first tool that is available to you and that you should be aware of are browser \"developer tools\". The exact name of these tools is different in each browser, and might require the installation of extensions. But basically they can allow you to inspect/modify the DOM, and interact with the Javascript code that runs the frontend.\n", | ||
Min RK
|
r18669 | "\n", | ||
Ben Rousch
|
r21628 | " - In Chrome and Safari, Developer tools are in the menu [Menu -> More tools -> Developer Tools] \n", | ||
" - In Firefox you might need to install [Firebug](http://getfirebug.com/)\n", | ||||
Min RK
|
r18669 | " - others ?\n", | ||
" \n", | ||||
Ben Rousch
|
r21628 | "Those will be your best friends to debug and try different approaches for your extensions." | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"### Injecting JS" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"#### using magics" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "The above tools can be tedious for editing long Javascipt files. Helpfully, we provide the `%%javascript` magic. This allows you to quickly inject Javascript into the notebook. Still, the Javascript injected this way will not survive reloading. Hence, it is a good tool for testing and refining a script.\n", | ||
Min RK
|
r18669 | "\n", | ||
Ben Rousch
|
r21628 | "You might see here and there people modifying CSS and injecting Javascript into notebook by reading files and publishing them into the notebook.\n", | ||
"Not only does this often break the flow of the notebook and break the re-execution of the notebook, but it also means that you need to execute those cells every time you need to update the code.\n", | ||||
Min RK
|
r18669 | "\n", | ||
Ben Rousch
|
r21628 | "This can still be useful in some cases, like the `%autosave` magic that allows you to control the time between each save. But this can be replaced by a Javascript dropdown menu to select a save interval." | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
"execution_count": null, | ||||
"metadata": { | ||||
"collapsed": false | ||||
}, | ||||
"outputs": [], | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "## You can inspect the autosave code to see what it does.\n", | ||
Min RK
|
r18669 | "%autosave??" | ||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"#### custom.js" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "To inject Javascript we provide an entry point: `custom.js` that allows the user to execute and load other resources into the notebook.\n", | ||
"Javascript code in `custom.js` will be executed when the notebook app starts and can then be used to customize almost anything in the UI and in the behavior of the notebook.\n", | ||||
Min RK
|
r18669 | "\n", | ||
Ben Rousch
|
r21628 | "`custom.js` can be found in the IPython profile dir, and so you can have different UI modifications on a per-profile basis, as well as share your modfications with others." | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"##### Because we like you...." | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "You have been provided with an pre-existing profile folder with this tutorial...\n", | ||
"Start the notebook from the root of the tutorial directory with :\n", | ||||
Min RK
|
r18669 | "\n", | ||
"```bash\n", | ||||
"$ ipython notebook --ProfileDir.location=./profile_euroscipy\n", | ||||
"```" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"##### but back to theory" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
"execution_count": null, | ||||
"metadata": { | ||||
"collapsed": false | ||||
}, | ||||
Jonathan Frederic
|
r20536 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"profile_dir = ! ipython locate\n", | ||||
"profile_dir = profile_dir[0]\n", | ||||
"profile_dir" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"and custom js is in " | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
"execution_count": null, | ||||
"metadata": { | ||||
"collapsed": false | ||||
}, | ||||
"outputs": [], | ||||
"source": [ | ||||
"import os.path\n", | ||||
"custom_js_path = os.path.join(profile_dir,'profile_default','static','custom','custom.js')" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
"execution_count": null, | ||||
"metadata": { | ||||
"collapsed": false | ||||
}, | ||||
Jonathan Frederic
|
r20536 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"# my custom js\n", | ||||
"with open(custom_js_path) as f:\n", | ||||
" for l in f: \n", | ||||
" print l," | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "Note that `custom.js` is meant to be modified by the user. When writing a script, you can define it in a separate file and add a line of configuration into `custom.js` that will fetch and execute the file." | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "**Warning** : even if modification of `custom.js` takes effect immediately after a browser refresh (except if browser cache is aggressive), *creating* a file in `static/` directory needs a **server restart**." | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"## Exercise :" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
" - Create a `custom.js` in the right location with the following content:\n", | ||||
"```javascript\n", | ||||
"alert(\"hello world from custom.js\")\n", | ||||
"```\n", | ||||
"\n", | ||||
" - Restart your server and open any notebook.\n", | ||||
" - Be greeted by custom.js" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "Have a look at [default custom.js](https://github.com/ipython/ipython/blob/1.x/IPython/html/static/custom/custom.js), to see it's contents and for more explanation." | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"#### For the quick ones : " | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "We've seen above that you can change the autosave rate by using a magic. This is typically something I don't want to type everytime, and that I don't like to embed into my workflow and documents. (The reader doesn't care what my autosave time is), so let's build an extension that allow to do it. " | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": { | ||||
"foo": true | ||||
}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "Create a dropdown elemement in the toolbar (DOM `IPython.toolbar.element`). You will need \n", | ||
Min RK
|
r18669 | "\n", | ||
"- `IPython.notebook.set_autosave_interval(miliseconds)`\n", | ||||
Ben Rousch
|
r21628 | "- know that 1 min = 60 sec, and 1 sec = 1000 ms" | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"```javascript\n", | ||||
"\n", | ||||
"var label = jQuery('<label/>').text('AutoScroll Limit:');\n", | ||||
"var select = jQuery('<select/>')\n", | ||||
" //.append(jQuery('<option/>').attr('value', '2').text('2min (default)'))\n", | ||||
" .append(jQuery('<option/>').attr('value', undefined).text('disabled'))\n", | ||||
"\n", | ||||
" // TODO:\n", | ||||
" //the_toolbar_element.append(label)\n", | ||||
" //the_toolbar_element.append(select);\n", | ||||
" \n", | ||||
"select.change(function() {\n", | ||||
" var val = jQuery(this).val() // val will be the value in [2]\n", | ||||
" // TODO\n", | ||||
" // this will be called when dropdown changes\n", | ||||
"\n", | ||||
"});\n", | ||||
"\n", | ||||
"var time_m = [1,5,10,15,30];\n", | ||||
"for (var i=0; i < time_m.length; i++) {\n", | ||||
" var ts = time_m[i];\n", | ||||
" //[2] ____ this will be `val` on [1] \n", | ||||
" // | \n", | ||||
" // v \n", | ||||
" select.append($('<option/>').attr('value', ts).text(thr+'min'));\n", | ||||
" // this will fill up the dropdown `select` with\n", | ||||
" // 1 min\n", | ||||
" // 5 min\n", | ||||
" // 10 min\n", | ||||
" // 10 min\n", | ||||
" // ...\n", | ||||
"}\n", | ||||
"```" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "#### A non-interactive example first" | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"I like my cython to be nicely highlighted\n", | ||||
"\n", | ||||
"```javascript\n", | ||||
Min RK
|
r21253 | "traitlets.config.cell_magic_highlight['magic_text/x-cython'] = {}\n", | ||
"traitlets.config.cell_magic_highlight['magic_text/x-cython'].reg = [/^%%cython/]\n", | ||||
Min RK
|
r18669 | "```\n", | ||
"\n", | ||||
Ben Rousch
|
r21628 | "`text/x-cython` is the name of a CodeMirror mode name, the `magic_` prefix will just patch the mode so that the first line that contains a magic does not screw up the highlighting. `reg`is a list or regular expression that will trigger the change of mode." | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"#### Get more docs" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "Sadly, you will have to read the Javascript source file (but there are lots of comments) and/or build the Javascript documentation using YUIDoc.\n", | ||
Min RK
|
r18669 | "If you have `node` and `yui-doc` installed:" | ||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"```bash\n", | ||||
"$ cd ~/ipython/IPython/html/static/notebook/js/\n", | ||||
"$ yuidoc . --server\n", | ||||
"warn: (yuidoc): Failed to extract port, setting to the default :3000\n", | ||||
"info: (yuidoc): Starting YUIDoc@0.3.45 using YUI@3.9.1 with NodeJS@0.10.15\n", | ||||
"info: (yuidoc): Scanning for yuidoc.json file.\n", | ||||
"info: (yuidoc): Starting YUIDoc with the following options:\n", | ||||
"info: (yuidoc):\n", | ||||
"{ port: 3000,\n", | ||||
" nocode: false,\n", | ||||
" paths: [ '.' ],\n", | ||||
" server: true,\n", | ||||
" outdir: './out' }\n", | ||||
"info: (yuidoc): Scanning for yuidoc.json file.\n", | ||||
"info: (server): Starting server: http://127.0.0.1:3000\n", | ||||
"```\n", | ||||
"\n", | ||||
Ben Rousch
|
r21628 | "and browse to http://127.0.0.1:3000 to get the docs" | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": { | ||||
"foo": true | ||||
}, | ||||
"source": [ | ||||
"#### Some convenience methods" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "By browsing the docs you will see that we have some convenience methods that avoid re-inventing the UI everytime :\n", | ||
Min RK
|
r18669 | "```javascript\n", | ||
"IPython.toolbar.add_buttons_group([\n", | ||||
" {\n", | ||||
" 'label' : 'run qtconsole',\n", | ||||
" 'icon' : 'icon-terminal', // select your icon from \n", | ||||
" // http://fortawesome.github.io/Font-Awesome/icons/\n", | ||||
" 'callback': function(){IPython.notebook.kernel.execute('%qtconsole')}\n", | ||||
" }\n", | ||||
" // add more button here if needed.\n", | ||||
" ]);\n", | ||||
"```\n", | ||||
"with a [lot of icons] you can select from. \n", | ||||
"\n", | ||||
"[lot of icons]: http://fortawesome.github.io/Font-Awesome/icons/" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": { | ||||
"foo": true | ||||
}, | ||||
"source": [ | ||||
"## Cell Metadata" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": { | ||||
"foo": true | ||||
}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "The most requested feature is generally to be able to distinguish individual cells in the notebook, or run specific actions with them.\n", | ||
"To do so, you can either use `IPython.notebook.get_selected_cell()`, or rely on `CellToolbar`. This allows you to register a set of actions and graphical elements that will be attached to individual cells." | ||||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"### Cell Toolbar" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "You can see some examples of what can be done by toggling the `Cell Toolbar` selector in the toolbar on top of the notebook. It provides two default `presets` that are `Default` and `slideshow`. Defaults allow editing the metadata attached to each cell manually." | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "First we define a function that takes as the first parameter an element on the DOM into which to inject UI element. The second element will be the cell with which this element will be registerd. Then we will need to register that function and give it a name.\n" | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"#### Register a callback" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
Jonathan Frederic
|
r20536 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
Jonathan Frederic
|
r20536 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"%%javascript\n", | ||||
"var CellToolbar = IPython.CellToolbar\n", | ||||
"var toggle = function(div, cell) {\n", | ||||
" var button_container = $(div)\n", | ||||
"\n", | ||||
Ben Rousch
|
r21628 | " // Let's create a button that shows the current value of the metadata\n", | ||
Min RK
|
r18669 | " var button = $('<button/>').addClass('btn btn-mini').text(String(cell.metadata.foo));\n", | ||
"\n", | ||||
" // On click, change the metadata value and update the button label\n", | ||||
" button.click(function(){\n", | ||||
" var v = cell.metadata.foo;\n", | ||||
" cell.metadata.foo = !v;\n", | ||||
" button.text(String(!v));\n", | ||||
" })\n", | ||||
"\n", | ||||
" // add the button to the DOM div.\n", | ||||
" button_container.append(button);\n", | ||||
"}\n", | ||||
"\n", | ||||
" // now we register the callback under the name foo to give the\n", | ||||
" // user the ability to use it later\n", | ||||
" CellToolbar.register_callback('tuto.foo', toggle);" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"#### Registering a preset" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "This function can now be part of any `preset` of the CellToolBar." | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
Jonathan Frederic
|
r20536 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false, | ||||
"foo": true, | ||||
"slideshow": { | ||||
"slide_type": "subslide" | ||||
} | ||||
}, | ||||
Jonathan Frederic
|
r20536 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"%%javascript\n", | ||||
"IPython.CellToolbar.register_preset('Tutorial 1',['tuto.foo','default.rawedit'])\n", | ||||
"IPython.CellToolbar.register_preset('Tutorial 2',['slideshow.select','tuto.foo'])" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"You should now have access to two presets :\n", | ||||
"\n", | ||||
" - Tutorial 1\n", | ||||
" - Tutorial 2\n", | ||||
" \n", | ||||
Ben Rousch
|
r21628 | "And check that the buttons you defined share state when you toggle preset. \n", | ||
"Also check that the metadata of the cell is modified when you click the button, and that the saved metadata is still available on reload." | ||||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"#### Exercise:" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"Try to wrap the all code in a file, put this file in `{profile}/static/custom/<a-name>.js`, and add \n", | ||||
"\n", | ||||
"```\n", | ||||
"require(['custom/<a-name>']);\n", | ||||
"```\n", | ||||
"\n", | ||||
"in `custom.js` to have this script automatically loaded in all your notebooks.\n", | ||||
"\n" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
Ben Rousch
|
r21628 | "`require` is provided by a [javascript library](http://requirejs.org/) that allows you to express dependency. For simple extensions like the previous one, we directly mute the global namespace, but for more complex extensions you could pass a callback to the `require([...], <callback>)` call, to allow the user to pass configuration information to your plugin.\n", | ||
Min RK
|
r18669 | "\n", | ||
"In Python lang, \n", | ||||
"\n", | ||||
"```javascript\n", | ||||
"require(['a/b', 'c/d'], function( e, f){\n", | ||||
" e.something()\n", | ||||
" f.something()\n", | ||||
"})\n", | ||||
"```\n", | ||||
"\n", | ||||
"could be read as\n", | ||||
"```python\n", | ||||
"import a.b as e\n", | ||||
"import c.d as f\n", | ||||
"e.something()\n", | ||||
"f.something()\n", | ||||
"```\n", | ||||
"\n", | ||||
"\n", | ||||
"See for example @damianavila [\"ZenMode\" plugin](https://github.com/ipython-contrib/IPython-notebook-extensions/blob/master/custom.example.js#L34) :\n", | ||||
"\n", | ||||
"```javascript\n", | ||||
"\n", | ||||
"// read that as\n", | ||||
"// import custom.zenmode.main as zenmode\n", | ||||
"require(['custom/zenmode/main'],function(zenmode){\n", | ||||
" zenmode.background('images/back12.jpg');\n", | ||||
"})\n", | ||||
"```\n" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"#### For the quickest" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"Try to use [the following](https://github.com/ipython/ipython/blob/1.x/IPython/html/static/notebook/js/celltoolbar.js#L367) to bind a dropdown list to `cell.metadata.difficulty.select`. \n", | ||||
"\n", | ||||
Ben Rousch
|
r21628 | "It should be able to take the four following values :\n", | ||
Min RK
|
r18669 | "\n", | ||
" - `<None>`\n", | ||||
" - `Easy`\n", | ||||
" - `Medium`\n", | ||||
" - `Hard`\n", | ||||
" \n", | ||||
Ben Rousch
|
r21628 | "We will use it to customize the output of the converted notebook depending on the tag on each cell" | ||
Min RK
|
r18669 | ] | ||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
"execution_count": null, | ||||
"metadata": { | ||||
"collapsed": false | ||||
}, | ||||
"outputs": [], | ||||
"source": [ | ||||
"%load soln/celldiff.js" | ||||
] | ||||
Brian E. Granger
|
r17485 | } | ||
Min RK
|
r18669 | ], | ||
Min RK
|
r20278 | "metadata": { | ||
"kernelspec": { | ||||
"display_name": "Python 3", | ||||
"language": "python", | ||||
"name": "python3" | ||||
}, | ||||
"language_info": { | ||||
"codemirror_mode": { | ||||
"name": "ipython", | ||||
"version": 3 | ||||
}, | ||||
"file_extension": ".py", | ||||
"mimetype": "text/x-python", | ||||
"name": "python", | ||||
"nbconvert_exporter": "python", | ||||
"pygments_lexer": "ipython3", | ||||
Jonathan Frederic
|
r20536 | "version": "3.4.3" | ||
Min RK
|
r20278 | } | ||
}, | ||||
Min RK
|
r18669 | "nbformat": 4, | ||
"nbformat_minor": 0 | ||||
Min RK
|
r20278 | } | ||