##// END OF EJS Templates
Merge pull request #6227 from Carreau/cellt-g-event...
Min RK -
r17463:11a830f4 merge
parent child Browse files
Show More
@@ -1,417 +1,416
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 'base/js/events'
8 ], function(IPython, $, events) {
8 "use strict";
9 "use strict";
9
10
10 var CellToolbar = function (options) {
11 var CellToolbar = function (options) {
11 // Constructor
12 // Constructor
12 //
13 //
13 // Parameters:
14 // Parameters:
14 // options: dictionary
15 // options: dictionary
15 // Dictionary of keyword arguments.
16 // Dictionary of keyword arguments.
16 // events: $(Events) instance
17 // events: $(Events) instance
17 // cell: Cell instance
18 // cell: Cell instance
18 // notebook: Notebook instance
19 // notebook: Notebook instance
19 CellToolbar._instances.push(this);
20 CellToolbar._instances.push(this);
20 this.notebook = options.notebook;
21 this.notebook = options.notebook;
21 this.events = options.events;
22 this.cell = options.cell;
22 this.cell = options.cell;
23 this.create_element();
23 this.create_element();
24 this.rebuild();
24 this.rebuild();
25 return this;
25 return this;
26 };
26 };
27
27
28
28
29 CellToolbar.prototype.create_element = function () {
29 CellToolbar.prototype.create_element = function () {
30 this.inner_element = $('<div/>').addClass('celltoolbar');
30 this.inner_element = $('<div/>').addClass('celltoolbar');
31 this.element = $('<div/>').addClass('ctb_hideshow')
31 this.element = $('<div/>').addClass('ctb_hideshow')
32 .append(this.inner_element);
32 .append(this.inner_element);
33 };
33 };
34
34
35
35
36 // The default css style for the outer celltoolbar div
36 // The default css style for the outer celltoolbar div
37 // (ctb_hideshow) is display: none.
37 // (ctb_hideshow) is display: none.
38 // To show the cell toolbar, *both* of the following conditions must be met:
38 // To show the cell toolbar, *both* of the following conditions must be met:
39 // - A parent container has class `ctb_global_show`
39 // - A parent container has class `ctb_global_show`
40 // - The celltoolbar has the class `ctb_show`
40 // - The celltoolbar has the class `ctb_show`
41 // This allows global show/hide, as well as per-cell show/hide.
41 // This allows global show/hide, as well as per-cell show/hide.
42
42
43 CellToolbar.global_hide = function () {
43 CellToolbar.global_hide = function () {
44 $('body').removeClass('ctb_global_show');
44 $('body').removeClass('ctb_global_show');
45 };
45 };
46
46
47
47
48 CellToolbar.global_show = function () {
48 CellToolbar.global_show = function () {
49 $('body').addClass('ctb_global_show');
49 $('body').addClass('ctb_global_show');
50 };
50 };
51
51
52
52
53 CellToolbar.prototype.hide = function () {
53 CellToolbar.prototype.hide = function () {
54 this.element.removeClass('ctb_show');
54 this.element.removeClass('ctb_show');
55 };
55 };
56
56
57
57
58 CellToolbar.prototype.show = function () {
58 CellToolbar.prototype.show = function () {
59 this.element.addClass('ctb_show');
59 this.element.addClass('ctb_show');
60 };
60 };
61
61
62
62
63 /**
63 /**
64 * Class variable that should contain a dict of all available callback
64 * Class variable that should contain a dict of all available callback
65 * we need to think of wether or not we allow nested namespace
65 * we need to think of wether or not we allow nested namespace
66 * @property _callback_dict
66 * @property _callback_dict
67 * @private
67 * @private
68 * @static
68 * @static
69 * @type Dict
69 * @type Dict
70 */
70 */
71 CellToolbar._callback_dict = {};
71 CellToolbar._callback_dict = {};
72
72
73
73
74 /**
74 /**
75 * Class variable that should contain the reverse order list of the button
75 * Class variable that should contain the reverse order list of the button
76 * to add to the toolbar of each cell
76 * to add to the toolbar of each cell
77 * @property _ui_controls_list
77 * @property _ui_controls_list
78 * @private
78 * @private
79 * @static
79 * @static
80 * @type List
80 * @type List
81 */
81 */
82 CellToolbar._ui_controls_list = [];
82 CellToolbar._ui_controls_list = [];
83
83
84
84
85 /**
85 /**
86 * Class variable that should contain the CellToolbar instances for each
86 * Class variable that should contain the CellToolbar instances for each
87 * cell of the notebook
87 * cell of the notebook
88 *
88 *
89 * @private
89 * @private
90 * @property _instances
90 * @property _instances
91 * @static
91 * @static
92 * @type List
92 * @type List
93 */
93 */
94 CellToolbar._instances = [];
94 CellToolbar._instances = [];
95
95
96
96
97 /**
97 /**
98 * keep a list of all the available presets for the toolbar
98 * keep a list of all the available presets for the toolbar
99 * @private
99 * @private
100 * @property _presets
100 * @property _presets
101 * @static
101 * @static
102 * @type Dict
102 * @type Dict
103 */
103 */
104 CellToolbar._presets = {};
104 CellToolbar._presets = {};
105
105
106
106
107 // this is by design not a prototype.
107 // this is by design not a prototype.
108 /**
108 /**
109 * Register a callback to create an UI element in a cell toolbar.
109 * Register a callback to create an UI element in a cell toolbar.
110 * @method register_callback
110 * @method register_callback
111 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
111 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
112 * for easier sorting and avoid collision
112 * for easier sorting and avoid collision
113 * @param callback {function(div, cell)} callback that will be called to generate the ui element
113 * @param callback {function(div, cell)} callback that will be called to generate the ui element
114 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
114 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
115 * will be added only to cells of types in the list.
115 * will be added only to cells of types in the list.
116 *
116 *
117 *
117 *
118 * The callback will receive the following element :
118 * The callback will receive the following element :
119 *
119 *
120 * * a div in which to add element.
120 * * a div in which to add element.
121 * * the cell it is responsible from
121 * * the cell it is responsible from
122 *
122 *
123 * @example
123 * @example
124 *
124 *
125 * Example that create callback for a button that toggle between `true` and `false` label,
125 * Example that create callback for a button that toggle between `true` and `false` label,
126 * with the metadata under the key 'foo' to reflect the status of the button.
126 * with the metadata under the key 'foo' to reflect the status of the button.
127 *
127 *
128 * // first param reference to a DOM div
128 * // first param reference to a DOM div
129 * // second param reference to the cell.
129 * // second param reference to the cell.
130 * var toggle = function(div, cell) {
130 * var toggle = function(div, cell) {
131 * var button_container = $(div)
131 * var button_container = $(div)
132 *
132 *
133 * // let's create a button that show the current value of the metadata
133 * // let's create a button that show the current value of the metadata
134 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
134 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
135 *
135 *
136 * // On click, change the metadata value and update the button label
136 * // On click, change the metadata value and update the button label
137 * button.click(function(){
137 * button.click(function(){
138 * var v = cell.metadata.foo;
138 * var v = cell.metadata.foo;
139 * cell.metadata.foo = !v;
139 * cell.metadata.foo = !v;
140 * button.button("option", "label", String(!v));
140 * button.button("option", "label", String(!v));
141 * })
141 * })
142 *
142 *
143 * // add the button to the DOM div.
143 * // add the button to the DOM div.
144 * button_container.append(button);
144 * button_container.append(button);
145 * }
145 * }
146 *
146 *
147 * // now we register the callback under the name `foo` to give the
147 * // now we register the callback under the name `foo` to give the
148 * // user the ability to use it later
148 * // user the ability to use it later
149 * CellToolbar.register_callback('foo', toggle);
149 * CellToolbar.register_callback('foo', toggle);
150 */
150 */
151 CellToolbar.register_callback = function(name, callback, cell_types) {
151 CellToolbar.register_callback = function(name, callback, cell_types) {
152 // Overwrite if it already exists.
152 // Overwrite if it already exists.
153 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
153 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
154 };
154 };
155
155
156
156
157 /**
157 /**
158 * Register a preset of UI element in a cell toolbar.
158 * Register a preset of UI element in a cell toolbar.
159 * Not supported Yet.
159 * Not supported Yet.
160 * @method register_preset
160 * @method register_preset
161 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
161 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
162 * for easier sorting and avoid collision
162 * for easier sorting and avoid collision
163 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
163 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
164 * should correspond to a name of a registerd callback.
164 * should correspond to a name of a registerd callback.
165 *
165 *
166 * @private
166 * @private
167 * @example
167 * @example
168 *
168 *
169 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
169 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
170 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
170 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
171 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
171 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
172 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
172 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
173 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
173 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
174 *
174 *
175 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
175 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
176 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
176 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
177 */
177 */
178 CellToolbar.register_preset = function(name, preset_list, notebook, events) {
178 CellToolbar.register_preset = function(name, preset_list, notebook) {
179 CellToolbar._presets[name] = preset_list;
179 CellToolbar._presets[name] = preset_list;
180 events.trigger('preset_added.CellToolbar', {name: name});
180 events.trigger('preset_added.CellToolbar', {name: name});
181 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
181 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
182 // In that case, activate the preset if needed.
182 // In that case, activate the preset if needed.
183 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name)
183 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){
184 CellToolbar.activate_preset(name, events);
184 CellToolbar.activate_preset(name);
185 }
185 };
186 };
186
187
187
188
188 /**
189 /**
189 * List the names of the presets that are currently registered.
190 * List the names of the presets that are currently registered.
190 *
191 *
191 * @method list_presets
192 * @method list_presets
192 * @static
193 * @static
193 */
194 */
194 CellToolbar.list_presets = function() {
195 CellToolbar.list_presets = function() {
195 var keys = [];
196 var keys = [];
196 for (var k in CellToolbar._presets) {
197 for (var k in CellToolbar._presets) {
197 keys.push(k);
198 keys.push(k);
198 }
199 }
199 return keys;
200 return keys;
200 };
201 };
201
202
202
203
203 /**
204 /**
204 * Activate an UI preset from `register_preset`
205 * Activate an UI preset from `register_preset`
205 *
206 *
206 * This does not update the selection UI.
207 * This does not update the selection UI.
207 *
208 *
208 * @method activate_preset
209 * @method activate_preset
209 * @param preset_name {String} string corresponding to the preset name
210 * @param preset_name {String} string corresponding to the preset name
210 *
211 *
211 * @static
212 * @static
212 * @private
213 * @private
213 * @example
214 * @example
214 *
215 *
215 * CellToolbar.activate_preset('foo.foo_preset1');
216 * CellToolbar.activate_preset('foo.foo_preset1');
216 */
217 */
217 CellToolbar.activate_preset = function(preset_name, events){
218 CellToolbar.activate_preset = function(preset_name){
218 var preset = CellToolbar._presets[preset_name];
219 var preset = CellToolbar._presets[preset_name];
219
220
220 if(preset !== undefined){
221 if(preset !== undefined){
221 CellToolbar._ui_controls_list = preset;
222 CellToolbar._ui_controls_list = preset;
222 CellToolbar.rebuild_all();
223 CellToolbar.rebuild_all();
223 }
224 }
224
225
225 if (events) {
226 events.trigger('preset_activated.CellToolbar', {name: preset_name});
226 events.trigger('preset_activated.CellToolbar', {name: preset_name});
227 }
228 };
227 };
229
228
230
229
231 /**
230 /**
232 * This should be called on the class and not on a instance as it will trigger
231 * This should be called on the class and not on a instance as it will trigger
233 * rebuild of all the instances.
232 * rebuild of all the instances.
234 * @method rebuild_all
233 * @method rebuild_all
235 * @static
234 * @static
236 *
235 *
237 */
236 */
238 CellToolbar.rebuild_all = function(){
237 CellToolbar.rebuild_all = function(){
239 for(var i=0; i < CellToolbar._instances.length; i++){
238 for(var i=0; i < CellToolbar._instances.length; i++){
240 CellToolbar._instances[i].rebuild();
239 CellToolbar._instances[i].rebuild();
241 }
240 }
242 };
241 };
243
242
244 /**
243 /**
245 * Rebuild all the button on the toolbar to update its state.
244 * Rebuild all the button on the toolbar to update its state.
246 * @method rebuild
245 * @method rebuild
247 */
246 */
248 CellToolbar.prototype.rebuild = function(){
247 CellToolbar.prototype.rebuild = function(){
249 // strip evrything from the div
248 // strip evrything from the div
250 // which is probably inner_element
249 // which is probably inner_element
251 // or this.element.
250 // or this.element.
252 this.inner_element.empty();
251 this.inner_element.empty();
253 this.ui_controls_list = [];
252 this.ui_controls_list = [];
254
253
255 var callbacks = CellToolbar._callback_dict;
254 var callbacks = CellToolbar._callback_dict;
256 var preset = CellToolbar._ui_controls_list;
255 var preset = CellToolbar._ui_controls_list;
257 // Yes we iterate on the class variable, not the instance one.
256 // Yes we iterate on the class variable, not the instance one.
258 for (var i=0; i < preset.length; i++) {
257 for (var i=0; i < preset.length; i++) {
259 var key = preset[i];
258 var key = preset[i];
260 var callback = callbacks[key];
259 var callback = callbacks[key];
261 if (!callback) continue;
260 if (!callback) continue;
262
261
263 if (typeof callback === 'object') {
262 if (typeof callback === 'object') {
264 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
263 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
265 callback = callback.callback;
264 callback = callback.callback;
266 }
265 }
267
266
268 var local_div = $('<div/>').addClass('button_container');
267 var local_div = $('<div/>').addClass('button_container');
269 try {
268 try {
270 callback(local_div, this.cell, this);
269 callback(local_div, this.cell, this);
271 this.ui_controls_list.push(key);
270 this.ui_controls_list.push(key);
272 } catch (e) {
271 } catch (e) {
273 console.log("Error in cell toolbar callback " + key, e);
272 console.log("Error in cell toolbar callback " + key, e);
274 continue;
273 continue;
275 }
274 }
276 // only append if callback succeeded.
275 // only append if callback succeeded.
277 this.inner_element.append(local_div);
276 this.inner_element.append(local_div);
278 }
277 }
279
278
280 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
279 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
281 if (!this.ui_controls_list.length || (this.cell.cell_type != 'code' && this.cell.rendered)) {
280 if (!this.ui_controls_list.length || (this.cell.cell_type != 'code' && this.cell.rendered)) {
282 this.hide();
281 this.hide();
283 } else {
282 } else {
284 this.show();
283 this.show();
285 }
284 }
286 };
285 };
287
286
288
287
289 /**
288 /**
290 */
289 */
291 CellToolbar.utils = {};
290 CellToolbar.utils = {};
292
291
293
292
294 /**
293 /**
295 * A utility function to generate bindings between a checkbox and cell/metadata
294 * A utility function to generate bindings between a checkbox and cell/metadata
296 * @method utils.checkbox_ui_generator
295 * @method utils.checkbox_ui_generator
297 * @static
296 * @static
298 *
297 *
299 * @param name {string} Label in front of the checkbox
298 * @param name {string} Label in front of the checkbox
300 * @param setter {function( cell, newValue )}
299 * @param setter {function( cell, newValue )}
301 * A setter method to set the newValue
300 * A setter method to set the newValue
302 * @param getter {function( cell )}
301 * @param getter {function( cell )}
303 * A getter methods which return the current value.
302 * A getter methods which return the current value.
304 *
303 *
305 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
304 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
306 *
305 *
307 * @example
306 * @example
308 *
307 *
309 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
308 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
310 *
309 *
311 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
310 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
312 * // setter
311 * // setter
313 * function(cell, value){
312 * function(cell, value){
314 * // we check that the slideshow namespace exist and create it if needed
313 * // we check that the slideshow namespace exist and create it if needed
315 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
314 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
316 * // set the value
315 * // set the value
317 * cell.metadata.slideshow.isSectionStart = value
316 * cell.metadata.slideshow.isSectionStart = value
318 * },
317 * },
319 * //geter
318 * //geter
320 * function(cell){ var ns = cell.metadata.slideshow;
319 * function(cell){ var ns = cell.metadata.slideshow;
321 * // if the slideshow namespace does not exist return `undefined`
320 * // if the slideshow namespace does not exist return `undefined`
322 * // (will be interpreted as `false` by checkbox) otherwise
321 * // (will be interpreted as `false` by checkbox) otherwise
323 * // return the value
322 * // return the value
324 * return (ns == undefined)? undefined: ns.isSectionStart
323 * return (ns == undefined)? undefined: ns.isSectionStart
325 * }
324 * }
326 * );
325 * );
327 *
326 *
328 * CellToolbar.register_callback('newSlide', newSlide);
327 * CellToolbar.register_callback('newSlide', newSlide);
329 *
328 *
330 */
329 */
331 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
330 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
332 return function(div, cell, celltoolbar) {
331 return function(div, cell, celltoolbar) {
333 var button_container = $(div);
332 var button_container = $(div);
334
333
335 var chkb = $('<input/>').attr('type', 'checkbox');
334 var chkb = $('<input/>').attr('type', 'checkbox');
336 var lbl = $('<label/>').append($('<span/>').text(name));
335 var lbl = $('<label/>').append($('<span/>').text(name));
337 lbl.append(chkb);
336 lbl.append(chkb);
338 chkb.attr("checked", getter(cell));
337 chkb.attr("checked", getter(cell));
339
338
340 chkb.click(function(){
339 chkb.click(function(){
341 var v = getter(cell);
340 var v = getter(cell);
342 setter(cell, !v);
341 setter(cell, !v);
343 chkb.attr("checked", !v);
342 chkb.attr("checked", !v);
344 });
343 });
345 button_container.append($('<span/>').append(lbl));
344 button_container.append($('<span/>').append(lbl));
346 };
345 };
347 };
346 };
348
347
349
348
350 /**
349 /**
351 * A utility function to generate bindings between a dropdown list cell
350 * A utility function to generate bindings between a dropdown list cell
352 * @method utils.select_ui_generator
351 * @method utils.select_ui_generator
353 * @static
352 * @static
354 *
353 *
355 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
354 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
356 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
355 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
357 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
356 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
358 * should not be "undefined" or behavior can be unexpected.
357 * should not be "undefined" or behavior can be unexpected.
359 * @param setter {function( cell, newValue )}
358 * @param setter {function( cell, newValue )}
360 * A setter method to set the newValue
359 * A setter method to set the newValue
361 * @param getter {function( cell )}
360 * @param getter {function( cell )}
362 * A getter methods which return the current value of the metadata.
361 * A getter methods which return the current value of the metadata.
363 * @param [label=""] {String} optionnal label for the dropdown menu
362 * @param [label=""] {String} optionnal label for the dropdown menu
364 *
363 *
365 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
364 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
366 *
365 *
367 * @example
366 * @example
368 *
367 *
369 * var select_type = CellToolbar.utils.select_ui_generator([
368 * var select_type = CellToolbar.utils.select_ui_generator([
370 * ["<None>" , "None" ],
369 * ["<None>" , "None" ],
371 * ["Header Slide" , "header_slide" ],
370 * ["Header Slide" , "header_slide" ],
372 * ["Slide" , "slide" ],
371 * ["Slide" , "slide" ],
373 * ["Fragment" , "fragment" ],
372 * ["Fragment" , "fragment" ],
374 * ["Skip" , "skip" ],
373 * ["Skip" , "skip" ],
375 * ],
374 * ],
376 * // setter
375 * // setter
377 * function(cell, value){
376 * function(cell, value){
378 * // we check that the slideshow namespace exist and create it if needed
377 * // we check that the slideshow namespace exist and create it if needed
379 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
378 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
380 * // set the value
379 * // set the value
381 * cell.metadata.slideshow.slide_type = value
380 * cell.metadata.slideshow.slide_type = value
382 * },
381 * },
383 * //geter
382 * //geter
384 * function(cell){ var ns = cell.metadata.slideshow;
383 * function(cell){ var ns = cell.metadata.slideshow;
385 * // if the slideshow namespace does not exist return `undefined`
384 * // if the slideshow namespace does not exist return `undefined`
386 * // (will be interpreted as `false` by checkbox) otherwise
385 * // (will be interpreted as `false` by checkbox) otherwise
387 * // return the value
386 * // return the value
388 * return (ns == undefined)? undefined: ns.slide_type
387 * return (ns == undefined)? undefined: ns.slide_type
389 * }
388 * }
390 * CellToolbar.register_callback('slideshow.select', select_type);
389 * CellToolbar.register_callback('slideshow.select', select_type);
391 *
390 *
392 */
391 */
393 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
392 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
394 label = label || "";
393 label = label || "";
395 return function(div, cell, celltoolbar) {
394 return function(div, cell, celltoolbar) {
396 var button_container = $(div);
395 var button_container = $(div);
397 var lbl = $("<label/>").append($('<span/>').text(label));
396 var lbl = $("<label/>").append($('<span/>').text(label));
398 var select = $('<select/>').addClass('ui-widget ui-widget-content');
397 var select = $('<select/>').addClass('ui-widget ui-widget-content');
399 for(var i=0; i < list_list.length; i++){
398 for(var i=0; i < list_list.length; i++){
400 var opt = $('<option/>')
399 var opt = $('<option/>')
401 .attr('value', list_list[i][1])
400 .attr('value', list_list[i][1])
402 .text(list_list[i][0]);
401 .text(list_list[i][0]);
403 select.append(opt);
402 select.append(opt);
404 }
403 }
405 select.val(getter(cell));
404 select.val(getter(cell));
406 select.change(function(){
405 select.change(function(){
407 setter(cell, select.val());
406 setter(cell, select.val());
408 });
407 });
409 button_container.append($('<span/>').append(lbl).append(select));
408 button_container.append($('<span/>').append(lbl).append(select));
410 };
409 };
411 };
410 };
412
411
413 // Backwards compatability.
412 // Backwards compatability.
414 IPython.CellToolbar = CellToolbar;
413 IPython.CellToolbar = CellToolbar;
415
414
416 return {'CellToolbar': CellToolbar};
415 return {'CellToolbar': CellToolbar};
417 });
416 });
@@ -1,51 +1,51
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'notebook/js/celltoolbar',
6 'notebook/js/celltoolbar',
7 'base/js/dialog',
7 'base/js/dialog',
8 ], function($, celltoolbar, dialog) {
8 ], function($, celltoolbar, dialog) {
9 "use strict";
9 "use strict";
10
10
11 var CellToolbar = celltoolbar.CellToolbar;
11 var CellToolbar = celltoolbar.CellToolbar;
12
12
13 var raw_edit = function (cell) {
13 var raw_edit = function (cell) {
14 dialog.edit_metadata({
14 dialog.edit_metadata({
15 md: cell.metadata,
15 md: cell.metadata,
16 callback: function (md) {
16 callback: function (md) {
17 cell.metadata = md;
17 cell.metadata = md;
18 },
18 },
19 name: 'Cell',
19 name: 'Cell',
20 notebook: this.notebook,
20 notebook: this.notebook,
21 keyboard_manager: this.keyboard_manager
21 keyboard_manager: this.keyboard_manager
22 });
22 });
23 };
23 };
24
24
25 var add_raw_edit_button = function(div, cell) {
25 var add_raw_edit_button = function(div, cell) {
26 var button_container = $(div);
26 var button_container = $(div);
27 var button = $('<button/>')
27 var button = $('<button/>')
28 .addClass("btn btn-default btn-xs")
28 .addClass("btn btn-default btn-xs")
29 .text("Edit Metadata")
29 .text("Edit Metadata")
30 .click( function () {
30 .click( function () {
31 raw_edit(cell);
31 raw_edit(cell);
32 return false;
32 return false;
33 });
33 });
34 button_container.append(button);
34 button_container.append(button);
35 };
35 };
36
36
37 var register = function (notebook, events) {
37 var register = function (notebook) {
38 CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
38 CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
39 raw_edit = $.proxy(raw_edit, {
39 raw_edit = $.proxy(raw_edit, {
40 notebook: notebook,
40 notebook: notebook,
41 keyboard_manager: notebook.keyboard_manager
41 keyboard_manager: notebook.keyboard_manager
42 });
42 });
43
43
44 var example_preset = [];
44 var example_preset = [];
45 example_preset.push('default.rawedit');
45 example_preset.push('default.rawedit');
46
46
47 CellToolbar.register_preset('Edit Metadata', example_preset, notebook, events);
47 CellToolbar.register_preset('Edit Metadata', example_preset, notebook);
48 console.log('Default extension for cell metadata editing loaded.');
48 console.log('Default extension for cell metadata editing loaded.');
49 };
49 };
50 return {'register': register};
50 return {'register': register};
51 });
51 });
@@ -1,148 +1,148
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 // Example Use for the CellToolbar library
4 // Example Use for the CellToolbar library
5 // add the following to your custom.js to load
5 // add the following to your custom.js to load
6 // Celltoolbar UI for slideshow
6 // Celltoolbar UI for slideshow
7
7
8 // ```
8 // ```
9 // $.getScript('/static/js/celltoolbarpresets/example.js');
9 // $.getScript('/static/js/celltoolbarpresets/example.js');
10 // ```
10 // ```
11 define([
11 define([
12 'jquery',
12 'jquery',
13 'notebook/js/celltoolbar',
13 'notebook/js/celltoolbar',
14 ], function($, celltoolbar) {
14 ], function($, celltoolbar) {
15 "use strict";
15 "use strict";
16
16
17 var CellToolbar = celltoolbar.CellToolbar;
17 var CellToolbar = celltoolbar.CellToolbar;
18
18
19 var example_preset = [];
19 var example_preset = [];
20
20
21 var simple_button = function(div, cell) {
21 var simple_button = function(div, cell) {
22 var button_container = $(div);
22 var button_container = $(div);
23 var button = $('<div/>').button({icons:{primary:'ui-icon-locked'}});
23 var button = $('<div/>').button({icons:{primary:'ui-icon-locked'}});
24 var fun = function(value){
24 var fun = function(value){
25 try{
25 try{
26 if(value){
26 if(value){
27 cell.code_mirror.setOption('readOnly','nocursor');
27 cell.code_mirror.setOption('readOnly','nocursor');
28 button.button('option','icons',{primary:'ui-icon-locked'});
28 button.button('option','icons',{primary:'ui-icon-locked'});
29 } else {
29 } else {
30 cell.code_mirror.setOption('readOnly',false);
30 cell.code_mirror.setOption('readOnly',false);
31 button.button('option','icons',{primary:'ui-icon-unlocked'});
31 button.button('option','icons',{primary:'ui-icon-unlocked'});
32 }
32 }
33 } catch(e){}
33 } catch(e){}
34
34
35 };
35 };
36 fun(cell.metadata.ro);
36 fun(cell.metadata.ro);
37 button.click(function(){
37 button.click(function(){
38 var v = cell.metadata.ro;
38 var v = cell.metadata.ro;
39 var locked = !v;
39 var locked = !v;
40 cell.metadata.ro = locked;
40 cell.metadata.ro = locked;
41 fun(locked);
41 fun(locked);
42 })
42 })
43 .css('height','16px')
43 .css('height','16px')
44 .css('width','35px');
44 .css('width','35px');
45 button_container.append(button);
45 button_container.append(button);
46 };
46 };
47
47
48 CellToolbar.register_callback('example.lock',simple_button);
48 CellToolbar.register_callback('example.lock',simple_button);
49 example_preset.push('example.lock');
49 example_preset.push('example.lock');
50
50
51 var toggle_test = function(div, cell) {
51 var toggle_test = function(div, cell) {
52 var button_container = $(div);
52 var button_container = $(div);
53 var button = $('<div/>')
53 var button = $('<div/>')
54 .button({label:String(cell.metadata.foo)}).
54 .button({label:String(cell.metadata.foo)}).
55 css('width','65px');
55 css('width','65px');
56 button.click(function(){
56 button.click(function(){
57 var v = cell.metadata.foo;
57 var v = cell.metadata.foo;
58 cell.metadata.foo = !v;
58 cell.metadata.foo = !v;
59 button.button("option","label",String(!v));
59 button.button("option","label",String(!v));
60 });
60 });
61 button_container.append(button);
61 button_container.append(button);
62 };
62 };
63
63
64 CellToolbar.register_callback('example.toggle',toggle_test);
64 CellToolbar.register_callback('example.toggle',toggle_test);
65 example_preset.push('example.toggle');
65 example_preset.push('example.toggle');
66
66
67 var checkbox_test = CellToolbar.utils.checkbox_ui_generator('Yes/No',
67 var checkbox_test = CellToolbar.utils.checkbox_ui_generator('Yes/No',
68 // setter
68 // setter
69 function(cell, value){
69 function(cell, value){
70 // we check that the slideshow namespace exist and create it if needed
70 // we check that the slideshow namespace exist and create it if needed
71 if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};}
71 if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};}
72 // set the value
72 // set the value
73 cell.metadata.yn_test.value = value;
73 cell.metadata.yn_test.value = value;
74 },
74 },
75 //geter
75 //geter
76 function(cell){ var ns = cell.metadata.yn_test;
76 function(cell){ var ns = cell.metadata.yn_test;
77 // if the slideshow namespace does not exist return `undefined`
77 // if the slideshow namespace does not exist return `undefined`
78 // (will be interpreted as `false` by checkbox) otherwise
78 // (will be interpreted as `false` by checkbox) otherwise
79 // return the value
79 // return the value
80 return (ns === undefined)? undefined: ns.value;
80 return (ns === undefined)? undefined: ns.value;
81 }
81 }
82 );
82 );
83
83
84
84
85 CellToolbar.register_callback('example.checkbox',checkbox_test);
85 CellToolbar.register_callback('example.checkbox',checkbox_test);
86 example_preset.push('example.checkbox');
86 example_preset.push('example.checkbox');
87
87
88 var select_test = CellToolbar.utils.select_ui_generator([
88 var select_test = CellToolbar.utils.select_ui_generator([
89 ["-" ,undefined ],
89 ["-" ,undefined ],
90 ["Header Slide" ,"header_slide" ],
90 ["Header Slide" ,"header_slide" ],
91 ["Slide" ,"slide" ],
91 ["Slide" ,"slide" ],
92 ["Fragment" ,"fragment" ],
92 ["Fragment" ,"fragment" ],
93 ["Skip" ,"skip" ],
93 ["Skip" ,"skip" ],
94 ],
94 ],
95 // setter
95 // setter
96 function(cell,value){
96 function(cell,value){
97 // we check that the slideshow namespace exist and create it if needed
97 // we check that the slideshow namespace exist and create it if needed
98 if (cell.metadata.test === undefined){cell.metadata.test = {};}
98 if (cell.metadata.test === undefined){cell.metadata.test = {};}
99 // set the value
99 // set the value
100 cell.metadata.test.slide_type = value;
100 cell.metadata.test.slide_type = value;
101 },
101 },
102 //geter
102 //geter
103 function(cell){ var ns = cell.metadata.test;
103 function(cell){ var ns = cell.metadata.test;
104 // if the slideshow namespace does not exist return `undefined`
104 // if the slideshow namespace does not exist return `undefined`
105 // (will be interpreted as `false` by checkbox) otherwise
105 // (will be interpreted as `false` by checkbox) otherwise
106 // return the value
106 // return the value
107 return (ns === undefined)? undefined: ns.slide_type;
107 return (ns === undefined)? undefined: ns.slide_type;
108 });
108 });
109
109
110 CellToolbar.register_callback('example.select',select_test);
110 CellToolbar.register_callback('example.select',select_test);
111 example_preset.push('example.select');
111 example_preset.push('example.select');
112
112
113 var simple_dialog = function(title,text){
113 var simple_dialog = function(title,text){
114 var dlg = $('<div/>').attr('title',title)
114 var dlg = $('<div/>').attr('title',title)
115 .append($('<p/>').text(text));
115 .append($('<p/>').text(text));
116 $(dlg).dialog({
116 $(dlg).dialog({
117 autoOpen: true,
117 autoOpen: true,
118 height: 300,
118 height: 300,
119 width: 650,
119 width: 650,
120 modal: true,
120 modal: true,
121 close: function() {
121 close: function() {
122 //cleanup on close
122 //cleanup on close
123 $(this).remove();
123 $(this).remove();
124 }
124 }
125 });
125 });
126 };
126 };
127
127
128 var add_simple_dialog_button = function(div, cell) {
128 var add_simple_dialog_button = function(div, cell) {
129 var help_text = ["This is the Metadata editting UI.",
129 var help_text = ["This is the Metadata editting UI.",
130 "It heavily rely on plugin to work ",
130 "It heavily rely on plugin to work ",
131 "and is still under developpement. You shouldn't wait too long before",
131 "and is still under developpement. You shouldn't wait too long before",
132 " seeing some customisable buttons in those toolbar."
132 " seeing some customisable buttons in those toolbar."
133 ].join('\n');
133 ].join('\n');
134 var button_container = $(div);
134 var button_container = $(div);
135 var button = $('<div/>').button({label:'?'})
135 var button = $('<div/>').button({label:'?'})
136 .click(function(){simple_dialog('help',help_text); return false;});
136 .click(function(){simple_dialog('help',help_text); return false;});
137 button_container.append(button);
137 button_container.append(button);
138 };
138 };
139
139
140 var register = function (notebook, events) {
140 var register = function (notebook) {
141 CellToolbar.register_callback('example.help',add_simple_dialog_button);
141 CellToolbar.register_callback('example.help',add_simple_dialog_button);
142 example_preset.push('example.help');
142 example_preset.push('example.help');
143
143
144 CellToolbar.register_preset('Example',example_preset, notebook, events);
144 CellToolbar.register_preset('Example',example_preset, notebook);
145 console.log('Example extension for metadata editing loaded.');
145 console.log('Example extension for metadata editing loaded.');
146 };
146 };
147 return {'register': register};
147 return {'register': register};
148 });
148 });
@@ -1,86 +1,86
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'notebook/js/celltoolbar',
6 'notebook/js/celltoolbar',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/keyboard',
8 'base/js/keyboard',
9 ], function($, celltoolbar, dialog, keyboard) {
9 ], function($, celltoolbar, dialog, keyboard) {
10 "use strict";
10 "use strict";
11
11
12 var CellToolbar = celltoolbar.CellToolbar;
12 var CellToolbar = celltoolbar.CellToolbar;
13 var raw_cell_preset = [];
13 var raw_cell_preset = [];
14
14
15 var select_type = CellToolbar.utils.select_ui_generator([
15 var select_type = CellToolbar.utils.select_ui_generator([
16 ["None", "-"],
16 ["None", "-"],
17 ["LaTeX", "text/latex"],
17 ["LaTeX", "text/latex"],
18 ["reST", "text/restructuredtext"],
18 ["reST", "text/restructuredtext"],
19 ["HTML", "text/html"],
19 ["HTML", "text/html"],
20 ["Markdown", "text/markdown"],
20 ["Markdown", "text/markdown"],
21 ["Python", "text/x-python"],
21 ["Python", "text/x-python"],
22 ["Custom", "dialog"],
22 ["Custom", "dialog"],
23
23
24 ],
24 ],
25 // setter
25 // setter
26 function(cell, value) {
26 function(cell, value) {
27 if (value === "-") {
27 if (value === "-") {
28 delete cell.metadata.raw_mimetype;
28 delete cell.metadata.raw_mimetype;
29 } else if (value === 'dialog'){
29 } else if (value === 'dialog'){
30 var dialog = $('<div/>').append(
30 var dialog = $('<div/>').append(
31 $("<p/>")
31 $("<p/>")
32 .text("Set the MIME type of the raw cell:")
32 .text("Set the MIME type of the raw cell:")
33 ).append(
33 ).append(
34 $("<br/>")
34 $("<br/>")
35 ).append(
35 ).append(
36 $('<input/>').attr('type','text').attr('size','25')
36 $('<input/>').attr('type','text').attr('size','25')
37 .val(cell.metadata.raw_mimetype || "-")
37 .val(cell.metadata.raw_mimetype || "-")
38 );
38 );
39 dialog.modal({
39 dialog.modal({
40 title: "Raw Cell MIME Type",
40 title: "Raw Cell MIME Type",
41 body: dialog,
41 body: dialog,
42 buttons : {
42 buttons : {
43 "Cancel": {},
43 "Cancel": {},
44 "OK": {
44 "OK": {
45 class: "btn-primary",
45 class: "btn-primary",
46 click: function () {
46 click: function () {
47 console.log(cell);
47 console.log(cell);
48 cell.metadata.raw_mimetype = $(this).find('input').val();
48 cell.metadata.raw_mimetype = $(this).find('input').val();
49 console.log(cell.metadata);
49 console.log(cell.metadata);
50 }
50 }
51 }
51 }
52 },
52 },
53 open : function (event, ui) {
53 open : function (event, ui) {
54 var that = $(this);
54 var that = $(this);
55 // Upon ENTER, click the OK button.
55 // Upon ENTER, click the OK button.
56 that.find('input[type="text"]').keydown(function (event, ui) {
56 that.find('input[type="text"]').keydown(function (event, ui) {
57 if (event.which === keyboard.keycodes.enter) {
57 if (event.which === keyboard.keycodes.enter) {
58 that.find('.btn-primary').first().click();
58 that.find('.btn-primary').first().click();
59 return false;
59 return false;
60 }
60 }
61 });
61 });
62 that.find('input[type="text"]').focus().select();
62 that.find('input[type="text"]').focus().select();
63 }
63 }
64 });
64 });
65 } else {
65 } else {
66 cell.metadata.raw_mimetype = value;
66 cell.metadata.raw_mimetype = value;
67 }
67 }
68 },
68 },
69 //getter
69 //getter
70 function(cell) {
70 function(cell) {
71 return cell.metadata.raw_mimetype || "";
71 return cell.metadata.raw_mimetype || "";
72 },
72 },
73 // name
73 // name
74 "Raw NBConvert Format"
74 "Raw NBConvert Format"
75 );
75 );
76
76
77 var register = function (notebook, events) {
77 var register = function (notebook) {
78 CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
78 CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
79 raw_cell_preset.push('raw_cell.select');
79 raw_cell_preset.push('raw_cell.select');
80
80
81 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset, notebook, events);
81 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset, notebook);
82 console.log('Raw Cell Format toolbar preset loaded.');
82 console.log('Raw Cell Format toolbar preset loaded.');
83 };
83 };
84 return {'register': register};
84 return {'register': register};
85
85
86 });
86 });
@@ -1,46 +1,46
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'notebook/js/celltoolbar',
6 'notebook/js/celltoolbar',
7 ], function($, celltoolbar) {
7 ], function($, celltoolbar) {
8 "use strict";
8 "use strict";
9
9
10
10
11 var CellToolbar = celltoolbar.CellToolbar;
11 var CellToolbar = celltoolbar.CellToolbar;
12 var slideshow_preset = [];
12 var slideshow_preset = [];
13
13
14 var select_type = CellToolbar.utils.select_ui_generator([
14 var select_type = CellToolbar.utils.select_ui_generator([
15 ["-" ,"-" ],
15 ["-" ,"-" ],
16 ["Slide" ,"slide" ],
16 ["Slide" ,"slide" ],
17 ["Sub-Slide" ,"subslide" ],
17 ["Sub-Slide" ,"subslide" ],
18 ["Fragment" ,"fragment" ],
18 ["Fragment" ,"fragment" ],
19 ["Skip" ,"skip" ],
19 ["Skip" ,"skip" ],
20 ["Notes" ,"notes" ],
20 ["Notes" ,"notes" ],
21 ],
21 ],
22 // setter
22 // setter
23 function(cell, value){
23 function(cell, value){
24 // we check that the slideshow namespace exist and create it if needed
24 // we check that the slideshow namespace exist and create it if needed
25 if (cell.metadata.slideshow === undefined){cell.metadata.slideshow = {};}
25 if (cell.metadata.slideshow === undefined){cell.metadata.slideshow = {};}
26 // set the value
26 // set the value
27 cell.metadata.slideshow.slide_type = value;
27 cell.metadata.slideshow.slide_type = value;
28 },
28 },
29 //geter
29 //geter
30 function(cell){ var ns = cell.metadata.slideshow;
30 function(cell){ var ns = cell.metadata.slideshow;
31 // if the slideshow namespace does not exist return `undefined`
31 // if the slideshow namespace does not exist return `undefined`
32 // (will be interpreted as `false` by checkbox) otherwise
32 // (will be interpreted as `false` by checkbox) otherwise
33 // return the value
33 // return the value
34 return (ns === undefined)? undefined: ns.slide_type;
34 return (ns === undefined)? undefined: ns.slide_type;
35 },
35 },
36 "Slide Type");
36 "Slide Type");
37
37
38 var register = function (notebook, events) {
38 var register = function (notebook) {
39 CellToolbar.register_callback('slideshow.select',select_type);
39 CellToolbar.register_callback('slideshow.select',select_type);
40 slideshow_preset.push('slideshow.select');
40 slideshow_preset.push('slideshow.select');
41
41
42 CellToolbar.register_preset('Slideshow',slideshow_preset, notebook, events);
42 CellToolbar.register_preset('Slideshow',slideshow_preset, notebook);
43 console.log('Slideshow extension for metadata editing loaded.');
43 console.log('Slideshow extension for metadata editing loaded.');
44 };
44 };
45 return {'register': register};
45 return {'register': register};
46 });
46 });
@@ -1,526 +1,525
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/keyboard',
8 'base/js/keyboard',
9 'notebook/js/cell',
9 'notebook/js/cell',
10 'notebook/js/outputarea',
10 'notebook/js/outputarea',
11 'notebook/js/completer',
11 'notebook/js/completer',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
13 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
14 "use strict";
14 "use strict";
15 var Cell = cell.Cell;
15 var Cell = cell.Cell;
16
16
17 /* local util for codemirror */
17 /* local util for codemirror */
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
19
19
20 /**
20 /**
21 *
21 *
22 * function to delete until previous non blanking space character
22 * function to delete until previous non blanking space character
23 * or first multiple of 4 tabstop.
23 * or first multiple of 4 tabstop.
24 * @private
24 * @private
25 */
25 */
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
30 var tabsize = cm.getOption('tabSize');
30 var tabsize = cm.getOption('tabSize');
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
33 var select = cm.getRange(from,cur);
33 var select = cm.getRange(from,cur);
34 if( select.match(/^\ +$/) !== null){
34 if( select.match(/^\ +$/) !== null){
35 cm.replaceRange("",from,cur);
35 cm.replaceRange("",from,cur);
36 } else {
36 } else {
37 cm.deleteH(-1,"char");
37 cm.deleteH(-1,"char");
38 }
38 }
39 };
39 };
40
40
41 var keycodes = keyboard.keycodes;
41 var keycodes = keyboard.keycodes;
42
42
43 var CodeCell = function (kernel, options) {
43 var CodeCell = function (kernel, options) {
44 // Constructor
44 // Constructor
45 //
45 //
46 // A Cell conceived to write code.
46 // A Cell conceived to write code.
47 //
47 //
48 // Parameters:
48 // Parameters:
49 // kernel: Kernel instance
49 // kernel: Kernel instance
50 // The kernel doesn't have to be set at creation time, in that case
50 // The kernel doesn't have to be set at creation time, in that case
51 // it will be null and set_kernel has to be called later.
51 // it will be null and set_kernel has to be called later.
52 // options: dictionary
52 // options: dictionary
53 // Dictionary of keyword arguments.
53 // Dictionary of keyword arguments.
54 // events: $(Events) instance
54 // events: $(Events) instance
55 // config: dictionary
55 // config: dictionary
56 // keyboard_manager: KeyboardManager instance
56 // keyboard_manager: KeyboardManager instance
57 // notebook: Notebook instance
57 // notebook: Notebook instance
58 // tooltip: Tooltip instance
58 // tooltip: Tooltip instance
59 this.kernel = kernel || null;
59 this.kernel = kernel || null;
60 this.notebook = options.notebook;
60 this.notebook = options.notebook;
61 this.collapsed = false;
61 this.collapsed = false;
62 this.events = options.events;
62 this.events = options.events;
63 this.tooltip = options.tooltip;
63 this.tooltip = options.tooltip;
64 this.config = options.config;
64 this.config = options.config;
65
65
66 // create all attributed in constructor function
66 // create all attributed in constructor function
67 // even if null for V8 VM optimisation
67 // even if null for V8 VM optimisation
68 this.input_prompt_number = null;
68 this.input_prompt_number = null;
69 this.celltoolbar = null;
69 this.celltoolbar = null;
70 this.output_area = null;
70 this.output_area = null;
71 this.last_msg_id = null;
71 this.last_msg_id = null;
72 this.completer = null;
72 this.completer = null;
73
73
74
74
75 var cm_overwrite_options = {
75 var cm_overwrite_options = {
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 };
77 };
78
78
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
80 Cell.apply(this,[{
80 Cell.apply(this,[{
81 config: config,
81 config: config,
82 keyboard_manager: options.keyboard_manager,
82 keyboard_manager: options.keyboard_manager,
83 events: this.events}]);
83 events: this.events}]);
84
84
85 // Attributes we want to override in this subclass.
85 // Attributes we want to override in this subclass.
86 this.cell_type = "code";
86 this.cell_type = "code";
87
87
88 var that = this;
88 var that = this;
89 this.element.focusout(
89 this.element.focusout(
90 function() { that.auto_highlight(); }
90 function() { that.auto_highlight(); }
91 );
91 );
92 };
92 };
93
93
94 CodeCell.options_default = {
94 CodeCell.options_default = {
95 cm_config : {
95 cm_config : {
96 extraKeys: {
96 extraKeys: {
97 "Tab" : "indentMore",
97 "Tab" : "indentMore",
98 "Shift-Tab" : "indentLess",
98 "Shift-Tab" : "indentLess",
99 "Backspace" : "delSpaceToPrevTabStop",
99 "Backspace" : "delSpaceToPrevTabStop",
100 "Cmd-/" : "toggleComment",
100 "Cmd-/" : "toggleComment",
101 "Ctrl-/" : "toggleComment"
101 "Ctrl-/" : "toggleComment"
102 },
102 },
103 mode: 'ipython',
103 mode: 'ipython',
104 theme: 'ipython',
104 theme: 'ipython',
105 matchBrackets: true,
105 matchBrackets: true,
106 // don't auto-close strings because of CodeMirror #2385
106 // don't auto-close strings because of CodeMirror #2385
107 autoCloseBrackets: "()[]{}"
107 autoCloseBrackets: "()[]{}"
108 }
108 }
109 };
109 };
110
110
111 CodeCell.msg_cells = {};
111 CodeCell.msg_cells = {};
112
112
113 CodeCell.prototype = new Cell();
113 CodeCell.prototype = new Cell();
114
114
115 /**
115 /**
116 * @method auto_highlight
116 * @method auto_highlight
117 */
117 */
118 CodeCell.prototype.auto_highlight = function () {
118 CodeCell.prototype.auto_highlight = function () {
119 this._auto_highlight(this.config.cell_magic_highlight);
119 this._auto_highlight(this.config.cell_magic_highlight);
120 };
120 };
121
121
122 /** @method create_element */
122 /** @method create_element */
123 CodeCell.prototype.create_element = function () {
123 CodeCell.prototype.create_element = function () {
124 Cell.prototype.create_element.apply(this, arguments);
124 Cell.prototype.create_element.apply(this, arguments);
125
125
126 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
126 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
127 cell.attr('tabindex','2');
127 cell.attr('tabindex','2');
128
128
129 var input = $('<div></div>').addClass('input');
129 var input = $('<div></div>').addClass('input');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
131 var inner_cell = $('<div/>').addClass('inner_cell');
131 var inner_cell = $('<div/>').addClass('inner_cell');
132 this.celltoolbar = new celltoolbar.CellToolbar({
132 this.celltoolbar = new celltoolbar.CellToolbar({
133 cell: this,
133 cell: this,
134 events: this.events,
135 notebook: this.notebook});
134 notebook: this.notebook});
136 inner_cell.append(this.celltoolbar.element);
135 inner_cell.append(this.celltoolbar.element);
137 var input_area = $('<div/>').addClass('input_area');
136 var input_area = $('<div/>').addClass('input_area');
138 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
137 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
139 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
138 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
140 inner_cell.append(input_area);
139 inner_cell.append(input_area);
141 input.append(prompt).append(inner_cell);
140 input.append(prompt).append(inner_cell);
142
141
143 var widget_area = $('<div/>')
142 var widget_area = $('<div/>')
144 .addClass('widget-area')
143 .addClass('widget-area')
145 .hide();
144 .hide();
146 this.widget_area = widget_area;
145 this.widget_area = widget_area;
147 var widget_prompt = $('<div/>')
146 var widget_prompt = $('<div/>')
148 .addClass('prompt')
147 .addClass('prompt')
149 .appendTo(widget_area);
148 .appendTo(widget_area);
150 var widget_subarea = $('<div/>')
149 var widget_subarea = $('<div/>')
151 .addClass('widget-subarea')
150 .addClass('widget-subarea')
152 .appendTo(widget_area);
151 .appendTo(widget_area);
153 this.widget_subarea = widget_subarea;
152 this.widget_subarea = widget_subarea;
154 var widget_clear_buton = $('<button />')
153 var widget_clear_buton = $('<button />')
155 .addClass('close')
154 .addClass('close')
156 .html('&times;')
155 .html('&times;')
157 .click(function() {
156 .click(function() {
158 widget_area.slideUp('', function(){ widget_subarea.html(''); });
157 widget_area.slideUp('', function(){ widget_subarea.html(''); });
159 })
158 })
160 .appendTo(widget_prompt);
159 .appendTo(widget_prompt);
161
160
162 var output = $('<div></div>');
161 var output = $('<div></div>');
163 cell.append(input).append(widget_area).append(output);
162 cell.append(input).append(widget_area).append(output);
164 this.element = cell;
163 this.element = cell;
165 this.output_area = new outputarea.OutputArea({
164 this.output_area = new outputarea.OutputArea({
166 selector: output,
165 selector: output,
167 prompt_area: true,
166 prompt_area: true,
168 events: this.events,
167 events: this.events,
169 keyboard_manager: this.keyboard_manager});
168 keyboard_manager: this.keyboard_manager});
170 this.completer = new completer.Completer(this, this.events);
169 this.completer = new completer.Completer(this, this.events);
171 };
170 };
172
171
173 /** @method bind_events */
172 /** @method bind_events */
174 CodeCell.prototype.bind_events = function () {
173 CodeCell.prototype.bind_events = function () {
175 Cell.prototype.bind_events.apply(this);
174 Cell.prototype.bind_events.apply(this);
176 var that = this;
175 var that = this;
177
176
178 this.element.focusout(
177 this.element.focusout(
179 function() { that.auto_highlight(); }
178 function() { that.auto_highlight(); }
180 );
179 );
181 };
180 };
182
181
183
182
184 /**
183 /**
185 * This method gets called in CodeMirror's onKeyDown/onKeyPress
184 * This method gets called in CodeMirror's onKeyDown/onKeyPress
186 * handlers and is used to provide custom key handling. Its return
185 * handlers and is used to provide custom key handling. Its return
187 * value is used to determine if CodeMirror should ignore the event:
186 * value is used to determine if CodeMirror should ignore the event:
188 * true = ignore, false = don't ignore.
187 * true = ignore, false = don't ignore.
189 * @method handle_codemirror_keyevent
188 * @method handle_codemirror_keyevent
190 */
189 */
191 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
190 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
192
191
193 var that = this;
192 var that = this;
194 // whatever key is pressed, first, cancel the tooltip request before
193 // whatever key is pressed, first, cancel the tooltip request before
195 // they are sent, and remove tooltip if any, except for tab again
194 // they are sent, and remove tooltip if any, except for tab again
196 var tooltip_closed = null;
195 var tooltip_closed = null;
197 if (event.type === 'keydown' && event.which != keycodes.tab ) {
196 if (event.type === 'keydown' && event.which != keycodes.tab ) {
198 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
197 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
199 }
198 }
200
199
201 var cur = editor.getCursor();
200 var cur = editor.getCursor();
202 if (event.keyCode === keycodes.enter){
201 if (event.keyCode === keycodes.enter){
203 this.auto_highlight();
202 this.auto_highlight();
204 }
203 }
205
204
206 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
205 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
207 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
206 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
208 // browser and keyboard layout !
207 // browser and keyboard layout !
209 // Pressing '(' , request tooltip, don't forget to reappend it
208 // Pressing '(' , request tooltip, don't forget to reappend it
210 // The second argument says to hide the tooltip if the docstring
209 // The second argument says to hide the tooltip if the docstring
211 // is actually empty
210 // is actually empty
212 this.tooltip.pending(that, true);
211 this.tooltip.pending(that, true);
213 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
212 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
214 // If tooltip is active, cancel it. The call to
213 // If tooltip is active, cancel it. The call to
215 // remove_and_cancel_tooltip above doesn't pass, force=true.
214 // remove_and_cancel_tooltip above doesn't pass, force=true.
216 // Because of this it won't actually close the tooltip
215 // Because of this it won't actually close the tooltip
217 // if it is in sticky mode. Thus, we have to check again if it is open
216 // if it is in sticky mode. Thus, we have to check again if it is open
218 // and close it with force=true.
217 // and close it with force=true.
219 if (!this.tooltip._hidden) {
218 if (!this.tooltip._hidden) {
220 this.tooltip.remove_and_cancel_tooltip(true);
219 this.tooltip.remove_and_cancel_tooltip(true);
221 }
220 }
222 // If we closed the tooltip, don't let CM or the global handlers
221 // If we closed the tooltip, don't let CM or the global handlers
223 // handle this event.
222 // handle this event.
224 event.stop();
223 event.stop();
225 return true;
224 return true;
226 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
225 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
227 if (editor.somethingSelected()){
226 if (editor.somethingSelected()){
228 var anchor = editor.getCursor("anchor");
227 var anchor = editor.getCursor("anchor");
229 var head = editor.getCursor("head");
228 var head = editor.getCursor("head");
230 if( anchor.line != head.line){
229 if( anchor.line != head.line){
231 return false;
230 return false;
232 }
231 }
233 }
232 }
234 this.tooltip.request(that);
233 this.tooltip.request(that);
235 event.stop();
234 event.stop();
236 return true;
235 return true;
237 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
236 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
238 // Tab completion.
237 // Tab completion.
239 this.tooltip.remove_and_cancel_tooltip();
238 this.tooltip.remove_and_cancel_tooltip();
240 if (editor.somethingSelected()) {
239 if (editor.somethingSelected()) {
241 return false;
240 return false;
242 }
241 }
243 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
242 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
244 if (pre_cursor.trim() === "") {
243 if (pre_cursor.trim() === "") {
245 // Don't autocomplete if the part of the line before the cursor
244 // Don't autocomplete if the part of the line before the cursor
246 // is empty. In this case, let CodeMirror handle indentation.
245 // is empty. In this case, let CodeMirror handle indentation.
247 return false;
246 return false;
248 } else {
247 } else {
249 event.stop();
248 event.stop();
250 this.completer.startCompletion();
249 this.completer.startCompletion();
251 return true;
250 return true;
252 }
251 }
253 }
252 }
254
253
255 // keyboard event wasn't one of those unique to code cells, let's see
254 // keyboard event wasn't one of those unique to code cells, let's see
256 // if it's one of the generic ones (i.e. check edit mode shortcuts)
255 // if it's one of the generic ones (i.e. check edit mode shortcuts)
257 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
256 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
258 };
257 };
259
258
260 // Kernel related calls.
259 // Kernel related calls.
261
260
262 CodeCell.prototype.set_kernel = function (kernel) {
261 CodeCell.prototype.set_kernel = function (kernel) {
263 this.kernel = kernel;
262 this.kernel = kernel;
264 };
263 };
265
264
266 /**
265 /**
267 * Execute current code cell to the kernel
266 * Execute current code cell to the kernel
268 * @method execute
267 * @method execute
269 */
268 */
270 CodeCell.prototype.execute = function () {
269 CodeCell.prototype.execute = function () {
271 this.output_area.clear_output();
270 this.output_area.clear_output();
272
271
273 // Clear widget area
272 // Clear widget area
274 this.widget_subarea.html('');
273 this.widget_subarea.html('');
275 this.widget_subarea.height('');
274 this.widget_subarea.height('');
276 this.widget_area.height('');
275 this.widget_area.height('');
277 this.widget_area.hide();
276 this.widget_area.hide();
278
277
279 this.set_input_prompt('*');
278 this.set_input_prompt('*');
280 this.element.addClass("running");
279 this.element.addClass("running");
281 if (this.last_msg_id) {
280 if (this.last_msg_id) {
282 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
281 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
283 }
282 }
284 var callbacks = this.get_callbacks();
283 var callbacks = this.get_callbacks();
285
284
286 var old_msg_id = this.last_msg_id;
285 var old_msg_id = this.last_msg_id;
287 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
286 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
288 if (old_msg_id) {
287 if (old_msg_id) {
289 delete CodeCell.msg_cells[old_msg_id];
288 delete CodeCell.msg_cells[old_msg_id];
290 }
289 }
291 CodeCell.msg_cells[this.last_msg_id] = this;
290 CodeCell.msg_cells[this.last_msg_id] = this;
292 };
291 };
293
292
294 /**
293 /**
295 * Construct the default callbacks for
294 * Construct the default callbacks for
296 * @method get_callbacks
295 * @method get_callbacks
297 */
296 */
298 CodeCell.prototype.get_callbacks = function () {
297 CodeCell.prototype.get_callbacks = function () {
299 return {
298 return {
300 shell : {
299 shell : {
301 reply : $.proxy(this._handle_execute_reply, this),
300 reply : $.proxy(this._handle_execute_reply, this),
302 payload : {
301 payload : {
303 set_next_input : $.proxy(this._handle_set_next_input, this),
302 set_next_input : $.proxy(this._handle_set_next_input, this),
304 page : $.proxy(this._open_with_pager, this)
303 page : $.proxy(this._open_with_pager, this)
305 }
304 }
306 },
305 },
307 iopub : {
306 iopub : {
308 output : $.proxy(this.output_area.handle_output, this.output_area),
307 output : $.proxy(this.output_area.handle_output, this.output_area),
309 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
308 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
310 },
309 },
311 input : $.proxy(this._handle_input_request, this)
310 input : $.proxy(this._handle_input_request, this)
312 };
311 };
313 };
312 };
314
313
315 CodeCell.prototype._open_with_pager = function (payload) {
314 CodeCell.prototype._open_with_pager = function (payload) {
316 this.events.trigger('open_with_text.Pager', payload);
315 this.events.trigger('open_with_text.Pager', payload);
317 };
316 };
318
317
319 /**
318 /**
320 * @method _handle_execute_reply
319 * @method _handle_execute_reply
321 * @private
320 * @private
322 */
321 */
323 CodeCell.prototype._handle_execute_reply = function (msg) {
322 CodeCell.prototype._handle_execute_reply = function (msg) {
324 this.set_input_prompt(msg.content.execution_count);
323 this.set_input_prompt(msg.content.execution_count);
325 this.element.removeClass("running");
324 this.element.removeClass("running");
326 this.events.trigger('set_dirty.Notebook', {value: true});
325 this.events.trigger('set_dirty.Notebook', {value: true});
327 };
326 };
328
327
329 /**
328 /**
330 * @method _handle_set_next_input
329 * @method _handle_set_next_input
331 * @private
330 * @private
332 */
331 */
333 CodeCell.prototype._handle_set_next_input = function (payload) {
332 CodeCell.prototype._handle_set_next_input = function (payload) {
334 var data = {'cell': this, 'text': payload.text};
333 var data = {'cell': this, 'text': payload.text};
335 this.events.trigger('set_next_input.Notebook', data);
334 this.events.trigger('set_next_input.Notebook', data);
336 };
335 };
337
336
338 /**
337 /**
339 * @method _handle_input_request
338 * @method _handle_input_request
340 * @private
339 * @private
341 */
340 */
342 CodeCell.prototype._handle_input_request = function (msg) {
341 CodeCell.prototype._handle_input_request = function (msg) {
343 this.output_area.append_raw_input(msg);
342 this.output_area.append_raw_input(msg);
344 };
343 };
345
344
346
345
347 // Basic cell manipulation.
346 // Basic cell manipulation.
348
347
349 CodeCell.prototype.select = function () {
348 CodeCell.prototype.select = function () {
350 var cont = Cell.prototype.select.apply(this);
349 var cont = Cell.prototype.select.apply(this);
351 if (cont) {
350 if (cont) {
352 this.code_mirror.refresh();
351 this.code_mirror.refresh();
353 this.auto_highlight();
352 this.auto_highlight();
354 }
353 }
355 return cont;
354 return cont;
356 };
355 };
357
356
358 CodeCell.prototype.render = function () {
357 CodeCell.prototype.render = function () {
359 var cont = Cell.prototype.render.apply(this);
358 var cont = Cell.prototype.render.apply(this);
360 // Always execute, even if we are already in the rendered state
359 // Always execute, even if we are already in the rendered state
361 return cont;
360 return cont;
362 };
361 };
363
362
364 CodeCell.prototype.unrender = function () {
363 CodeCell.prototype.unrender = function () {
365 // CodeCell is always rendered
364 // CodeCell is always rendered
366 return false;
365 return false;
367 };
366 };
368
367
369 CodeCell.prototype.select_all = function () {
368 CodeCell.prototype.select_all = function () {
370 var start = {line: 0, ch: 0};
369 var start = {line: 0, ch: 0};
371 var nlines = this.code_mirror.lineCount();
370 var nlines = this.code_mirror.lineCount();
372 var last_line = this.code_mirror.getLine(nlines-1);
371 var last_line = this.code_mirror.getLine(nlines-1);
373 var end = {line: nlines-1, ch: last_line.length};
372 var end = {line: nlines-1, ch: last_line.length};
374 this.code_mirror.setSelection(start, end);
373 this.code_mirror.setSelection(start, end);
375 };
374 };
376
375
377
376
378 CodeCell.prototype.collapse_output = function () {
377 CodeCell.prototype.collapse_output = function () {
379 this.collapsed = true;
378 this.collapsed = true;
380 this.output_area.collapse();
379 this.output_area.collapse();
381 };
380 };
382
381
383
382
384 CodeCell.prototype.expand_output = function () {
383 CodeCell.prototype.expand_output = function () {
385 this.collapsed = false;
384 this.collapsed = false;
386 this.output_area.expand();
385 this.output_area.expand();
387 this.output_area.unscroll_area();
386 this.output_area.unscroll_area();
388 };
387 };
389
388
390 CodeCell.prototype.scroll_output = function () {
389 CodeCell.prototype.scroll_output = function () {
391 this.output_area.expand();
390 this.output_area.expand();
392 this.output_area.scroll_if_long();
391 this.output_area.scroll_if_long();
393 };
392 };
394
393
395 CodeCell.prototype.toggle_output = function () {
394 CodeCell.prototype.toggle_output = function () {
396 this.collapsed = Boolean(1 - this.collapsed);
395 this.collapsed = Boolean(1 - this.collapsed);
397 this.output_area.toggle_output();
396 this.output_area.toggle_output();
398 };
397 };
399
398
400 CodeCell.prototype.toggle_output_scroll = function () {
399 CodeCell.prototype.toggle_output_scroll = function () {
401 this.output_area.toggle_scroll();
400 this.output_area.toggle_scroll();
402 };
401 };
403
402
404
403
405 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
404 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
406 var ns;
405 var ns;
407 if (prompt_value === undefined) {
406 if (prompt_value === undefined) {
408 ns = "&nbsp;";
407 ns = "&nbsp;";
409 } else {
408 } else {
410 ns = encodeURIComponent(prompt_value);
409 ns = encodeURIComponent(prompt_value);
411 }
410 }
412 return 'In&nbsp;[' + ns + ']:';
411 return 'In&nbsp;[' + ns + ']:';
413 };
412 };
414
413
415 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
414 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
416 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
415 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
417 for(var i=1; i < lines_number; i++) {
416 for(var i=1; i < lines_number; i++) {
418 html.push(['...:']);
417 html.push(['...:']);
419 }
418 }
420 return html.join('<br/>');
419 return html.join('<br/>');
421 };
420 };
422
421
423 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
422 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
424
423
425
424
426 CodeCell.prototype.set_input_prompt = function (number) {
425 CodeCell.prototype.set_input_prompt = function (number) {
427 var nline = 1;
426 var nline = 1;
428 if (this.code_mirror !== undefined) {
427 if (this.code_mirror !== undefined) {
429 nline = this.code_mirror.lineCount();
428 nline = this.code_mirror.lineCount();
430 }
429 }
431 this.input_prompt_number = number;
430 this.input_prompt_number = number;
432 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
431 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
433 // This HTML call is okay because the user contents are escaped.
432 // This HTML call is okay because the user contents are escaped.
434 this.element.find('div.input_prompt').html(prompt_html);
433 this.element.find('div.input_prompt').html(prompt_html);
435 };
434 };
436
435
437
436
438 CodeCell.prototype.clear_input = function () {
437 CodeCell.prototype.clear_input = function () {
439 this.code_mirror.setValue('');
438 this.code_mirror.setValue('');
440 };
439 };
441
440
442
441
443 CodeCell.prototype.get_text = function () {
442 CodeCell.prototype.get_text = function () {
444 return this.code_mirror.getValue();
443 return this.code_mirror.getValue();
445 };
444 };
446
445
447
446
448 CodeCell.prototype.set_text = function (code) {
447 CodeCell.prototype.set_text = function (code) {
449 return this.code_mirror.setValue(code);
448 return this.code_mirror.setValue(code);
450 };
449 };
451
450
452
451
453 CodeCell.prototype.clear_output = function (wait) {
452 CodeCell.prototype.clear_output = function (wait) {
454 this.output_area.clear_output(wait);
453 this.output_area.clear_output(wait);
455 this.set_input_prompt();
454 this.set_input_prompt();
456 };
455 };
457
456
458
457
459 // JSON serialization
458 // JSON serialization
460
459
461 CodeCell.prototype.fromJSON = function (data) {
460 CodeCell.prototype.fromJSON = function (data) {
462 Cell.prototype.fromJSON.apply(this, arguments);
461 Cell.prototype.fromJSON.apply(this, arguments);
463 if (data.cell_type === 'code') {
462 if (data.cell_type === 'code') {
464 if (data.input !== undefined) {
463 if (data.input !== undefined) {
465 this.set_text(data.input);
464 this.set_text(data.input);
466 // make this value the starting point, so that we can only undo
465 // make this value the starting point, so that we can only undo
467 // to this state, instead of a blank cell
466 // to this state, instead of a blank cell
468 this.code_mirror.clearHistory();
467 this.code_mirror.clearHistory();
469 this.auto_highlight();
468 this.auto_highlight();
470 }
469 }
471 if (data.prompt_number !== undefined) {
470 if (data.prompt_number !== undefined) {
472 this.set_input_prompt(data.prompt_number);
471 this.set_input_prompt(data.prompt_number);
473 } else {
472 } else {
474 this.set_input_prompt();
473 this.set_input_prompt();
475 }
474 }
476 this.output_area.trusted = data.trusted || false;
475 this.output_area.trusted = data.trusted || false;
477 this.output_area.fromJSON(data.outputs);
476 this.output_area.fromJSON(data.outputs);
478 if (data.collapsed !== undefined) {
477 if (data.collapsed !== undefined) {
479 if (data.collapsed) {
478 if (data.collapsed) {
480 this.collapse_output();
479 this.collapse_output();
481 } else {
480 } else {
482 this.expand_output();
481 this.expand_output();
483 }
482 }
484 }
483 }
485 }
484 }
486 };
485 };
487
486
488
487
489 CodeCell.prototype.toJSON = function () {
488 CodeCell.prototype.toJSON = function () {
490 var data = Cell.prototype.toJSON.apply(this);
489 var data = Cell.prototype.toJSON.apply(this);
491 data.input = this.get_text();
490 data.input = this.get_text();
492 // is finite protect against undefined and '*' value
491 // is finite protect against undefined and '*' value
493 if (isFinite(this.input_prompt_number)) {
492 if (isFinite(this.input_prompt_number)) {
494 data.prompt_number = this.input_prompt_number;
493 data.prompt_number = this.input_prompt_number;
495 }
494 }
496 var outputs = this.output_area.toJSON();
495 var outputs = this.output_area.toJSON();
497 data.outputs = outputs;
496 data.outputs = outputs;
498 data.language = 'python';
497 data.language = 'python';
499 data.trusted = this.output_area.trusted;
498 data.trusted = this.output_area.trusted;
500 data.collapsed = this.collapsed;
499 data.collapsed = this.collapsed;
501 return data;
500 return data;
502 };
501 };
503
502
504 /**
503 /**
505 * handle cell level logic when a cell is unselected
504 * handle cell level logic when a cell is unselected
506 * @method unselect
505 * @method unselect
507 * @return is the action being taken
506 * @return is the action being taken
508 */
507 */
509 CodeCell.prototype.unselect = function () {
508 CodeCell.prototype.unselect = function () {
510 var cont = Cell.prototype.unselect.apply(this);
509 var cont = Cell.prototype.unselect.apply(this);
511 if (cont) {
510 if (cont) {
512 // When a code cell is usnelected, make sure that the corresponding
511 // When a code cell is usnelected, make sure that the corresponding
513 // tooltip and completer to that cell is closed.
512 // tooltip and completer to that cell is closed.
514 this.tooltip.remove_and_cancel_tooltip(true);
513 this.tooltip.remove_and_cancel_tooltip(true);
515 if (this.completer !== null) {
514 if (this.completer !== null) {
516 this.completer.close();
515 this.completer.close();
517 }
516 }
518 }
517 }
519 return cont;
518 return cont;
520 };
519 };
521
520
522 // Backwards compatability.
521 // Backwards compatability.
523 IPython.CodeCell = CodeCell;
522 IPython.CodeCell = CodeCell;
524
523
525 return {'CodeCell': CodeCell};
524 return {'CodeCell': CodeCell};
526 });
525 });
@@ -1,2589 +1,2589
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/js/session',
11 'services/sessions/js/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'highlight',
14 'highlight',
15 'notebook/js/mathjaxutils',
15 'notebook/js/mathjaxutils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'notebook/js/tooltip',
17 'notebook/js/tooltip',
18 'notebook/js/celltoolbarpresets/default',
18 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/rawcell',
19 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/slideshow',
20 'notebook/js/celltoolbarpresets/slideshow',
21 ], function (
21 ], function (
22 IPython,
22 IPython,
23 $,
23 $,
24 utils,
24 utils,
25 dialog,
25 dialog,
26 textcell,
26 textcell,
27 codecell,
27 codecell,
28 session,
28 session,
29 celltoolbar,
29 celltoolbar,
30 marked,
30 marked,
31 hljs,
31 hljs,
32 mathjaxutils,
32 mathjaxutils,
33 keyboard,
33 keyboard,
34 tooltip,
34 tooltip,
35 default_celltoolbar,
35 default_celltoolbar,
36 rawcell_celltoolbar,
36 rawcell_celltoolbar,
37 slideshow_celltoolbar
37 slideshow_celltoolbar
38 ) {
38 ) {
39
39
40 var Notebook = function (selector, options) {
40 var Notebook = function (selector, options) {
41 // Constructor
41 // Constructor
42 //
42 //
43 // A notebook contains and manages cells.
43 // A notebook contains and manages cells.
44 //
44 //
45 // Parameters:
45 // Parameters:
46 // selector: string
46 // selector: string
47 // options: dictionary
47 // options: dictionary
48 // Dictionary of keyword arguments.
48 // Dictionary of keyword arguments.
49 // events: $(Events) instance
49 // events: $(Events) instance
50 // keyboard_manager: KeyboardManager instance
50 // keyboard_manager: KeyboardManager instance
51 // save_widget: SaveWidget instance
51 // save_widget: SaveWidget instance
52 // config: dictionary
52 // config: dictionary
53 // base_url : string
53 // base_url : string
54 // notebook_path : string
54 // notebook_path : string
55 // notebook_name : string
55 // notebook_name : string
56 this.config = options.config || {};
56 this.config = options.config || {};
57 this.base_url = options.base_url;
57 this.base_url = options.base_url;
58 this.notebook_path = options.notebook_path;
58 this.notebook_path = options.notebook_path;
59 this.notebook_name = options.notebook_name;
59 this.notebook_name = options.notebook_name;
60 this.events = options.events;
60 this.events = options.events;
61 this.keyboard_manager = options.keyboard_manager;
61 this.keyboard_manager = options.keyboard_manager;
62 this.save_widget = options.save_widget;
62 this.save_widget = options.save_widget;
63 this.tooltip = new tooltip.Tooltip(this.events);
63 this.tooltip = new tooltip.Tooltip(this.events);
64 this.ws_url = options.ws_url;
64 this.ws_url = options.ws_url;
65 // default_kernel_name is a temporary measure while we implement proper
65 // default_kernel_name is a temporary measure while we implement proper
66 // kernel selection and delayed start. Do not rely on it.
66 // kernel selection and delayed start. Do not rely on it.
67 this.default_kernel_name = 'python';
67 this.default_kernel_name = 'python';
68 // TODO: This code smells (and the other `= this` line a couple lines down)
68 // TODO: This code smells (and the other `= this` line a couple lines down)
69 // We need a better way to deal with circular instance references.
69 // We need a better way to deal with circular instance references.
70 this.keyboard_manager.notebook = this;
70 this.keyboard_manager.notebook = this;
71 this.save_widget.notebook = this;
71 this.save_widget.notebook = this;
72
72
73 mathjaxutils.init();
73 mathjaxutils.init();
74
74
75 if (marked) {
75 if (marked) {
76 marked.setOptions({
76 marked.setOptions({
77 gfm : true,
77 gfm : true,
78 tables: true,
78 tables: true,
79 langPrefix: "language-",
79 langPrefix: "language-",
80 highlight: function(code, lang) {
80 highlight: function(code, lang) {
81 if (!lang) {
81 if (!lang) {
82 // no language, no highlight
82 // no language, no highlight
83 return code;
83 return code;
84 }
84 }
85 var highlighted;
85 var highlighted;
86 try {
86 try {
87 highlighted = hljs.highlight(lang, code, false);
87 highlighted = hljs.highlight(lang, code, false);
88 } catch(err) {
88 } catch(err) {
89 highlighted = hljs.highlightAuto(code);
89 highlighted = hljs.highlightAuto(code);
90 }
90 }
91 return highlighted.value;
91 return highlighted.value;
92 }
92 }
93 });
93 });
94 }
94 }
95
95
96 this.element = $(selector);
96 this.element = $(selector);
97 this.element.scroll();
97 this.element.scroll();
98 this.element.data("notebook", this);
98 this.element.data("notebook", this);
99 this.next_prompt_number = 1;
99 this.next_prompt_number = 1;
100 this.session = null;
100 this.session = null;
101 this.kernel = null;
101 this.kernel = null;
102 this.clipboard = null;
102 this.clipboard = null;
103 this.undelete_backup = null;
103 this.undelete_backup = null;
104 this.undelete_index = null;
104 this.undelete_index = null;
105 this.undelete_below = false;
105 this.undelete_below = false;
106 this.paste_enabled = false;
106 this.paste_enabled = false;
107 // It is important to start out in command mode to match the intial mode
107 // It is important to start out in command mode to match the intial mode
108 // of the KeyboardManager.
108 // of the KeyboardManager.
109 this.mode = 'command';
109 this.mode = 'command';
110 this.set_dirty(false);
110 this.set_dirty(false);
111 this.metadata = {};
111 this.metadata = {};
112 this._checkpoint_after_save = false;
112 this._checkpoint_after_save = false;
113 this.last_checkpoint = null;
113 this.last_checkpoint = null;
114 this.checkpoints = [];
114 this.checkpoints = [];
115 this.autosave_interval = 0;
115 this.autosave_interval = 0;
116 this.autosave_timer = null;
116 this.autosave_timer = null;
117 // autosave *at most* every two minutes
117 // autosave *at most* every two minutes
118 this.minimum_autosave_interval = 120000;
118 this.minimum_autosave_interval = 120000;
119 // single worksheet for now
119 // single worksheet for now
120 this.worksheet_metadata = {};
120 this.worksheet_metadata = {};
121 this.notebook_name_blacklist_re = /[\/\\:]/;
121 this.notebook_name_blacklist_re = /[\/\\:]/;
122 this.nbformat = 3; // Increment this when changing the nbformat
122 this.nbformat = 3; // Increment this when changing the nbformat
123 this.nbformat_minor = 0; // Increment this when changing the nbformat
123 this.nbformat_minor = 0; // Increment this when changing the nbformat
124 this.codemirror_mode = 'ipython'
124 this.codemirror_mode = 'ipython'
125 this.style();
125 this.style();
126 this.create_elements();
126 this.create_elements();
127 this.bind_events();
127 this.bind_events();
128 this.save_notebook = function() { // don't allow save until notebook_loaded
128 this.save_notebook = function() { // don't allow save until notebook_loaded
129 this.save_notebook_error(null, null, "Load failed, save is disabled");
129 this.save_notebook_error(null, null, "Load failed, save is disabled");
130 };
130 };
131
131
132 // Trigger cell toolbar registration.
132 // Trigger cell toolbar registration.
133 default_celltoolbar.register(this, options.events);
133 default_celltoolbar.register(this);
134 rawcell_celltoolbar.register(this, options.events);
134 rawcell_celltoolbar.register(this);
135 slideshow_celltoolbar.register(this, options.events);
135 slideshow_celltoolbar.register(this);
136 };
136 };
137
137
138 /**
138 /**
139 * Tweak the notebook's CSS style.
139 * Tweak the notebook's CSS style.
140 *
140 *
141 * @method style
141 * @method style
142 */
142 */
143 Notebook.prototype.style = function () {
143 Notebook.prototype.style = function () {
144 $('div#notebook').addClass('border-box-sizing');
144 $('div#notebook').addClass('border-box-sizing');
145 };
145 };
146
146
147 /**
147 /**
148 * Create an HTML and CSS representation of the notebook.
148 * Create an HTML and CSS representation of the notebook.
149 *
149 *
150 * @method create_elements
150 * @method create_elements
151 */
151 */
152 Notebook.prototype.create_elements = function () {
152 Notebook.prototype.create_elements = function () {
153 var that = this;
153 var that = this;
154 this.element.attr('tabindex','-1');
154 this.element.attr('tabindex','-1');
155 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
155 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
156 // We add this end_space div to the end of the notebook div to:
156 // We add this end_space div to the end of the notebook div to:
157 // i) provide a margin between the last cell and the end of the notebook
157 // i) provide a margin between the last cell and the end of the notebook
158 // ii) to prevent the div from scrolling up when the last cell is being
158 // ii) to prevent the div from scrolling up when the last cell is being
159 // edited, but is too low on the page, which browsers will do automatically.
159 // edited, but is too low on the page, which browsers will do automatically.
160 var end_space = $('<div/>').addClass('end_space');
160 var end_space = $('<div/>').addClass('end_space');
161 end_space.dblclick(function (e) {
161 end_space.dblclick(function (e) {
162 var ncells = that.ncells();
162 var ncells = that.ncells();
163 that.insert_cell_below('code',ncells-1);
163 that.insert_cell_below('code',ncells-1);
164 });
164 });
165 this.element.append(this.container);
165 this.element.append(this.container);
166 this.container.append(end_space);
166 this.container.append(end_space);
167 };
167 };
168
168
169 /**
169 /**
170 * Bind JavaScript events: key presses and custom IPython events.
170 * Bind JavaScript events: key presses and custom IPython events.
171 *
171 *
172 * @method bind_events
172 * @method bind_events
173 */
173 */
174 Notebook.prototype.bind_events = function () {
174 Notebook.prototype.bind_events = function () {
175 var that = this;
175 var that = this;
176
176
177 this.events.on('set_next_input.Notebook', function (event, data) {
177 this.events.on('set_next_input.Notebook', function (event, data) {
178 var index = that.find_cell_index(data.cell);
178 var index = that.find_cell_index(data.cell);
179 var new_cell = that.insert_cell_below('code',index);
179 var new_cell = that.insert_cell_below('code',index);
180 new_cell.set_text(data.text);
180 new_cell.set_text(data.text);
181 that.dirty = true;
181 that.dirty = true;
182 });
182 });
183
183
184 this.events.on('set_dirty.Notebook', function (event, data) {
184 this.events.on('set_dirty.Notebook', function (event, data) {
185 that.dirty = data.value;
185 that.dirty = data.value;
186 });
186 });
187
187
188 this.events.on('trust_changed.Notebook', function (event, data) {
188 this.events.on('trust_changed.Notebook', function (event, data) {
189 that.trusted = data.value;
189 that.trusted = data.value;
190 });
190 });
191
191
192 this.events.on('select.Cell', function (event, data) {
192 this.events.on('select.Cell', function (event, data) {
193 var index = that.find_cell_index(data.cell);
193 var index = that.find_cell_index(data.cell);
194 that.select(index);
194 that.select(index);
195 });
195 });
196
196
197 this.events.on('edit_mode.Cell', function (event, data) {
197 this.events.on('edit_mode.Cell', function (event, data) {
198 that.handle_edit_mode(data.cell);
198 that.handle_edit_mode(data.cell);
199 });
199 });
200
200
201 this.events.on('command_mode.Cell', function (event, data) {
201 this.events.on('command_mode.Cell', function (event, data) {
202 that.handle_command_mode(data.cell);
202 that.handle_command_mode(data.cell);
203 });
203 });
204
204
205 this.events.on('status_autorestarting.Kernel', function () {
205 this.events.on('status_autorestarting.Kernel', function () {
206 dialog.modal({
206 dialog.modal({
207 notebook: that,
207 notebook: that,
208 keyboard_manager: that.keyboard_manager,
208 keyboard_manager: that.keyboard_manager,
209 title: "Kernel Restarting",
209 title: "Kernel Restarting",
210 body: "The kernel appears to have died. It will restart automatically.",
210 body: "The kernel appears to have died. It will restart automatically.",
211 buttons: {
211 buttons: {
212 OK : {
212 OK : {
213 class : "btn-primary"
213 class : "btn-primary"
214 }
214 }
215 }
215 }
216 });
216 });
217 });
217 });
218
218
219 this.events.on('spec_changed.Kernel', function(event, data) {
219 this.events.on('spec_changed.Kernel', function(event, data) {
220 that.set_kernelspec_metadata(data);
220 that.set_kernelspec_metadata(data);
221 if (data.codemirror_mode) {
221 if (data.codemirror_mode) {
222 that.set_codemirror_mode(data.codemirror_mode);
222 that.set_codemirror_mode(data.codemirror_mode);
223 }
223 }
224 });
224 });
225
225
226 var collapse_time = function (time) {
226 var collapse_time = function (time) {
227 var app_height = $('#ipython-main-app').height(); // content height
227 var app_height = $('#ipython-main-app').height(); // content height
228 var splitter_height = $('div#pager_splitter').outerHeight(true);
228 var splitter_height = $('div#pager_splitter').outerHeight(true);
229 var new_height = app_height - splitter_height;
229 var new_height = app_height - splitter_height;
230 that.element.animate({height : new_height + 'px'}, time);
230 that.element.animate({height : new_height + 'px'}, time);
231 };
231 };
232
232
233 this.element.bind('collapse_pager', function (event, extrap) {
233 this.element.bind('collapse_pager', function (event, extrap) {
234 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
234 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
235 collapse_time(time);
235 collapse_time(time);
236 });
236 });
237
237
238 var expand_time = function (time) {
238 var expand_time = function (time) {
239 var app_height = $('#ipython-main-app').height(); // content height
239 var app_height = $('#ipython-main-app').height(); // content height
240 var splitter_height = $('div#pager_splitter').outerHeight(true);
240 var splitter_height = $('div#pager_splitter').outerHeight(true);
241 var pager_height = $('div#pager').outerHeight(true);
241 var pager_height = $('div#pager').outerHeight(true);
242 var new_height = app_height - pager_height - splitter_height;
242 var new_height = app_height - pager_height - splitter_height;
243 that.element.animate({height : new_height + 'px'}, time);
243 that.element.animate({height : new_height + 'px'}, time);
244 };
244 };
245
245
246 this.element.bind('expand_pager', function (event, extrap) {
246 this.element.bind('expand_pager', function (event, extrap) {
247 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
247 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
248 expand_time(time);
248 expand_time(time);
249 });
249 });
250
250
251 // Firefox 22 broke $(window).on("beforeunload")
251 // Firefox 22 broke $(window).on("beforeunload")
252 // I'm not sure why or how.
252 // I'm not sure why or how.
253 window.onbeforeunload = function (e) {
253 window.onbeforeunload = function (e) {
254 // TODO: Make killing the kernel configurable.
254 // TODO: Make killing the kernel configurable.
255 var kill_kernel = false;
255 var kill_kernel = false;
256 if (kill_kernel) {
256 if (kill_kernel) {
257 that.session.kill_kernel();
257 that.session.kill_kernel();
258 }
258 }
259 // if we are autosaving, trigger an autosave on nav-away.
259 // if we are autosaving, trigger an autosave on nav-away.
260 // still warn, because if we don't the autosave may fail.
260 // still warn, because if we don't the autosave may fail.
261 if (that.dirty) {
261 if (that.dirty) {
262 if ( that.autosave_interval ) {
262 if ( that.autosave_interval ) {
263 // schedule autosave in a timeout
263 // schedule autosave in a timeout
264 // this gives you a chance to forcefully discard changes
264 // this gives you a chance to forcefully discard changes
265 // by reloading the page if you *really* want to.
265 // by reloading the page if you *really* want to.
266 // the timer doesn't start until you *dismiss* the dialog.
266 // the timer doesn't start until you *dismiss* the dialog.
267 setTimeout(function () {
267 setTimeout(function () {
268 if (that.dirty) {
268 if (that.dirty) {
269 that.save_notebook();
269 that.save_notebook();
270 }
270 }
271 }, 1000);
271 }, 1000);
272 return "Autosave in progress, latest changes may be lost.";
272 return "Autosave in progress, latest changes may be lost.";
273 } else {
273 } else {
274 return "Unsaved changes will be lost.";
274 return "Unsaved changes will be lost.";
275 }
275 }
276 }
276 }
277 // Null is the *only* return value that will make the browser not
277 // Null is the *only* return value that will make the browser not
278 // pop up the "don't leave" dialog.
278 // pop up the "don't leave" dialog.
279 return null;
279 return null;
280 };
280 };
281 };
281 };
282
282
283 /**
283 /**
284 * Set the dirty flag, and trigger the set_dirty.Notebook event
284 * Set the dirty flag, and trigger the set_dirty.Notebook event
285 *
285 *
286 * @method set_dirty
286 * @method set_dirty
287 */
287 */
288 Notebook.prototype.set_dirty = function (value) {
288 Notebook.prototype.set_dirty = function (value) {
289 if (value === undefined) {
289 if (value === undefined) {
290 value = true;
290 value = true;
291 }
291 }
292 if (this.dirty == value) {
292 if (this.dirty == value) {
293 return;
293 return;
294 }
294 }
295 this.events.trigger('set_dirty.Notebook', {value: value});
295 this.events.trigger('set_dirty.Notebook', {value: value});
296 };
296 };
297
297
298 /**
298 /**
299 * Scroll the top of the page to a given cell.
299 * Scroll the top of the page to a given cell.
300 *
300 *
301 * @method scroll_to_cell
301 * @method scroll_to_cell
302 * @param {Number} cell_number An index of the cell to view
302 * @param {Number} cell_number An index of the cell to view
303 * @param {Number} time Animation time in milliseconds
303 * @param {Number} time Animation time in milliseconds
304 * @return {Number} Pixel offset from the top of the container
304 * @return {Number} Pixel offset from the top of the container
305 */
305 */
306 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
306 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
307 var cells = this.get_cells();
307 var cells = this.get_cells();
308 time = time || 0;
308 time = time || 0;
309 cell_number = Math.min(cells.length-1,cell_number);
309 cell_number = Math.min(cells.length-1,cell_number);
310 cell_number = Math.max(0 ,cell_number);
310 cell_number = Math.max(0 ,cell_number);
311 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
311 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
312 this.element.animate({scrollTop:scroll_value}, time);
312 this.element.animate({scrollTop:scroll_value}, time);
313 return scroll_value;
313 return scroll_value;
314 };
314 };
315
315
316 /**
316 /**
317 * Scroll to the bottom of the page.
317 * Scroll to the bottom of the page.
318 *
318 *
319 * @method scroll_to_bottom
319 * @method scroll_to_bottom
320 */
320 */
321 Notebook.prototype.scroll_to_bottom = function () {
321 Notebook.prototype.scroll_to_bottom = function () {
322 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
322 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
323 };
323 };
324
324
325 /**
325 /**
326 * Scroll to the top of the page.
326 * Scroll to the top of the page.
327 *
327 *
328 * @method scroll_to_top
328 * @method scroll_to_top
329 */
329 */
330 Notebook.prototype.scroll_to_top = function () {
330 Notebook.prototype.scroll_to_top = function () {
331 this.element.animate({scrollTop:0}, 0);
331 this.element.animate({scrollTop:0}, 0);
332 };
332 };
333
333
334 // Edit Notebook metadata
334 // Edit Notebook metadata
335
335
336 Notebook.prototype.edit_metadata = function () {
336 Notebook.prototype.edit_metadata = function () {
337 var that = this;
337 var that = this;
338 dialog.edit_metadata({
338 dialog.edit_metadata({
339 md: this.metadata,
339 md: this.metadata,
340 callback: function (md) {
340 callback: function (md) {
341 that.metadata = md;
341 that.metadata = md;
342 },
342 },
343 name: 'Notebook',
343 name: 'Notebook',
344 notebook: this,
344 notebook: this,
345 keyboard_manager: this.keyboard_manager});
345 keyboard_manager: this.keyboard_manager});
346 };
346 };
347
347
348 Notebook.prototype.set_kernelspec_metadata = function(ks) {
348 Notebook.prototype.set_kernelspec_metadata = function(ks) {
349 var tostore = {};
349 var tostore = {};
350 $.map(ks, function(value, field) {
350 $.map(ks, function(value, field) {
351 if (field !== 'argv' && field !== 'env') {
351 if (field !== 'argv' && field !== 'env') {
352 tostore[field] = value;
352 tostore[field] = value;
353 }
353 }
354 });
354 });
355 this.metadata.kernelspec = tostore;
355 this.metadata.kernelspec = tostore;
356 }
356 }
357
357
358 // Cell indexing, retrieval, etc.
358 // Cell indexing, retrieval, etc.
359
359
360 /**
360 /**
361 * Get all cell elements in the notebook.
361 * Get all cell elements in the notebook.
362 *
362 *
363 * @method get_cell_elements
363 * @method get_cell_elements
364 * @return {jQuery} A selector of all cell elements
364 * @return {jQuery} A selector of all cell elements
365 */
365 */
366 Notebook.prototype.get_cell_elements = function () {
366 Notebook.prototype.get_cell_elements = function () {
367 return this.container.children("div.cell");
367 return this.container.children("div.cell");
368 };
368 };
369
369
370 /**
370 /**
371 * Get a particular cell element.
371 * Get a particular cell element.
372 *
372 *
373 * @method get_cell_element
373 * @method get_cell_element
374 * @param {Number} index An index of a cell to select
374 * @param {Number} index An index of a cell to select
375 * @return {jQuery} A selector of the given cell.
375 * @return {jQuery} A selector of the given cell.
376 */
376 */
377 Notebook.prototype.get_cell_element = function (index) {
377 Notebook.prototype.get_cell_element = function (index) {
378 var result = null;
378 var result = null;
379 var e = this.get_cell_elements().eq(index);
379 var e = this.get_cell_elements().eq(index);
380 if (e.length !== 0) {
380 if (e.length !== 0) {
381 result = e;
381 result = e;
382 }
382 }
383 return result;
383 return result;
384 };
384 };
385
385
386 /**
386 /**
387 * Try to get a particular cell by msg_id.
387 * Try to get a particular cell by msg_id.
388 *
388 *
389 * @method get_msg_cell
389 * @method get_msg_cell
390 * @param {String} msg_id A message UUID
390 * @param {String} msg_id A message UUID
391 * @return {Cell} Cell or null if no cell was found.
391 * @return {Cell} Cell or null if no cell was found.
392 */
392 */
393 Notebook.prototype.get_msg_cell = function (msg_id) {
393 Notebook.prototype.get_msg_cell = function (msg_id) {
394 return codecell.CodeCell.msg_cells[msg_id] || null;
394 return codecell.CodeCell.msg_cells[msg_id] || null;
395 };
395 };
396
396
397 /**
397 /**
398 * Count the cells in this notebook.
398 * Count the cells in this notebook.
399 *
399 *
400 * @method ncells
400 * @method ncells
401 * @return {Number} The number of cells in this notebook
401 * @return {Number} The number of cells in this notebook
402 */
402 */
403 Notebook.prototype.ncells = function () {
403 Notebook.prototype.ncells = function () {
404 return this.get_cell_elements().length;
404 return this.get_cell_elements().length;
405 };
405 };
406
406
407 /**
407 /**
408 * Get all Cell objects in this notebook.
408 * Get all Cell objects in this notebook.
409 *
409 *
410 * @method get_cells
410 * @method get_cells
411 * @return {Array} This notebook's Cell objects
411 * @return {Array} This notebook's Cell objects
412 */
412 */
413 // TODO: we are often calling cells as cells()[i], which we should optimize
413 // TODO: we are often calling cells as cells()[i], which we should optimize
414 // to cells(i) or a new method.
414 // to cells(i) or a new method.
415 Notebook.prototype.get_cells = function () {
415 Notebook.prototype.get_cells = function () {
416 return this.get_cell_elements().toArray().map(function (e) {
416 return this.get_cell_elements().toArray().map(function (e) {
417 return $(e).data("cell");
417 return $(e).data("cell");
418 });
418 });
419 };
419 };
420
420
421 /**
421 /**
422 * Get a Cell object from this notebook.
422 * Get a Cell object from this notebook.
423 *
423 *
424 * @method get_cell
424 * @method get_cell
425 * @param {Number} index An index of a cell to retrieve
425 * @param {Number} index An index of a cell to retrieve
426 * @return {Cell} A particular cell
426 * @return {Cell} A particular cell
427 */
427 */
428 Notebook.prototype.get_cell = function (index) {
428 Notebook.prototype.get_cell = function (index) {
429 var result = null;
429 var result = null;
430 var ce = this.get_cell_element(index);
430 var ce = this.get_cell_element(index);
431 if (ce !== null) {
431 if (ce !== null) {
432 result = ce.data('cell');
432 result = ce.data('cell');
433 }
433 }
434 return result;
434 return result;
435 };
435 };
436
436
437 /**
437 /**
438 * Get the cell below a given cell.
438 * Get the cell below a given cell.
439 *
439 *
440 * @method get_next_cell
440 * @method get_next_cell
441 * @param {Cell} cell The provided cell
441 * @param {Cell} cell The provided cell
442 * @return {Cell} The next cell
442 * @return {Cell} The next cell
443 */
443 */
444 Notebook.prototype.get_next_cell = function (cell) {
444 Notebook.prototype.get_next_cell = function (cell) {
445 var result = null;
445 var result = null;
446 var index = this.find_cell_index(cell);
446 var index = this.find_cell_index(cell);
447 if (this.is_valid_cell_index(index+1)) {
447 if (this.is_valid_cell_index(index+1)) {
448 result = this.get_cell(index+1);
448 result = this.get_cell(index+1);
449 }
449 }
450 return result;
450 return result;
451 };
451 };
452
452
453 /**
453 /**
454 * Get the cell above a given cell.
454 * Get the cell above a given cell.
455 *
455 *
456 * @method get_prev_cell
456 * @method get_prev_cell
457 * @param {Cell} cell The provided cell
457 * @param {Cell} cell The provided cell
458 * @return {Cell} The previous cell
458 * @return {Cell} The previous cell
459 */
459 */
460 Notebook.prototype.get_prev_cell = function (cell) {
460 Notebook.prototype.get_prev_cell = function (cell) {
461 // TODO: off-by-one
461 // TODO: off-by-one
462 // nb.get_prev_cell(nb.get_cell(1)) is null
462 // nb.get_prev_cell(nb.get_cell(1)) is null
463 var result = null;
463 var result = null;
464 var index = this.find_cell_index(cell);
464 var index = this.find_cell_index(cell);
465 if (index !== null && index > 1) {
465 if (index !== null && index > 1) {
466 result = this.get_cell(index-1);
466 result = this.get_cell(index-1);
467 }
467 }
468 return result;
468 return result;
469 };
469 };
470
470
471 /**
471 /**
472 * Get the numeric index of a given cell.
472 * Get the numeric index of a given cell.
473 *
473 *
474 * @method find_cell_index
474 * @method find_cell_index
475 * @param {Cell} cell The provided cell
475 * @param {Cell} cell The provided cell
476 * @return {Number} The cell's numeric index
476 * @return {Number} The cell's numeric index
477 */
477 */
478 Notebook.prototype.find_cell_index = function (cell) {
478 Notebook.prototype.find_cell_index = function (cell) {
479 var result = null;
479 var result = null;
480 this.get_cell_elements().filter(function (index) {
480 this.get_cell_elements().filter(function (index) {
481 if ($(this).data("cell") === cell) {
481 if ($(this).data("cell") === cell) {
482 result = index;
482 result = index;
483 }
483 }
484 });
484 });
485 return result;
485 return result;
486 };
486 };
487
487
488 /**
488 /**
489 * Get a given index , or the selected index if none is provided.
489 * Get a given index , or the selected index if none is provided.
490 *
490 *
491 * @method index_or_selected
491 * @method index_or_selected
492 * @param {Number} index A cell's index
492 * @param {Number} index A cell's index
493 * @return {Number} The given index, or selected index if none is provided.
493 * @return {Number} The given index, or selected index if none is provided.
494 */
494 */
495 Notebook.prototype.index_or_selected = function (index) {
495 Notebook.prototype.index_or_selected = function (index) {
496 var i;
496 var i;
497 if (index === undefined || index === null) {
497 if (index === undefined || index === null) {
498 i = this.get_selected_index();
498 i = this.get_selected_index();
499 if (i === null) {
499 if (i === null) {
500 i = 0;
500 i = 0;
501 }
501 }
502 } else {
502 } else {
503 i = index;
503 i = index;
504 }
504 }
505 return i;
505 return i;
506 };
506 };
507
507
508 /**
508 /**
509 * Get the currently selected cell.
509 * Get the currently selected cell.
510 * @method get_selected_cell
510 * @method get_selected_cell
511 * @return {Cell} The selected cell
511 * @return {Cell} The selected cell
512 */
512 */
513 Notebook.prototype.get_selected_cell = function () {
513 Notebook.prototype.get_selected_cell = function () {
514 var index = this.get_selected_index();
514 var index = this.get_selected_index();
515 return this.get_cell(index);
515 return this.get_cell(index);
516 };
516 };
517
517
518 /**
518 /**
519 * Check whether a cell index is valid.
519 * Check whether a cell index is valid.
520 *
520 *
521 * @method is_valid_cell_index
521 * @method is_valid_cell_index
522 * @param {Number} index A cell index
522 * @param {Number} index A cell index
523 * @return True if the index is valid, false otherwise
523 * @return True if the index is valid, false otherwise
524 */
524 */
525 Notebook.prototype.is_valid_cell_index = function (index) {
525 Notebook.prototype.is_valid_cell_index = function (index) {
526 if (index !== null && index >= 0 && index < this.ncells()) {
526 if (index !== null && index >= 0 && index < this.ncells()) {
527 return true;
527 return true;
528 } else {
528 } else {
529 return false;
529 return false;
530 }
530 }
531 };
531 };
532
532
533 /**
533 /**
534 * Get the index of the currently selected cell.
534 * Get the index of the currently selected cell.
535
535
536 * @method get_selected_index
536 * @method get_selected_index
537 * @return {Number} The selected cell's numeric index
537 * @return {Number} The selected cell's numeric index
538 */
538 */
539 Notebook.prototype.get_selected_index = function () {
539 Notebook.prototype.get_selected_index = function () {
540 var result = null;
540 var result = null;
541 this.get_cell_elements().filter(function (index) {
541 this.get_cell_elements().filter(function (index) {
542 if ($(this).data("cell").selected === true) {
542 if ($(this).data("cell").selected === true) {
543 result = index;
543 result = index;
544 }
544 }
545 });
545 });
546 return result;
546 return result;
547 };
547 };
548
548
549
549
550 // Cell selection.
550 // Cell selection.
551
551
552 /**
552 /**
553 * Programmatically select a cell.
553 * Programmatically select a cell.
554 *
554 *
555 * @method select
555 * @method select
556 * @param {Number} index A cell's index
556 * @param {Number} index A cell's index
557 * @return {Notebook} This notebook
557 * @return {Notebook} This notebook
558 */
558 */
559 Notebook.prototype.select = function (index) {
559 Notebook.prototype.select = function (index) {
560 if (this.is_valid_cell_index(index)) {
560 if (this.is_valid_cell_index(index)) {
561 var sindex = this.get_selected_index();
561 var sindex = this.get_selected_index();
562 if (sindex !== null && index !== sindex) {
562 if (sindex !== null && index !== sindex) {
563 // If we are about to select a different cell, make sure we are
563 // If we are about to select a different cell, make sure we are
564 // first in command mode.
564 // first in command mode.
565 if (this.mode !== 'command') {
565 if (this.mode !== 'command') {
566 this.command_mode();
566 this.command_mode();
567 }
567 }
568 this.get_cell(sindex).unselect();
568 this.get_cell(sindex).unselect();
569 }
569 }
570 var cell = this.get_cell(index);
570 var cell = this.get_cell(index);
571 cell.select();
571 cell.select();
572 if (cell.cell_type === 'heading') {
572 if (cell.cell_type === 'heading') {
573 this.events.trigger('selected_cell_type_changed.Notebook',
573 this.events.trigger('selected_cell_type_changed.Notebook',
574 {'cell_type':cell.cell_type,level:cell.level}
574 {'cell_type':cell.cell_type,level:cell.level}
575 );
575 );
576 } else {
576 } else {
577 this.events.trigger('selected_cell_type_changed.Notebook',
577 this.events.trigger('selected_cell_type_changed.Notebook',
578 {'cell_type':cell.cell_type}
578 {'cell_type':cell.cell_type}
579 );
579 );
580 }
580 }
581 }
581 }
582 return this;
582 return this;
583 };
583 };
584
584
585 /**
585 /**
586 * Programmatically select the next cell.
586 * Programmatically select the next cell.
587 *
587 *
588 * @method select_next
588 * @method select_next
589 * @return {Notebook} This notebook
589 * @return {Notebook} This notebook
590 */
590 */
591 Notebook.prototype.select_next = function () {
591 Notebook.prototype.select_next = function () {
592 var index = this.get_selected_index();
592 var index = this.get_selected_index();
593 this.select(index+1);
593 this.select(index+1);
594 return this;
594 return this;
595 };
595 };
596
596
597 /**
597 /**
598 * Programmatically select the previous cell.
598 * Programmatically select the previous cell.
599 *
599 *
600 * @method select_prev
600 * @method select_prev
601 * @return {Notebook} This notebook
601 * @return {Notebook} This notebook
602 */
602 */
603 Notebook.prototype.select_prev = function () {
603 Notebook.prototype.select_prev = function () {
604 var index = this.get_selected_index();
604 var index = this.get_selected_index();
605 this.select(index-1);
605 this.select(index-1);
606 return this;
606 return this;
607 };
607 };
608
608
609
609
610 // Edit/Command mode
610 // Edit/Command mode
611
611
612 /**
612 /**
613 * Gets the index of the cell that is in edit mode.
613 * Gets the index of the cell that is in edit mode.
614 *
614 *
615 * @method get_edit_index
615 * @method get_edit_index
616 *
616 *
617 * @return index {int}
617 * @return index {int}
618 **/
618 **/
619 Notebook.prototype.get_edit_index = function () {
619 Notebook.prototype.get_edit_index = function () {
620 var result = null;
620 var result = null;
621 this.get_cell_elements().filter(function (index) {
621 this.get_cell_elements().filter(function (index) {
622 if ($(this).data("cell").mode === 'edit') {
622 if ($(this).data("cell").mode === 'edit') {
623 result = index;
623 result = index;
624 }
624 }
625 });
625 });
626 return result;
626 return result;
627 };
627 };
628
628
629 /**
629 /**
630 * Handle when a a cell blurs and the notebook should enter command mode.
630 * Handle when a a cell blurs and the notebook should enter command mode.
631 *
631 *
632 * @method handle_command_mode
632 * @method handle_command_mode
633 * @param [cell] {Cell} Cell to enter command mode on.
633 * @param [cell] {Cell} Cell to enter command mode on.
634 **/
634 **/
635 Notebook.prototype.handle_command_mode = function (cell) {
635 Notebook.prototype.handle_command_mode = function (cell) {
636 if (this.mode !== 'command') {
636 if (this.mode !== 'command') {
637 cell.command_mode();
637 cell.command_mode();
638 this.mode = 'command';
638 this.mode = 'command';
639 this.events.trigger('command_mode.Notebook');
639 this.events.trigger('command_mode.Notebook');
640 this.keyboard_manager.command_mode();
640 this.keyboard_manager.command_mode();
641 }
641 }
642 };
642 };
643
643
644 /**
644 /**
645 * Make the notebook enter command mode.
645 * Make the notebook enter command mode.
646 *
646 *
647 * @method command_mode
647 * @method command_mode
648 **/
648 **/
649 Notebook.prototype.command_mode = function () {
649 Notebook.prototype.command_mode = function () {
650 var cell = this.get_cell(this.get_edit_index());
650 var cell = this.get_cell(this.get_edit_index());
651 if (cell && this.mode !== 'command') {
651 if (cell && this.mode !== 'command') {
652 // We don't call cell.command_mode, but rather call cell.focus_cell()
652 // We don't call cell.command_mode, but rather call cell.focus_cell()
653 // which will blur and CM editor and trigger the call to
653 // which will blur and CM editor and trigger the call to
654 // handle_command_mode.
654 // handle_command_mode.
655 cell.focus_cell();
655 cell.focus_cell();
656 }
656 }
657 };
657 };
658
658
659 /**
659 /**
660 * Handle when a cell fires it's edit_mode event.
660 * Handle when a cell fires it's edit_mode event.
661 *
661 *
662 * @method handle_edit_mode
662 * @method handle_edit_mode
663 * @param [cell] {Cell} Cell to enter edit mode on.
663 * @param [cell] {Cell} Cell to enter edit mode on.
664 **/
664 **/
665 Notebook.prototype.handle_edit_mode = function (cell) {
665 Notebook.prototype.handle_edit_mode = function (cell) {
666 if (cell && this.mode !== 'edit') {
666 if (cell && this.mode !== 'edit') {
667 cell.edit_mode();
667 cell.edit_mode();
668 this.mode = 'edit';
668 this.mode = 'edit';
669 this.events.trigger('edit_mode.Notebook');
669 this.events.trigger('edit_mode.Notebook');
670 this.keyboard_manager.edit_mode();
670 this.keyboard_manager.edit_mode();
671 }
671 }
672 };
672 };
673
673
674 /**
674 /**
675 * Make a cell enter edit mode.
675 * Make a cell enter edit mode.
676 *
676 *
677 * @method edit_mode
677 * @method edit_mode
678 **/
678 **/
679 Notebook.prototype.edit_mode = function () {
679 Notebook.prototype.edit_mode = function () {
680 var cell = this.get_selected_cell();
680 var cell = this.get_selected_cell();
681 if (cell && this.mode !== 'edit') {
681 if (cell && this.mode !== 'edit') {
682 cell.unrender();
682 cell.unrender();
683 cell.focus_editor();
683 cell.focus_editor();
684 }
684 }
685 };
685 };
686
686
687 /**
687 /**
688 * Focus the currently selected cell.
688 * Focus the currently selected cell.
689 *
689 *
690 * @method focus_cell
690 * @method focus_cell
691 **/
691 **/
692 Notebook.prototype.focus_cell = function () {
692 Notebook.prototype.focus_cell = function () {
693 var cell = this.get_selected_cell();
693 var cell = this.get_selected_cell();
694 if (cell === null) {return;} // No cell is selected
694 if (cell === null) {return;} // No cell is selected
695 cell.focus_cell();
695 cell.focus_cell();
696 };
696 };
697
697
698 // Cell movement
698 // Cell movement
699
699
700 /**
700 /**
701 * Move given (or selected) cell up and select it.
701 * Move given (or selected) cell up and select it.
702 *
702 *
703 * @method move_cell_up
703 * @method move_cell_up
704 * @param [index] {integer} cell index
704 * @param [index] {integer} cell index
705 * @return {Notebook} This notebook
705 * @return {Notebook} This notebook
706 **/
706 **/
707 Notebook.prototype.move_cell_up = function (index) {
707 Notebook.prototype.move_cell_up = function (index) {
708 var i = this.index_or_selected(index);
708 var i = this.index_or_selected(index);
709 if (this.is_valid_cell_index(i) && i > 0) {
709 if (this.is_valid_cell_index(i) && i > 0) {
710 var pivot = this.get_cell_element(i-1);
710 var pivot = this.get_cell_element(i-1);
711 var tomove = this.get_cell_element(i);
711 var tomove = this.get_cell_element(i);
712 if (pivot !== null && tomove !== null) {
712 if (pivot !== null && tomove !== null) {
713 tomove.detach();
713 tomove.detach();
714 pivot.before(tomove);
714 pivot.before(tomove);
715 this.select(i-1);
715 this.select(i-1);
716 var cell = this.get_selected_cell();
716 var cell = this.get_selected_cell();
717 cell.focus_cell();
717 cell.focus_cell();
718 }
718 }
719 this.set_dirty(true);
719 this.set_dirty(true);
720 }
720 }
721 return this;
721 return this;
722 };
722 };
723
723
724
724
725 /**
725 /**
726 * Move given (or selected) cell down and select it
726 * Move given (or selected) cell down and select it
727 *
727 *
728 * @method move_cell_down
728 * @method move_cell_down
729 * @param [index] {integer} cell index
729 * @param [index] {integer} cell index
730 * @return {Notebook} This notebook
730 * @return {Notebook} This notebook
731 **/
731 **/
732 Notebook.prototype.move_cell_down = function (index) {
732 Notebook.prototype.move_cell_down = function (index) {
733 var i = this.index_or_selected(index);
733 var i = this.index_or_selected(index);
734 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
734 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
735 var pivot = this.get_cell_element(i+1);
735 var pivot = this.get_cell_element(i+1);
736 var tomove = this.get_cell_element(i);
736 var tomove = this.get_cell_element(i);
737 if (pivot !== null && tomove !== null) {
737 if (pivot !== null && tomove !== null) {
738 tomove.detach();
738 tomove.detach();
739 pivot.after(tomove);
739 pivot.after(tomove);
740 this.select(i+1);
740 this.select(i+1);
741 var cell = this.get_selected_cell();
741 var cell = this.get_selected_cell();
742 cell.focus_cell();
742 cell.focus_cell();
743 }
743 }
744 }
744 }
745 this.set_dirty();
745 this.set_dirty();
746 return this;
746 return this;
747 };
747 };
748
748
749
749
750 // Insertion, deletion.
750 // Insertion, deletion.
751
751
752 /**
752 /**
753 * Delete a cell from the notebook.
753 * Delete a cell from the notebook.
754 *
754 *
755 * @method delete_cell
755 * @method delete_cell
756 * @param [index] A cell's numeric index
756 * @param [index] A cell's numeric index
757 * @return {Notebook} This notebook
757 * @return {Notebook} This notebook
758 */
758 */
759 Notebook.prototype.delete_cell = function (index) {
759 Notebook.prototype.delete_cell = function (index) {
760 var i = this.index_or_selected(index);
760 var i = this.index_or_selected(index);
761 var cell = this.get_selected_cell();
761 var cell = this.get_selected_cell();
762 this.undelete_backup = cell.toJSON();
762 this.undelete_backup = cell.toJSON();
763 $('#undelete_cell').removeClass('disabled');
763 $('#undelete_cell').removeClass('disabled');
764 if (this.is_valid_cell_index(i)) {
764 if (this.is_valid_cell_index(i)) {
765 var old_ncells = this.ncells();
765 var old_ncells = this.ncells();
766 var ce = this.get_cell_element(i);
766 var ce = this.get_cell_element(i);
767 ce.remove();
767 ce.remove();
768 if (i === 0) {
768 if (i === 0) {
769 // Always make sure we have at least one cell.
769 // Always make sure we have at least one cell.
770 if (old_ncells === 1) {
770 if (old_ncells === 1) {
771 this.insert_cell_below('code');
771 this.insert_cell_below('code');
772 }
772 }
773 this.select(0);
773 this.select(0);
774 this.undelete_index = 0;
774 this.undelete_index = 0;
775 this.undelete_below = false;
775 this.undelete_below = false;
776 } else if (i === old_ncells-1 && i !== 0) {
776 } else if (i === old_ncells-1 && i !== 0) {
777 this.select(i-1);
777 this.select(i-1);
778 this.undelete_index = i - 1;
778 this.undelete_index = i - 1;
779 this.undelete_below = true;
779 this.undelete_below = true;
780 } else {
780 } else {
781 this.select(i);
781 this.select(i);
782 this.undelete_index = i;
782 this.undelete_index = i;
783 this.undelete_below = false;
783 this.undelete_below = false;
784 }
784 }
785 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
785 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
786 this.set_dirty(true);
786 this.set_dirty(true);
787 }
787 }
788 return this;
788 return this;
789 };
789 };
790
790
791 /**
791 /**
792 * Restore the most recently deleted cell.
792 * Restore the most recently deleted cell.
793 *
793 *
794 * @method undelete
794 * @method undelete
795 */
795 */
796 Notebook.prototype.undelete_cell = function() {
796 Notebook.prototype.undelete_cell = function() {
797 if (this.undelete_backup !== null && this.undelete_index !== null) {
797 if (this.undelete_backup !== null && this.undelete_index !== null) {
798 var current_index = this.get_selected_index();
798 var current_index = this.get_selected_index();
799 if (this.undelete_index < current_index) {
799 if (this.undelete_index < current_index) {
800 current_index = current_index + 1;
800 current_index = current_index + 1;
801 }
801 }
802 if (this.undelete_index >= this.ncells()) {
802 if (this.undelete_index >= this.ncells()) {
803 this.select(this.ncells() - 1);
803 this.select(this.ncells() - 1);
804 }
804 }
805 else {
805 else {
806 this.select(this.undelete_index);
806 this.select(this.undelete_index);
807 }
807 }
808 var cell_data = this.undelete_backup;
808 var cell_data = this.undelete_backup;
809 var new_cell = null;
809 var new_cell = null;
810 if (this.undelete_below) {
810 if (this.undelete_below) {
811 new_cell = this.insert_cell_below(cell_data.cell_type);
811 new_cell = this.insert_cell_below(cell_data.cell_type);
812 } else {
812 } else {
813 new_cell = this.insert_cell_above(cell_data.cell_type);
813 new_cell = this.insert_cell_above(cell_data.cell_type);
814 }
814 }
815 new_cell.fromJSON(cell_data);
815 new_cell.fromJSON(cell_data);
816 if (this.undelete_below) {
816 if (this.undelete_below) {
817 this.select(current_index+1);
817 this.select(current_index+1);
818 } else {
818 } else {
819 this.select(current_index);
819 this.select(current_index);
820 }
820 }
821 this.undelete_backup = null;
821 this.undelete_backup = null;
822 this.undelete_index = null;
822 this.undelete_index = null;
823 }
823 }
824 $('#undelete_cell').addClass('disabled');
824 $('#undelete_cell').addClass('disabled');
825 };
825 };
826
826
827 /**
827 /**
828 * Insert a cell so that after insertion the cell is at given index.
828 * Insert a cell so that after insertion the cell is at given index.
829 *
829 *
830 * If cell type is not provided, it will default to the type of the
830 * If cell type is not provided, it will default to the type of the
831 * currently active cell.
831 * currently active cell.
832 *
832 *
833 * Similar to insert_above, but index parameter is mandatory
833 * Similar to insert_above, but index parameter is mandatory
834 *
834 *
835 * Index will be brought back into the accessible range [0,n]
835 * Index will be brought back into the accessible range [0,n]
836 *
836 *
837 * @method insert_cell_at_index
837 * @method insert_cell_at_index
838 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
838 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
839 * @param [index] {int} a valid index where to insert cell
839 * @param [index] {int} a valid index where to insert cell
840 *
840 *
841 * @return cell {cell|null} created cell or null
841 * @return cell {cell|null} created cell or null
842 **/
842 **/
843 Notebook.prototype.insert_cell_at_index = function(type, index){
843 Notebook.prototype.insert_cell_at_index = function(type, index){
844
844
845 var ncells = this.ncells();
845 var ncells = this.ncells();
846 index = Math.min(index,ncells);
846 index = Math.min(index,ncells);
847 index = Math.max(index,0);
847 index = Math.max(index,0);
848 var cell = null;
848 var cell = null;
849 type = type || this.get_selected_cell().cell_type;
849 type = type || this.get_selected_cell().cell_type;
850
850
851 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
851 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
852 var cell_options = {
852 var cell_options = {
853 events: this.events,
853 events: this.events,
854 config: this.config,
854 config: this.config,
855 keyboard_manager: this.keyboard_manager,
855 keyboard_manager: this.keyboard_manager,
856 notebook: this,
856 notebook: this,
857 tooltip: this.tooltip,
857 tooltip: this.tooltip,
858 };
858 };
859 if (type === 'code') {
859 if (type === 'code') {
860 cell = new codecell.CodeCell(this.kernel, cell_options);
860 cell = new codecell.CodeCell(this.kernel, cell_options);
861 cell.set_input_prompt();
861 cell.set_input_prompt();
862 } else if (type === 'markdown') {
862 } else if (type === 'markdown') {
863 cell = new textcell.MarkdownCell(cell_options);
863 cell = new textcell.MarkdownCell(cell_options);
864 } else if (type === 'raw') {
864 } else if (type === 'raw') {
865 cell = new textcell.RawCell(cell_options);
865 cell = new textcell.RawCell(cell_options);
866 } else if (type === 'heading') {
866 } else if (type === 'heading') {
867 cell = new textcell.HeadingCell(cell_options);
867 cell = new textcell.HeadingCell(cell_options);
868 }
868 }
869
869
870 if(this._insert_element_at_index(cell.element,index)) {
870 if(this._insert_element_at_index(cell.element,index)) {
871 cell.render();
871 cell.render();
872 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
872 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
873 cell.refresh();
873 cell.refresh();
874 // We used to select the cell after we refresh it, but there
874 // We used to select the cell after we refresh it, but there
875 // are now cases were this method is called where select is
875 // are now cases were this method is called where select is
876 // not appropriate. The selection logic should be handled by the
876 // not appropriate. The selection logic should be handled by the
877 // caller of the the top level insert_cell methods.
877 // caller of the the top level insert_cell methods.
878 this.set_dirty(true);
878 this.set_dirty(true);
879 }
879 }
880 }
880 }
881 return cell;
881 return cell;
882
882
883 };
883 };
884
884
885 /**
885 /**
886 * Insert an element at given cell index.
886 * Insert an element at given cell index.
887 *
887 *
888 * @method _insert_element_at_index
888 * @method _insert_element_at_index
889 * @param element {dom element} a cell element
889 * @param element {dom element} a cell element
890 * @param [index] {int} a valid index where to inser cell
890 * @param [index] {int} a valid index where to inser cell
891 * @private
891 * @private
892 *
892 *
893 * return true if everything whent fine.
893 * return true if everything whent fine.
894 **/
894 **/
895 Notebook.prototype._insert_element_at_index = function(element, index){
895 Notebook.prototype._insert_element_at_index = function(element, index){
896 if (element === undefined){
896 if (element === undefined){
897 return false;
897 return false;
898 }
898 }
899
899
900 var ncells = this.ncells();
900 var ncells = this.ncells();
901
901
902 if (ncells === 0) {
902 if (ncells === 0) {
903 // special case append if empty
903 // special case append if empty
904 this.element.find('div.end_space').before(element);
904 this.element.find('div.end_space').before(element);
905 } else if ( ncells === index ) {
905 } else if ( ncells === index ) {
906 // special case append it the end, but not empty
906 // special case append it the end, but not empty
907 this.get_cell_element(index-1).after(element);
907 this.get_cell_element(index-1).after(element);
908 } else if (this.is_valid_cell_index(index)) {
908 } else if (this.is_valid_cell_index(index)) {
909 // otherwise always somewhere to append to
909 // otherwise always somewhere to append to
910 this.get_cell_element(index).before(element);
910 this.get_cell_element(index).before(element);
911 } else {
911 } else {
912 return false;
912 return false;
913 }
913 }
914
914
915 if (this.undelete_index !== null && index <= this.undelete_index) {
915 if (this.undelete_index !== null && index <= this.undelete_index) {
916 this.undelete_index = this.undelete_index + 1;
916 this.undelete_index = this.undelete_index + 1;
917 this.set_dirty(true);
917 this.set_dirty(true);
918 }
918 }
919 return true;
919 return true;
920 };
920 };
921
921
922 /**
922 /**
923 * Insert a cell of given type above given index, or at top
923 * Insert a cell of given type above given index, or at top
924 * of notebook if index smaller than 0.
924 * of notebook if index smaller than 0.
925 *
925 *
926 * default index value is the one of currently selected cell
926 * default index value is the one of currently selected cell
927 *
927 *
928 * @method insert_cell_above
928 * @method insert_cell_above
929 * @param [type] {string} cell type
929 * @param [type] {string} cell type
930 * @param [index] {integer}
930 * @param [index] {integer}
931 *
931 *
932 * @return handle to created cell or null
932 * @return handle to created cell or null
933 **/
933 **/
934 Notebook.prototype.insert_cell_above = function (type, index) {
934 Notebook.prototype.insert_cell_above = function (type, index) {
935 index = this.index_or_selected(index);
935 index = this.index_or_selected(index);
936 return this.insert_cell_at_index(type, index);
936 return this.insert_cell_at_index(type, index);
937 };
937 };
938
938
939 /**
939 /**
940 * Insert a cell of given type below given index, or at bottom
940 * Insert a cell of given type below given index, or at bottom
941 * of notebook if index greater than number of cells
941 * of notebook if index greater than number of cells
942 *
942 *
943 * default index value is the one of currently selected cell
943 * default index value is the one of currently selected cell
944 *
944 *
945 * @method insert_cell_below
945 * @method insert_cell_below
946 * @param [type] {string} cell type
946 * @param [type] {string} cell type
947 * @param [index] {integer}
947 * @param [index] {integer}
948 *
948 *
949 * @return handle to created cell or null
949 * @return handle to created cell or null
950 *
950 *
951 **/
951 **/
952 Notebook.prototype.insert_cell_below = function (type, index) {
952 Notebook.prototype.insert_cell_below = function (type, index) {
953 index = this.index_or_selected(index);
953 index = this.index_or_selected(index);
954 return this.insert_cell_at_index(type, index+1);
954 return this.insert_cell_at_index(type, index+1);
955 };
955 };
956
956
957
957
958 /**
958 /**
959 * Insert cell at end of notebook
959 * Insert cell at end of notebook
960 *
960 *
961 * @method insert_cell_at_bottom
961 * @method insert_cell_at_bottom
962 * @param {String} type cell type
962 * @param {String} type cell type
963 *
963 *
964 * @return the added cell; or null
964 * @return the added cell; or null
965 **/
965 **/
966 Notebook.prototype.insert_cell_at_bottom = function (type){
966 Notebook.prototype.insert_cell_at_bottom = function (type){
967 var len = this.ncells();
967 var len = this.ncells();
968 return this.insert_cell_below(type,len-1);
968 return this.insert_cell_below(type,len-1);
969 };
969 };
970
970
971 /**
971 /**
972 * Turn a cell into a code cell.
972 * Turn a cell into a code cell.
973 *
973 *
974 * @method to_code
974 * @method to_code
975 * @param {Number} [index] A cell's index
975 * @param {Number} [index] A cell's index
976 */
976 */
977 Notebook.prototype.to_code = function (index) {
977 Notebook.prototype.to_code = function (index) {
978 var i = this.index_or_selected(index);
978 var i = this.index_or_selected(index);
979 if (this.is_valid_cell_index(i)) {
979 if (this.is_valid_cell_index(i)) {
980 var source_element = this.get_cell_element(i);
980 var source_element = this.get_cell_element(i);
981 var source_cell = source_element.data("cell");
981 var source_cell = source_element.data("cell");
982 if (!(source_cell instanceof codecell.CodeCell)) {
982 if (!(source_cell instanceof codecell.CodeCell)) {
983 var target_cell = this.insert_cell_below('code',i);
983 var target_cell = this.insert_cell_below('code',i);
984 var text = source_cell.get_text();
984 var text = source_cell.get_text();
985 if (text === source_cell.placeholder) {
985 if (text === source_cell.placeholder) {
986 text = '';
986 text = '';
987 }
987 }
988 target_cell.set_text(text);
988 target_cell.set_text(text);
989 // make this value the starting point, so that we can only undo
989 // make this value the starting point, so that we can only undo
990 // to this state, instead of a blank cell
990 // to this state, instead of a blank cell
991 target_cell.code_mirror.clearHistory();
991 target_cell.code_mirror.clearHistory();
992 source_element.remove();
992 source_element.remove();
993 this.select(i);
993 this.select(i);
994 var cursor = source_cell.code_mirror.getCursor();
994 var cursor = source_cell.code_mirror.getCursor();
995 target_cell.code_mirror.setCursor(cursor);
995 target_cell.code_mirror.setCursor(cursor);
996 this.set_dirty(true);
996 this.set_dirty(true);
997 }
997 }
998 }
998 }
999 };
999 };
1000
1000
1001 /**
1001 /**
1002 * Turn a cell into a Markdown cell.
1002 * Turn a cell into a Markdown cell.
1003 *
1003 *
1004 * @method to_markdown
1004 * @method to_markdown
1005 * @param {Number} [index] A cell's index
1005 * @param {Number} [index] A cell's index
1006 */
1006 */
1007 Notebook.prototype.to_markdown = function (index) {
1007 Notebook.prototype.to_markdown = function (index) {
1008 var i = this.index_or_selected(index);
1008 var i = this.index_or_selected(index);
1009 if (this.is_valid_cell_index(i)) {
1009 if (this.is_valid_cell_index(i)) {
1010 var source_element = this.get_cell_element(i);
1010 var source_element = this.get_cell_element(i);
1011 var source_cell = source_element.data("cell");
1011 var source_cell = source_element.data("cell");
1012 if (!(source_cell instanceof textcell.MarkdownCell)) {
1012 if (!(source_cell instanceof textcell.MarkdownCell)) {
1013 var target_cell = this.insert_cell_below('markdown',i);
1013 var target_cell = this.insert_cell_below('markdown',i);
1014 var text = source_cell.get_text();
1014 var text = source_cell.get_text();
1015 if (text === source_cell.placeholder) {
1015 if (text === source_cell.placeholder) {
1016 text = '';
1016 text = '';
1017 }
1017 }
1018 // We must show the editor before setting its contents
1018 // We must show the editor before setting its contents
1019 target_cell.unrender();
1019 target_cell.unrender();
1020 target_cell.set_text(text);
1020 target_cell.set_text(text);
1021 // make this value the starting point, so that we can only undo
1021 // make this value the starting point, so that we can only undo
1022 // to this state, instead of a blank cell
1022 // to this state, instead of a blank cell
1023 target_cell.code_mirror.clearHistory();
1023 target_cell.code_mirror.clearHistory();
1024 source_element.remove();
1024 source_element.remove();
1025 this.select(i);
1025 this.select(i);
1026 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1026 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1027 target_cell.render();
1027 target_cell.render();
1028 }
1028 }
1029 var cursor = source_cell.code_mirror.getCursor();
1029 var cursor = source_cell.code_mirror.getCursor();
1030 target_cell.code_mirror.setCursor(cursor);
1030 target_cell.code_mirror.setCursor(cursor);
1031 this.set_dirty(true);
1031 this.set_dirty(true);
1032 }
1032 }
1033 }
1033 }
1034 };
1034 };
1035
1035
1036 /**
1036 /**
1037 * Turn a cell into a raw text cell.
1037 * Turn a cell into a raw text cell.
1038 *
1038 *
1039 * @method to_raw
1039 * @method to_raw
1040 * @param {Number} [index] A cell's index
1040 * @param {Number} [index] A cell's index
1041 */
1041 */
1042 Notebook.prototype.to_raw = function (index) {
1042 Notebook.prototype.to_raw = function (index) {
1043 var i = this.index_or_selected(index);
1043 var i = this.index_or_selected(index);
1044 if (this.is_valid_cell_index(i)) {
1044 if (this.is_valid_cell_index(i)) {
1045 var source_element = this.get_cell_element(i);
1045 var source_element = this.get_cell_element(i);
1046 var source_cell = source_element.data("cell");
1046 var source_cell = source_element.data("cell");
1047 var target_cell = null;
1047 var target_cell = null;
1048 if (!(source_cell instanceof textcell.RawCell)) {
1048 if (!(source_cell instanceof textcell.RawCell)) {
1049 target_cell = this.insert_cell_below('raw',i);
1049 target_cell = this.insert_cell_below('raw',i);
1050 var text = source_cell.get_text();
1050 var text = source_cell.get_text();
1051 if (text === source_cell.placeholder) {
1051 if (text === source_cell.placeholder) {
1052 text = '';
1052 text = '';
1053 }
1053 }
1054 // We must show the editor before setting its contents
1054 // We must show the editor before setting its contents
1055 target_cell.unrender();
1055 target_cell.unrender();
1056 target_cell.set_text(text);
1056 target_cell.set_text(text);
1057 // make this value the starting point, so that we can only undo
1057 // make this value the starting point, so that we can only undo
1058 // to this state, instead of a blank cell
1058 // to this state, instead of a blank cell
1059 target_cell.code_mirror.clearHistory();
1059 target_cell.code_mirror.clearHistory();
1060 source_element.remove();
1060 source_element.remove();
1061 this.select(i);
1061 this.select(i);
1062 var cursor = source_cell.code_mirror.getCursor();
1062 var cursor = source_cell.code_mirror.getCursor();
1063 target_cell.code_mirror.setCursor(cursor);
1063 target_cell.code_mirror.setCursor(cursor);
1064 this.set_dirty(true);
1064 this.set_dirty(true);
1065 }
1065 }
1066 }
1066 }
1067 };
1067 };
1068
1068
1069 /**
1069 /**
1070 * Turn a cell into a heading cell.
1070 * Turn a cell into a heading cell.
1071 *
1071 *
1072 * @method to_heading
1072 * @method to_heading
1073 * @param {Number} [index] A cell's index
1073 * @param {Number} [index] A cell's index
1074 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1074 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1075 */
1075 */
1076 Notebook.prototype.to_heading = function (index, level) {
1076 Notebook.prototype.to_heading = function (index, level) {
1077 level = level || 1;
1077 level = level || 1;
1078 var i = this.index_or_selected(index);
1078 var i = this.index_or_selected(index);
1079 if (this.is_valid_cell_index(i)) {
1079 if (this.is_valid_cell_index(i)) {
1080 var source_element = this.get_cell_element(i);
1080 var source_element = this.get_cell_element(i);
1081 var source_cell = source_element.data("cell");
1081 var source_cell = source_element.data("cell");
1082 var target_cell = null;
1082 var target_cell = null;
1083 if (source_cell instanceof textcell.HeadingCell) {
1083 if (source_cell instanceof textcell.HeadingCell) {
1084 source_cell.set_level(level);
1084 source_cell.set_level(level);
1085 } else {
1085 } else {
1086 target_cell = this.insert_cell_below('heading',i);
1086 target_cell = this.insert_cell_below('heading',i);
1087 var text = source_cell.get_text();
1087 var text = source_cell.get_text();
1088 if (text === source_cell.placeholder) {
1088 if (text === source_cell.placeholder) {
1089 text = '';
1089 text = '';
1090 }
1090 }
1091 // We must show the editor before setting its contents
1091 // We must show the editor before setting its contents
1092 target_cell.set_level(level);
1092 target_cell.set_level(level);
1093 target_cell.unrender();
1093 target_cell.unrender();
1094 target_cell.set_text(text);
1094 target_cell.set_text(text);
1095 // make this value the starting point, so that we can only undo
1095 // make this value the starting point, so that we can only undo
1096 // to this state, instead of a blank cell
1096 // to this state, instead of a blank cell
1097 target_cell.code_mirror.clearHistory();
1097 target_cell.code_mirror.clearHistory();
1098 source_element.remove();
1098 source_element.remove();
1099 this.select(i);
1099 this.select(i);
1100 var cursor = source_cell.code_mirror.getCursor();
1100 var cursor = source_cell.code_mirror.getCursor();
1101 target_cell.code_mirror.setCursor(cursor);
1101 target_cell.code_mirror.setCursor(cursor);
1102 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1102 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1103 target_cell.render();
1103 target_cell.render();
1104 }
1104 }
1105 }
1105 }
1106 this.set_dirty(true);
1106 this.set_dirty(true);
1107 this.events.trigger('selected_cell_type_changed.Notebook',
1107 this.events.trigger('selected_cell_type_changed.Notebook',
1108 {'cell_type':'heading',level:level}
1108 {'cell_type':'heading',level:level}
1109 );
1109 );
1110 }
1110 }
1111 };
1111 };
1112
1112
1113
1113
1114 // Cut/Copy/Paste
1114 // Cut/Copy/Paste
1115
1115
1116 /**
1116 /**
1117 * Enable UI elements for pasting cells.
1117 * Enable UI elements for pasting cells.
1118 *
1118 *
1119 * @method enable_paste
1119 * @method enable_paste
1120 */
1120 */
1121 Notebook.prototype.enable_paste = function () {
1121 Notebook.prototype.enable_paste = function () {
1122 var that = this;
1122 var that = this;
1123 if (!this.paste_enabled) {
1123 if (!this.paste_enabled) {
1124 $('#paste_cell_replace').removeClass('disabled')
1124 $('#paste_cell_replace').removeClass('disabled')
1125 .on('click', function () {that.paste_cell_replace();});
1125 .on('click', function () {that.paste_cell_replace();});
1126 $('#paste_cell_above').removeClass('disabled')
1126 $('#paste_cell_above').removeClass('disabled')
1127 .on('click', function () {that.paste_cell_above();});
1127 .on('click', function () {that.paste_cell_above();});
1128 $('#paste_cell_below').removeClass('disabled')
1128 $('#paste_cell_below').removeClass('disabled')
1129 .on('click', function () {that.paste_cell_below();});
1129 .on('click', function () {that.paste_cell_below();});
1130 this.paste_enabled = true;
1130 this.paste_enabled = true;
1131 }
1131 }
1132 };
1132 };
1133
1133
1134 /**
1134 /**
1135 * Disable UI elements for pasting cells.
1135 * Disable UI elements for pasting cells.
1136 *
1136 *
1137 * @method disable_paste
1137 * @method disable_paste
1138 */
1138 */
1139 Notebook.prototype.disable_paste = function () {
1139 Notebook.prototype.disable_paste = function () {
1140 if (this.paste_enabled) {
1140 if (this.paste_enabled) {
1141 $('#paste_cell_replace').addClass('disabled').off('click');
1141 $('#paste_cell_replace').addClass('disabled').off('click');
1142 $('#paste_cell_above').addClass('disabled').off('click');
1142 $('#paste_cell_above').addClass('disabled').off('click');
1143 $('#paste_cell_below').addClass('disabled').off('click');
1143 $('#paste_cell_below').addClass('disabled').off('click');
1144 this.paste_enabled = false;
1144 this.paste_enabled = false;
1145 }
1145 }
1146 };
1146 };
1147
1147
1148 /**
1148 /**
1149 * Cut a cell.
1149 * Cut a cell.
1150 *
1150 *
1151 * @method cut_cell
1151 * @method cut_cell
1152 */
1152 */
1153 Notebook.prototype.cut_cell = function () {
1153 Notebook.prototype.cut_cell = function () {
1154 this.copy_cell();
1154 this.copy_cell();
1155 this.delete_cell();
1155 this.delete_cell();
1156 };
1156 };
1157
1157
1158 /**
1158 /**
1159 * Copy a cell.
1159 * Copy a cell.
1160 *
1160 *
1161 * @method copy_cell
1161 * @method copy_cell
1162 */
1162 */
1163 Notebook.prototype.copy_cell = function () {
1163 Notebook.prototype.copy_cell = function () {
1164 var cell = this.get_selected_cell();
1164 var cell = this.get_selected_cell();
1165 this.clipboard = cell.toJSON();
1165 this.clipboard = cell.toJSON();
1166 this.enable_paste();
1166 this.enable_paste();
1167 };
1167 };
1168
1168
1169 /**
1169 /**
1170 * Replace the selected cell with a cell in the clipboard.
1170 * Replace the selected cell with a cell in the clipboard.
1171 *
1171 *
1172 * @method paste_cell_replace
1172 * @method paste_cell_replace
1173 */
1173 */
1174 Notebook.prototype.paste_cell_replace = function () {
1174 Notebook.prototype.paste_cell_replace = function () {
1175 if (this.clipboard !== null && this.paste_enabled) {
1175 if (this.clipboard !== null && this.paste_enabled) {
1176 var cell_data = this.clipboard;
1176 var cell_data = this.clipboard;
1177 var new_cell = this.insert_cell_above(cell_data.cell_type);
1177 var new_cell = this.insert_cell_above(cell_data.cell_type);
1178 new_cell.fromJSON(cell_data);
1178 new_cell.fromJSON(cell_data);
1179 var old_cell = this.get_next_cell(new_cell);
1179 var old_cell = this.get_next_cell(new_cell);
1180 this.delete_cell(this.find_cell_index(old_cell));
1180 this.delete_cell(this.find_cell_index(old_cell));
1181 this.select(this.find_cell_index(new_cell));
1181 this.select(this.find_cell_index(new_cell));
1182 }
1182 }
1183 };
1183 };
1184
1184
1185 /**
1185 /**
1186 * Paste a cell from the clipboard above the selected cell.
1186 * Paste a cell from the clipboard above the selected cell.
1187 *
1187 *
1188 * @method paste_cell_above
1188 * @method paste_cell_above
1189 */
1189 */
1190 Notebook.prototype.paste_cell_above = function () {
1190 Notebook.prototype.paste_cell_above = function () {
1191 if (this.clipboard !== null && this.paste_enabled) {
1191 if (this.clipboard !== null && this.paste_enabled) {
1192 var cell_data = this.clipboard;
1192 var cell_data = this.clipboard;
1193 var new_cell = this.insert_cell_above(cell_data.cell_type);
1193 var new_cell = this.insert_cell_above(cell_data.cell_type);
1194 new_cell.fromJSON(cell_data);
1194 new_cell.fromJSON(cell_data);
1195 new_cell.focus_cell();
1195 new_cell.focus_cell();
1196 }
1196 }
1197 };
1197 };
1198
1198
1199 /**
1199 /**
1200 * Paste a cell from the clipboard below the selected cell.
1200 * Paste a cell from the clipboard below the selected cell.
1201 *
1201 *
1202 * @method paste_cell_below
1202 * @method paste_cell_below
1203 */
1203 */
1204 Notebook.prototype.paste_cell_below = function () {
1204 Notebook.prototype.paste_cell_below = function () {
1205 if (this.clipboard !== null && this.paste_enabled) {
1205 if (this.clipboard !== null && this.paste_enabled) {
1206 var cell_data = this.clipboard;
1206 var cell_data = this.clipboard;
1207 var new_cell = this.insert_cell_below(cell_data.cell_type);
1207 var new_cell = this.insert_cell_below(cell_data.cell_type);
1208 new_cell.fromJSON(cell_data);
1208 new_cell.fromJSON(cell_data);
1209 new_cell.focus_cell();
1209 new_cell.focus_cell();
1210 }
1210 }
1211 };
1211 };
1212
1212
1213 // Split/merge
1213 // Split/merge
1214
1214
1215 /**
1215 /**
1216 * Split the selected cell into two, at the cursor.
1216 * Split the selected cell into two, at the cursor.
1217 *
1217 *
1218 * @method split_cell
1218 * @method split_cell
1219 */
1219 */
1220 Notebook.prototype.split_cell = function () {
1220 Notebook.prototype.split_cell = function () {
1221 var mdc = textcell.MarkdownCell;
1221 var mdc = textcell.MarkdownCell;
1222 var rc = textcell.RawCell;
1222 var rc = textcell.RawCell;
1223 var cell = this.get_selected_cell();
1223 var cell = this.get_selected_cell();
1224 if (cell.is_splittable()) {
1224 if (cell.is_splittable()) {
1225 var texta = cell.get_pre_cursor();
1225 var texta = cell.get_pre_cursor();
1226 var textb = cell.get_post_cursor();
1226 var textb = cell.get_post_cursor();
1227 cell.set_text(textb);
1227 cell.set_text(textb);
1228 var new_cell = this.insert_cell_above(cell.cell_type);
1228 var new_cell = this.insert_cell_above(cell.cell_type);
1229 // Unrender the new cell so we can call set_text.
1229 // Unrender the new cell so we can call set_text.
1230 new_cell.unrender();
1230 new_cell.unrender();
1231 new_cell.set_text(texta);
1231 new_cell.set_text(texta);
1232 }
1232 }
1233 };
1233 };
1234
1234
1235 /**
1235 /**
1236 * Combine the selected cell into the cell above it.
1236 * Combine the selected cell into the cell above it.
1237 *
1237 *
1238 * @method merge_cell_above
1238 * @method merge_cell_above
1239 */
1239 */
1240 Notebook.prototype.merge_cell_above = function () {
1240 Notebook.prototype.merge_cell_above = function () {
1241 var mdc = textcell.MarkdownCell;
1241 var mdc = textcell.MarkdownCell;
1242 var rc = textcell.RawCell;
1242 var rc = textcell.RawCell;
1243 var index = this.get_selected_index();
1243 var index = this.get_selected_index();
1244 var cell = this.get_cell(index);
1244 var cell = this.get_cell(index);
1245 var render = cell.rendered;
1245 var render = cell.rendered;
1246 if (!cell.is_mergeable()) {
1246 if (!cell.is_mergeable()) {
1247 return;
1247 return;
1248 }
1248 }
1249 if (index > 0) {
1249 if (index > 0) {
1250 var upper_cell = this.get_cell(index-1);
1250 var upper_cell = this.get_cell(index-1);
1251 if (!upper_cell.is_mergeable()) {
1251 if (!upper_cell.is_mergeable()) {
1252 return;
1252 return;
1253 }
1253 }
1254 var upper_text = upper_cell.get_text();
1254 var upper_text = upper_cell.get_text();
1255 var text = cell.get_text();
1255 var text = cell.get_text();
1256 if (cell instanceof codecell.CodeCell) {
1256 if (cell instanceof codecell.CodeCell) {
1257 cell.set_text(upper_text+'\n'+text);
1257 cell.set_text(upper_text+'\n'+text);
1258 } else {
1258 } else {
1259 cell.unrender(); // Must unrender before we set_text.
1259 cell.unrender(); // Must unrender before we set_text.
1260 cell.set_text(upper_text+'\n\n'+text);
1260 cell.set_text(upper_text+'\n\n'+text);
1261 if (render) {
1261 if (render) {
1262 // The rendered state of the final cell should match
1262 // The rendered state of the final cell should match
1263 // that of the original selected cell;
1263 // that of the original selected cell;
1264 cell.render();
1264 cell.render();
1265 }
1265 }
1266 }
1266 }
1267 this.delete_cell(index-1);
1267 this.delete_cell(index-1);
1268 this.select(this.find_cell_index(cell));
1268 this.select(this.find_cell_index(cell));
1269 }
1269 }
1270 };
1270 };
1271
1271
1272 /**
1272 /**
1273 * Combine the selected cell into the cell below it.
1273 * Combine the selected cell into the cell below it.
1274 *
1274 *
1275 * @method merge_cell_below
1275 * @method merge_cell_below
1276 */
1276 */
1277 Notebook.prototype.merge_cell_below = function () {
1277 Notebook.prototype.merge_cell_below = function () {
1278 var mdc = textcell.MarkdownCell;
1278 var mdc = textcell.MarkdownCell;
1279 var rc = textcell.RawCell;
1279 var rc = textcell.RawCell;
1280 var index = this.get_selected_index();
1280 var index = this.get_selected_index();
1281 var cell = this.get_cell(index);
1281 var cell = this.get_cell(index);
1282 var render = cell.rendered;
1282 var render = cell.rendered;
1283 if (!cell.is_mergeable()) {
1283 if (!cell.is_mergeable()) {
1284 return;
1284 return;
1285 }
1285 }
1286 if (index < this.ncells()-1) {
1286 if (index < this.ncells()-1) {
1287 var lower_cell = this.get_cell(index+1);
1287 var lower_cell = this.get_cell(index+1);
1288 if (!lower_cell.is_mergeable()) {
1288 if (!lower_cell.is_mergeable()) {
1289 return;
1289 return;
1290 }
1290 }
1291 var lower_text = lower_cell.get_text();
1291 var lower_text = lower_cell.get_text();
1292 var text = cell.get_text();
1292 var text = cell.get_text();
1293 if (cell instanceof codecell.CodeCell) {
1293 if (cell instanceof codecell.CodeCell) {
1294 cell.set_text(text+'\n'+lower_text);
1294 cell.set_text(text+'\n'+lower_text);
1295 } else {
1295 } else {
1296 cell.unrender(); // Must unrender before we set_text.
1296 cell.unrender(); // Must unrender before we set_text.
1297 cell.set_text(text+'\n\n'+lower_text);
1297 cell.set_text(text+'\n\n'+lower_text);
1298 if (render) {
1298 if (render) {
1299 // The rendered state of the final cell should match
1299 // The rendered state of the final cell should match
1300 // that of the original selected cell;
1300 // that of the original selected cell;
1301 cell.render();
1301 cell.render();
1302 }
1302 }
1303 }
1303 }
1304 this.delete_cell(index+1);
1304 this.delete_cell(index+1);
1305 this.select(this.find_cell_index(cell));
1305 this.select(this.find_cell_index(cell));
1306 }
1306 }
1307 };
1307 };
1308
1308
1309
1309
1310 // Cell collapsing and output clearing
1310 // Cell collapsing and output clearing
1311
1311
1312 /**
1312 /**
1313 * Hide a cell's output.
1313 * Hide a cell's output.
1314 *
1314 *
1315 * @method collapse_output
1315 * @method collapse_output
1316 * @param {Number} index A cell's numeric index
1316 * @param {Number} index A cell's numeric index
1317 */
1317 */
1318 Notebook.prototype.collapse_output = function (index) {
1318 Notebook.prototype.collapse_output = function (index) {
1319 var i = this.index_or_selected(index);
1319 var i = this.index_or_selected(index);
1320 var cell = this.get_cell(i);
1320 var cell = this.get_cell(i);
1321 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1321 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1322 cell.collapse_output();
1322 cell.collapse_output();
1323 this.set_dirty(true);
1323 this.set_dirty(true);
1324 }
1324 }
1325 };
1325 };
1326
1326
1327 /**
1327 /**
1328 * Hide each code cell's output area.
1328 * Hide each code cell's output area.
1329 *
1329 *
1330 * @method collapse_all_output
1330 * @method collapse_all_output
1331 */
1331 */
1332 Notebook.prototype.collapse_all_output = function () {
1332 Notebook.prototype.collapse_all_output = function () {
1333 $.map(this.get_cells(), function (cell, i) {
1333 $.map(this.get_cells(), function (cell, i) {
1334 if (cell instanceof codecell.CodeCell) {
1334 if (cell instanceof codecell.CodeCell) {
1335 cell.collapse_output();
1335 cell.collapse_output();
1336 }
1336 }
1337 });
1337 });
1338 // this should not be set if the `collapse` key is removed from nbformat
1338 // this should not be set if the `collapse` key is removed from nbformat
1339 this.set_dirty(true);
1339 this.set_dirty(true);
1340 };
1340 };
1341
1341
1342 /**
1342 /**
1343 * Show a cell's output.
1343 * Show a cell's output.
1344 *
1344 *
1345 * @method expand_output
1345 * @method expand_output
1346 * @param {Number} index A cell's numeric index
1346 * @param {Number} index A cell's numeric index
1347 */
1347 */
1348 Notebook.prototype.expand_output = function (index) {
1348 Notebook.prototype.expand_output = function (index) {
1349 var i = this.index_or_selected(index);
1349 var i = this.index_or_selected(index);
1350 var cell = this.get_cell(i);
1350 var cell = this.get_cell(i);
1351 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1351 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1352 cell.expand_output();
1352 cell.expand_output();
1353 this.set_dirty(true);
1353 this.set_dirty(true);
1354 }
1354 }
1355 };
1355 };
1356
1356
1357 /**
1357 /**
1358 * Expand each code cell's output area, and remove scrollbars.
1358 * Expand each code cell's output area, and remove scrollbars.
1359 *
1359 *
1360 * @method expand_all_output
1360 * @method expand_all_output
1361 */
1361 */
1362 Notebook.prototype.expand_all_output = function () {
1362 Notebook.prototype.expand_all_output = function () {
1363 $.map(this.get_cells(), function (cell, i) {
1363 $.map(this.get_cells(), function (cell, i) {
1364 if (cell instanceof codecell.CodeCell) {
1364 if (cell instanceof codecell.CodeCell) {
1365 cell.expand_output();
1365 cell.expand_output();
1366 }
1366 }
1367 });
1367 });
1368 // this should not be set if the `collapse` key is removed from nbformat
1368 // this should not be set if the `collapse` key is removed from nbformat
1369 this.set_dirty(true);
1369 this.set_dirty(true);
1370 };
1370 };
1371
1371
1372 /**
1372 /**
1373 * Clear the selected CodeCell's output area.
1373 * Clear the selected CodeCell's output area.
1374 *
1374 *
1375 * @method clear_output
1375 * @method clear_output
1376 * @param {Number} index A cell's numeric index
1376 * @param {Number} index A cell's numeric index
1377 */
1377 */
1378 Notebook.prototype.clear_output = function (index) {
1378 Notebook.prototype.clear_output = function (index) {
1379 var i = this.index_or_selected(index);
1379 var i = this.index_or_selected(index);
1380 var cell = this.get_cell(i);
1380 var cell = this.get_cell(i);
1381 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1381 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1382 cell.clear_output();
1382 cell.clear_output();
1383 this.set_dirty(true);
1383 this.set_dirty(true);
1384 }
1384 }
1385 };
1385 };
1386
1386
1387 /**
1387 /**
1388 * Clear each code cell's output area.
1388 * Clear each code cell's output area.
1389 *
1389 *
1390 * @method clear_all_output
1390 * @method clear_all_output
1391 */
1391 */
1392 Notebook.prototype.clear_all_output = function () {
1392 Notebook.prototype.clear_all_output = function () {
1393 $.map(this.get_cells(), function (cell, i) {
1393 $.map(this.get_cells(), function (cell, i) {
1394 if (cell instanceof codecell.CodeCell) {
1394 if (cell instanceof codecell.CodeCell) {
1395 cell.clear_output();
1395 cell.clear_output();
1396 }
1396 }
1397 });
1397 });
1398 this.set_dirty(true);
1398 this.set_dirty(true);
1399 };
1399 };
1400
1400
1401 /**
1401 /**
1402 * Scroll the selected CodeCell's output area.
1402 * Scroll the selected CodeCell's output area.
1403 *
1403 *
1404 * @method scroll_output
1404 * @method scroll_output
1405 * @param {Number} index A cell's numeric index
1405 * @param {Number} index A cell's numeric index
1406 */
1406 */
1407 Notebook.prototype.scroll_output = function (index) {
1407 Notebook.prototype.scroll_output = function (index) {
1408 var i = this.index_or_selected(index);
1408 var i = this.index_or_selected(index);
1409 var cell = this.get_cell(i);
1409 var cell = this.get_cell(i);
1410 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1410 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1411 cell.scroll_output();
1411 cell.scroll_output();
1412 this.set_dirty(true);
1412 this.set_dirty(true);
1413 }
1413 }
1414 };
1414 };
1415
1415
1416 /**
1416 /**
1417 * Expand each code cell's output area, and add a scrollbar for long output.
1417 * Expand each code cell's output area, and add a scrollbar for long output.
1418 *
1418 *
1419 * @method scroll_all_output
1419 * @method scroll_all_output
1420 */
1420 */
1421 Notebook.prototype.scroll_all_output = function () {
1421 Notebook.prototype.scroll_all_output = function () {
1422 $.map(this.get_cells(), function (cell, i) {
1422 $.map(this.get_cells(), function (cell, i) {
1423 if (cell instanceof codecell.CodeCell) {
1423 if (cell instanceof codecell.CodeCell) {
1424 cell.scroll_output();
1424 cell.scroll_output();
1425 }
1425 }
1426 });
1426 });
1427 // this should not be set if the `collapse` key is removed from nbformat
1427 // this should not be set if the `collapse` key is removed from nbformat
1428 this.set_dirty(true);
1428 this.set_dirty(true);
1429 };
1429 };
1430
1430
1431 /** Toggle whether a cell's output is collapsed or expanded.
1431 /** Toggle whether a cell's output is collapsed or expanded.
1432 *
1432 *
1433 * @method toggle_output
1433 * @method toggle_output
1434 * @param {Number} index A cell's numeric index
1434 * @param {Number} index A cell's numeric index
1435 */
1435 */
1436 Notebook.prototype.toggle_output = function (index) {
1436 Notebook.prototype.toggle_output = function (index) {
1437 var i = this.index_or_selected(index);
1437 var i = this.index_or_selected(index);
1438 var cell = this.get_cell(i);
1438 var cell = this.get_cell(i);
1439 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1439 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1440 cell.toggle_output();
1440 cell.toggle_output();
1441 this.set_dirty(true);
1441 this.set_dirty(true);
1442 }
1442 }
1443 };
1443 };
1444
1444
1445 /**
1445 /**
1446 * Hide/show the output of all cells.
1446 * Hide/show the output of all cells.
1447 *
1447 *
1448 * @method toggle_all_output
1448 * @method toggle_all_output
1449 */
1449 */
1450 Notebook.prototype.toggle_all_output = function () {
1450 Notebook.prototype.toggle_all_output = function () {
1451 $.map(this.get_cells(), function (cell, i) {
1451 $.map(this.get_cells(), function (cell, i) {
1452 if (cell instanceof codecell.CodeCell) {
1452 if (cell instanceof codecell.CodeCell) {
1453 cell.toggle_output();
1453 cell.toggle_output();
1454 }
1454 }
1455 });
1455 });
1456 // this should not be set if the `collapse` key is removed from nbformat
1456 // this should not be set if the `collapse` key is removed from nbformat
1457 this.set_dirty(true);
1457 this.set_dirty(true);
1458 };
1458 };
1459
1459
1460 /**
1460 /**
1461 * Toggle a scrollbar for long cell outputs.
1461 * Toggle a scrollbar for long cell outputs.
1462 *
1462 *
1463 * @method toggle_output_scroll
1463 * @method toggle_output_scroll
1464 * @param {Number} index A cell's numeric index
1464 * @param {Number} index A cell's numeric index
1465 */
1465 */
1466 Notebook.prototype.toggle_output_scroll = function (index) {
1466 Notebook.prototype.toggle_output_scroll = function (index) {
1467 var i = this.index_or_selected(index);
1467 var i = this.index_or_selected(index);
1468 var cell = this.get_cell(i);
1468 var cell = this.get_cell(i);
1469 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1469 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1470 cell.toggle_output_scroll();
1470 cell.toggle_output_scroll();
1471 this.set_dirty(true);
1471 this.set_dirty(true);
1472 }
1472 }
1473 };
1473 };
1474
1474
1475 /**
1475 /**
1476 * Toggle the scrolling of long output on all cells.
1476 * Toggle the scrolling of long output on all cells.
1477 *
1477 *
1478 * @method toggle_all_output_scrolling
1478 * @method toggle_all_output_scrolling
1479 */
1479 */
1480 Notebook.prototype.toggle_all_output_scroll = function () {
1480 Notebook.prototype.toggle_all_output_scroll = function () {
1481 $.map(this.get_cells(), function (cell, i) {
1481 $.map(this.get_cells(), function (cell, i) {
1482 if (cell instanceof codecell.CodeCell) {
1482 if (cell instanceof codecell.CodeCell) {
1483 cell.toggle_output_scroll();
1483 cell.toggle_output_scroll();
1484 }
1484 }
1485 });
1485 });
1486 // this should not be set if the `collapse` key is removed from nbformat
1486 // this should not be set if the `collapse` key is removed from nbformat
1487 this.set_dirty(true);
1487 this.set_dirty(true);
1488 };
1488 };
1489
1489
1490 // Other cell functions: line numbers, ...
1490 // Other cell functions: line numbers, ...
1491
1491
1492 /**
1492 /**
1493 * Toggle line numbers in the selected cell's input area.
1493 * Toggle line numbers in the selected cell's input area.
1494 *
1494 *
1495 * @method cell_toggle_line_numbers
1495 * @method cell_toggle_line_numbers
1496 */
1496 */
1497 Notebook.prototype.cell_toggle_line_numbers = function() {
1497 Notebook.prototype.cell_toggle_line_numbers = function() {
1498 this.get_selected_cell().toggle_line_numbers();
1498 this.get_selected_cell().toggle_line_numbers();
1499 };
1499 };
1500
1500
1501 /**
1501 /**
1502 * Set the codemirror mode for all code cells, including the default for
1502 * Set the codemirror mode for all code cells, including the default for
1503 * new code cells.
1503 * new code cells.
1504 *
1504 *
1505 * @method set_codemirror_mode
1505 * @method set_codemirror_mode
1506 */
1506 */
1507 Notebook.prototype.set_codemirror_mode = function(newmode){
1507 Notebook.prototype.set_codemirror_mode = function(newmode){
1508 if (newmode === this.codemirror_mode) {
1508 if (newmode === this.codemirror_mode) {
1509 return;
1509 return;
1510 }
1510 }
1511 this.codemirror_mode = newmode;
1511 this.codemirror_mode = newmode;
1512 codecell.CodeCell.options_default.cm_config.mode = newmode;
1512 codecell.CodeCell.options_default.cm_config.mode = newmode;
1513 modename = newmode.name || newmode
1513 modename = newmode.name || newmode
1514
1514
1515 that = this;
1515 that = this;
1516 CodeMirror.requireMode(modename, function(){
1516 CodeMirror.requireMode(modename, function(){
1517 $.map(that.get_cells(), function(cell, i) {
1517 $.map(that.get_cells(), function(cell, i) {
1518 if (cell.cell_type === 'code'){
1518 if (cell.cell_type === 'code'){
1519 cell.code_mirror.setOption('mode', newmode);
1519 cell.code_mirror.setOption('mode', newmode);
1520 // This is currently redundant, because cm_config ends up as
1520 // This is currently redundant, because cm_config ends up as
1521 // codemirror's own .options object, but I don't want to
1521 // codemirror's own .options object, but I don't want to
1522 // rely on that.
1522 // rely on that.
1523 cell.cm_config.mode = newmode;
1523 cell.cm_config.mode = newmode;
1524 }
1524 }
1525 });
1525 });
1526 })
1526 })
1527 };
1527 };
1528
1528
1529 // Session related things
1529 // Session related things
1530
1530
1531 /**
1531 /**
1532 * Start a new session and set it on each code cell.
1532 * Start a new session and set it on each code cell.
1533 *
1533 *
1534 * @method start_session
1534 * @method start_session
1535 */
1535 */
1536 Notebook.prototype.start_session = function (kernel_name) {
1536 Notebook.prototype.start_session = function (kernel_name) {
1537 if (kernel_name === undefined) {
1537 if (kernel_name === undefined) {
1538 kernel_name = this.default_kernel_name;
1538 kernel_name = this.default_kernel_name;
1539 }
1539 }
1540 this.session = new session.Session({
1540 this.session = new session.Session({
1541 base_url: this.base_url,
1541 base_url: this.base_url,
1542 ws_url: this.ws_url,
1542 ws_url: this.ws_url,
1543 notebook_path: this.notebook_path,
1543 notebook_path: this.notebook_path,
1544 notebook_name: this.notebook_name,
1544 notebook_name: this.notebook_name,
1545 // For now, create all sessions with the 'python' kernel, which is the
1545 // For now, create all sessions with the 'python' kernel, which is the
1546 // default. Later, the user will be able to select kernels. This is
1546 // default. Later, the user will be able to select kernels. This is
1547 // overridden if KernelManager.kernel_cmd is specified for the server.
1547 // overridden if KernelManager.kernel_cmd is specified for the server.
1548 kernel_name: kernel_name,
1548 kernel_name: kernel_name,
1549 notebook: this});
1549 notebook: this});
1550
1550
1551 this.session.start($.proxy(this._session_started, this));
1551 this.session.start($.proxy(this._session_started, this));
1552 };
1552 };
1553
1553
1554
1554
1555 /**
1555 /**
1556 * Once a session is started, link the code cells to the kernel and pass the
1556 * Once a session is started, link the code cells to the kernel and pass the
1557 * comm manager to the widget manager
1557 * comm manager to the widget manager
1558 *
1558 *
1559 */
1559 */
1560 Notebook.prototype._session_started = function(){
1560 Notebook.prototype._session_started = function(){
1561 this.kernel = this.session.kernel;
1561 this.kernel = this.session.kernel;
1562 var ncells = this.ncells();
1562 var ncells = this.ncells();
1563 for (var i=0; i<ncells; i++) {
1563 for (var i=0; i<ncells; i++) {
1564 var cell = this.get_cell(i);
1564 var cell = this.get_cell(i);
1565 if (cell instanceof codecell.CodeCell) {
1565 if (cell instanceof codecell.CodeCell) {
1566 cell.set_kernel(this.session.kernel);
1566 cell.set_kernel(this.session.kernel);
1567 }
1567 }
1568 }
1568 }
1569 };
1569 };
1570
1570
1571 /**
1571 /**
1572 * Prompt the user to restart the IPython kernel.
1572 * Prompt the user to restart the IPython kernel.
1573 *
1573 *
1574 * @method restart_kernel
1574 * @method restart_kernel
1575 */
1575 */
1576 Notebook.prototype.restart_kernel = function () {
1576 Notebook.prototype.restart_kernel = function () {
1577 var that = this;
1577 var that = this;
1578 dialog.modal({
1578 dialog.modal({
1579 notebook: this,
1579 notebook: this,
1580 keyboard_manager: this.keyboard_manager,
1580 keyboard_manager: this.keyboard_manager,
1581 title : "Restart kernel or continue running?",
1581 title : "Restart kernel or continue running?",
1582 body : $("<p/>").text(
1582 body : $("<p/>").text(
1583 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1583 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1584 ),
1584 ),
1585 buttons : {
1585 buttons : {
1586 "Continue running" : {},
1586 "Continue running" : {},
1587 "Restart" : {
1587 "Restart" : {
1588 "class" : "btn-danger",
1588 "class" : "btn-danger",
1589 "click" : function() {
1589 "click" : function() {
1590 that.session.restart_kernel();
1590 that.session.restart_kernel();
1591 }
1591 }
1592 }
1592 }
1593 }
1593 }
1594 });
1594 });
1595 };
1595 };
1596
1596
1597 /**
1597 /**
1598 * Execute or render cell outputs and go into command mode.
1598 * Execute or render cell outputs and go into command mode.
1599 *
1599 *
1600 * @method execute_cell
1600 * @method execute_cell
1601 */
1601 */
1602 Notebook.prototype.execute_cell = function () {
1602 Notebook.prototype.execute_cell = function () {
1603 // mode = shift, ctrl, alt
1603 // mode = shift, ctrl, alt
1604 var cell = this.get_selected_cell();
1604 var cell = this.get_selected_cell();
1605 var cell_index = this.find_cell_index(cell);
1605 var cell_index = this.find_cell_index(cell);
1606
1606
1607 cell.execute();
1607 cell.execute();
1608 this.command_mode();
1608 this.command_mode();
1609 this.set_dirty(true);
1609 this.set_dirty(true);
1610 };
1610 };
1611
1611
1612 /**
1612 /**
1613 * Execute or render cell outputs and insert a new cell below.
1613 * Execute or render cell outputs and insert a new cell below.
1614 *
1614 *
1615 * @method execute_cell_and_insert_below
1615 * @method execute_cell_and_insert_below
1616 */
1616 */
1617 Notebook.prototype.execute_cell_and_insert_below = function () {
1617 Notebook.prototype.execute_cell_and_insert_below = function () {
1618 var cell = this.get_selected_cell();
1618 var cell = this.get_selected_cell();
1619 var cell_index = this.find_cell_index(cell);
1619 var cell_index = this.find_cell_index(cell);
1620
1620
1621 cell.execute();
1621 cell.execute();
1622
1622
1623 // If we are at the end always insert a new cell and return
1623 // If we are at the end always insert a new cell and return
1624 if (cell_index === (this.ncells()-1)) {
1624 if (cell_index === (this.ncells()-1)) {
1625 this.command_mode();
1625 this.command_mode();
1626 this.insert_cell_below();
1626 this.insert_cell_below();
1627 this.select(cell_index+1);
1627 this.select(cell_index+1);
1628 this.edit_mode();
1628 this.edit_mode();
1629 this.scroll_to_bottom();
1629 this.scroll_to_bottom();
1630 this.set_dirty(true);
1630 this.set_dirty(true);
1631 return;
1631 return;
1632 }
1632 }
1633
1633
1634 this.command_mode();
1634 this.command_mode();
1635 this.insert_cell_below();
1635 this.insert_cell_below();
1636 this.select(cell_index+1);
1636 this.select(cell_index+1);
1637 this.edit_mode();
1637 this.edit_mode();
1638 this.set_dirty(true);
1638 this.set_dirty(true);
1639 };
1639 };
1640
1640
1641 /**
1641 /**
1642 * Execute or render cell outputs and select the next cell.
1642 * Execute or render cell outputs and select the next cell.
1643 *
1643 *
1644 * @method execute_cell_and_select_below
1644 * @method execute_cell_and_select_below
1645 */
1645 */
1646 Notebook.prototype.execute_cell_and_select_below = function () {
1646 Notebook.prototype.execute_cell_and_select_below = function () {
1647
1647
1648 var cell = this.get_selected_cell();
1648 var cell = this.get_selected_cell();
1649 var cell_index = this.find_cell_index(cell);
1649 var cell_index = this.find_cell_index(cell);
1650
1650
1651 cell.execute();
1651 cell.execute();
1652
1652
1653 // If we are at the end always insert a new cell and return
1653 // If we are at the end always insert a new cell and return
1654 if (cell_index === (this.ncells()-1)) {
1654 if (cell_index === (this.ncells()-1)) {
1655 this.command_mode();
1655 this.command_mode();
1656 this.insert_cell_below();
1656 this.insert_cell_below();
1657 this.select(cell_index+1);
1657 this.select(cell_index+1);
1658 this.edit_mode();
1658 this.edit_mode();
1659 this.scroll_to_bottom();
1659 this.scroll_to_bottom();
1660 this.set_dirty(true);
1660 this.set_dirty(true);
1661 return;
1661 return;
1662 }
1662 }
1663
1663
1664 this.command_mode();
1664 this.command_mode();
1665 this.select(cell_index+1);
1665 this.select(cell_index+1);
1666 this.focus_cell();
1666 this.focus_cell();
1667 this.set_dirty(true);
1667 this.set_dirty(true);
1668 };
1668 };
1669
1669
1670 /**
1670 /**
1671 * Execute all cells below the selected cell.
1671 * Execute all cells below the selected cell.
1672 *
1672 *
1673 * @method execute_cells_below
1673 * @method execute_cells_below
1674 */
1674 */
1675 Notebook.prototype.execute_cells_below = function () {
1675 Notebook.prototype.execute_cells_below = function () {
1676 this.execute_cell_range(this.get_selected_index(), this.ncells());
1676 this.execute_cell_range(this.get_selected_index(), this.ncells());
1677 this.scroll_to_bottom();
1677 this.scroll_to_bottom();
1678 };
1678 };
1679
1679
1680 /**
1680 /**
1681 * Execute all cells above the selected cell.
1681 * Execute all cells above the selected cell.
1682 *
1682 *
1683 * @method execute_cells_above
1683 * @method execute_cells_above
1684 */
1684 */
1685 Notebook.prototype.execute_cells_above = function () {
1685 Notebook.prototype.execute_cells_above = function () {
1686 this.execute_cell_range(0, this.get_selected_index());
1686 this.execute_cell_range(0, this.get_selected_index());
1687 };
1687 };
1688
1688
1689 /**
1689 /**
1690 * Execute all cells.
1690 * Execute all cells.
1691 *
1691 *
1692 * @method execute_all_cells
1692 * @method execute_all_cells
1693 */
1693 */
1694 Notebook.prototype.execute_all_cells = function () {
1694 Notebook.prototype.execute_all_cells = function () {
1695 this.execute_cell_range(0, this.ncells());
1695 this.execute_cell_range(0, this.ncells());
1696 this.scroll_to_bottom();
1696 this.scroll_to_bottom();
1697 };
1697 };
1698
1698
1699 /**
1699 /**
1700 * Execute a contiguous range of cells.
1700 * Execute a contiguous range of cells.
1701 *
1701 *
1702 * @method execute_cell_range
1702 * @method execute_cell_range
1703 * @param {Number} start Index of the first cell to execute (inclusive)
1703 * @param {Number} start Index of the first cell to execute (inclusive)
1704 * @param {Number} end Index of the last cell to execute (exclusive)
1704 * @param {Number} end Index of the last cell to execute (exclusive)
1705 */
1705 */
1706 Notebook.prototype.execute_cell_range = function (start, end) {
1706 Notebook.prototype.execute_cell_range = function (start, end) {
1707 this.command_mode();
1707 this.command_mode();
1708 for (var i=start; i<end; i++) {
1708 for (var i=start; i<end; i++) {
1709 this.select(i);
1709 this.select(i);
1710 this.execute_cell();
1710 this.execute_cell();
1711 }
1711 }
1712 };
1712 };
1713
1713
1714 // Persistance and loading
1714 // Persistance and loading
1715
1715
1716 /**
1716 /**
1717 * Getter method for this notebook's name.
1717 * Getter method for this notebook's name.
1718 *
1718 *
1719 * @method get_notebook_name
1719 * @method get_notebook_name
1720 * @return {String} This notebook's name (excluding file extension)
1720 * @return {String} This notebook's name (excluding file extension)
1721 */
1721 */
1722 Notebook.prototype.get_notebook_name = function () {
1722 Notebook.prototype.get_notebook_name = function () {
1723 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1723 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1724 return nbname;
1724 return nbname;
1725 };
1725 };
1726
1726
1727 /**
1727 /**
1728 * Setter method for this notebook's name.
1728 * Setter method for this notebook's name.
1729 *
1729 *
1730 * @method set_notebook_name
1730 * @method set_notebook_name
1731 * @param {String} name A new name for this notebook
1731 * @param {String} name A new name for this notebook
1732 */
1732 */
1733 Notebook.prototype.set_notebook_name = function (name) {
1733 Notebook.prototype.set_notebook_name = function (name) {
1734 this.notebook_name = name;
1734 this.notebook_name = name;
1735 };
1735 };
1736
1736
1737 /**
1737 /**
1738 * Check that a notebook's name is valid.
1738 * Check that a notebook's name is valid.
1739 *
1739 *
1740 * @method test_notebook_name
1740 * @method test_notebook_name
1741 * @param {String} nbname A name for this notebook
1741 * @param {String} nbname A name for this notebook
1742 * @return {Boolean} True if the name is valid, false if invalid
1742 * @return {Boolean} True if the name is valid, false if invalid
1743 */
1743 */
1744 Notebook.prototype.test_notebook_name = function (nbname) {
1744 Notebook.prototype.test_notebook_name = function (nbname) {
1745 nbname = nbname || '';
1745 nbname = nbname || '';
1746 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1746 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1747 return true;
1747 return true;
1748 } else {
1748 } else {
1749 return false;
1749 return false;
1750 }
1750 }
1751 };
1751 };
1752
1752
1753 /**
1753 /**
1754 * Load a notebook from JSON (.ipynb).
1754 * Load a notebook from JSON (.ipynb).
1755 *
1755 *
1756 * This currently handles one worksheet: others are deleted.
1756 * This currently handles one worksheet: others are deleted.
1757 *
1757 *
1758 * @method fromJSON
1758 * @method fromJSON
1759 * @param {Object} data JSON representation of a notebook
1759 * @param {Object} data JSON representation of a notebook
1760 */
1760 */
1761 Notebook.prototype.fromJSON = function (data) {
1761 Notebook.prototype.fromJSON = function (data) {
1762 var content = data.content;
1762 var content = data.content;
1763 var ncells = this.ncells();
1763 var ncells = this.ncells();
1764 var i;
1764 var i;
1765 for (i=0; i<ncells; i++) {
1765 for (i=0; i<ncells; i++) {
1766 // Always delete cell 0 as they get renumbered as they are deleted.
1766 // Always delete cell 0 as they get renumbered as they are deleted.
1767 this.delete_cell(0);
1767 this.delete_cell(0);
1768 }
1768 }
1769 // Save the metadata and name.
1769 // Save the metadata and name.
1770 this.metadata = content.metadata;
1770 this.metadata = content.metadata;
1771 this.notebook_name = data.name;
1771 this.notebook_name = data.name;
1772 var trusted = true;
1772 var trusted = true;
1773
1773
1774 // Trigger an event changing the kernel spec - this will set the default
1774 // Trigger an event changing the kernel spec - this will set the default
1775 // codemirror mode
1775 // codemirror mode
1776 if (this.metadata.kernelspec !== undefined) {
1776 if (this.metadata.kernelspec !== undefined) {
1777 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1777 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1778 }
1778 }
1779
1779
1780 // Only handle 1 worksheet for now.
1780 // Only handle 1 worksheet for now.
1781 var worksheet = content.worksheets[0];
1781 var worksheet = content.worksheets[0];
1782 if (worksheet !== undefined) {
1782 if (worksheet !== undefined) {
1783 if (worksheet.metadata) {
1783 if (worksheet.metadata) {
1784 this.worksheet_metadata = worksheet.metadata;
1784 this.worksheet_metadata = worksheet.metadata;
1785 }
1785 }
1786 var new_cells = worksheet.cells;
1786 var new_cells = worksheet.cells;
1787 ncells = new_cells.length;
1787 ncells = new_cells.length;
1788 var cell_data = null;
1788 var cell_data = null;
1789 var new_cell = null;
1789 var new_cell = null;
1790 for (i=0; i<ncells; i++) {
1790 for (i=0; i<ncells; i++) {
1791 cell_data = new_cells[i];
1791 cell_data = new_cells[i];
1792 // VERSIONHACK: plaintext -> raw
1792 // VERSIONHACK: plaintext -> raw
1793 // handle never-released plaintext name for raw cells
1793 // handle never-released plaintext name for raw cells
1794 if (cell_data.cell_type === 'plaintext'){
1794 if (cell_data.cell_type === 'plaintext'){
1795 cell_data.cell_type = 'raw';
1795 cell_data.cell_type = 'raw';
1796 }
1796 }
1797
1797
1798 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1798 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1799 new_cell.fromJSON(cell_data);
1799 new_cell.fromJSON(cell_data);
1800 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1800 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1801 trusted = false;
1801 trusted = false;
1802 }
1802 }
1803 }
1803 }
1804 }
1804 }
1805 if (trusted != this.trusted) {
1805 if (trusted != this.trusted) {
1806 this.trusted = trusted;
1806 this.trusted = trusted;
1807 this.events.trigger("trust_changed.Notebook", trusted);
1807 this.events.trigger("trust_changed.Notebook", trusted);
1808 }
1808 }
1809 if (content.worksheets.length > 1) {
1809 if (content.worksheets.length > 1) {
1810 dialog.modal({
1810 dialog.modal({
1811 notebook: this,
1811 notebook: this,
1812 keyboard_manager: this.keyboard_manager,
1812 keyboard_manager: this.keyboard_manager,
1813 title : "Multiple worksheets",
1813 title : "Multiple worksheets",
1814 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1814 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1815 "but this version of IPython can only handle the first. " +
1815 "but this version of IPython can only handle the first. " +
1816 "If you save this notebook, worksheets after the first will be lost.",
1816 "If you save this notebook, worksheets after the first will be lost.",
1817 buttons : {
1817 buttons : {
1818 OK : {
1818 OK : {
1819 class : "btn-danger"
1819 class : "btn-danger"
1820 }
1820 }
1821 }
1821 }
1822 });
1822 });
1823 }
1823 }
1824 };
1824 };
1825
1825
1826 /**
1826 /**
1827 * Dump this notebook into a JSON-friendly object.
1827 * Dump this notebook into a JSON-friendly object.
1828 *
1828 *
1829 * @method toJSON
1829 * @method toJSON
1830 * @return {Object} A JSON-friendly representation of this notebook.
1830 * @return {Object} A JSON-friendly representation of this notebook.
1831 */
1831 */
1832 Notebook.prototype.toJSON = function () {
1832 Notebook.prototype.toJSON = function () {
1833 var cells = this.get_cells();
1833 var cells = this.get_cells();
1834 var ncells = cells.length;
1834 var ncells = cells.length;
1835 var cell_array = new Array(ncells);
1835 var cell_array = new Array(ncells);
1836 var trusted = true;
1836 var trusted = true;
1837 for (var i=0; i<ncells; i++) {
1837 for (var i=0; i<ncells; i++) {
1838 var cell = cells[i];
1838 var cell = cells[i];
1839 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1839 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1840 trusted = false;
1840 trusted = false;
1841 }
1841 }
1842 cell_array[i] = cell.toJSON();
1842 cell_array[i] = cell.toJSON();
1843 }
1843 }
1844 var data = {
1844 var data = {
1845 // Only handle 1 worksheet for now.
1845 // Only handle 1 worksheet for now.
1846 worksheets : [{
1846 worksheets : [{
1847 cells: cell_array,
1847 cells: cell_array,
1848 metadata: this.worksheet_metadata
1848 metadata: this.worksheet_metadata
1849 }],
1849 }],
1850 metadata : this.metadata
1850 metadata : this.metadata
1851 };
1851 };
1852 if (trusted != this.trusted) {
1852 if (trusted != this.trusted) {
1853 this.trusted = trusted;
1853 this.trusted = trusted;
1854 this.events.trigger("trust_changed.Notebook", trusted);
1854 this.events.trigger("trust_changed.Notebook", trusted);
1855 }
1855 }
1856 return data;
1856 return data;
1857 };
1857 };
1858
1858
1859 /**
1859 /**
1860 * Start an autosave timer, for periodically saving the notebook.
1860 * Start an autosave timer, for periodically saving the notebook.
1861 *
1861 *
1862 * @method set_autosave_interval
1862 * @method set_autosave_interval
1863 * @param {Integer} interval the autosave interval in milliseconds
1863 * @param {Integer} interval the autosave interval in milliseconds
1864 */
1864 */
1865 Notebook.prototype.set_autosave_interval = function (interval) {
1865 Notebook.prototype.set_autosave_interval = function (interval) {
1866 var that = this;
1866 var that = this;
1867 // clear previous interval, so we don't get simultaneous timers
1867 // clear previous interval, so we don't get simultaneous timers
1868 if (this.autosave_timer) {
1868 if (this.autosave_timer) {
1869 clearInterval(this.autosave_timer);
1869 clearInterval(this.autosave_timer);
1870 }
1870 }
1871
1871
1872 this.autosave_interval = this.minimum_autosave_interval = interval;
1872 this.autosave_interval = this.minimum_autosave_interval = interval;
1873 if (interval) {
1873 if (interval) {
1874 this.autosave_timer = setInterval(function() {
1874 this.autosave_timer = setInterval(function() {
1875 if (that.dirty) {
1875 if (that.dirty) {
1876 that.save_notebook();
1876 that.save_notebook();
1877 }
1877 }
1878 }, interval);
1878 }, interval);
1879 this.events.trigger("autosave_enabled.Notebook", interval);
1879 this.events.trigger("autosave_enabled.Notebook", interval);
1880 } else {
1880 } else {
1881 this.autosave_timer = null;
1881 this.autosave_timer = null;
1882 this.events.trigger("autosave_disabled.Notebook");
1882 this.events.trigger("autosave_disabled.Notebook");
1883 }
1883 }
1884 };
1884 };
1885
1885
1886 /**
1886 /**
1887 * Save this notebook on the server. This becomes a notebook instance's
1887 * Save this notebook on the server. This becomes a notebook instance's
1888 * .save_notebook method *after* the entire notebook has been loaded.
1888 * .save_notebook method *after* the entire notebook has been loaded.
1889 *
1889 *
1890 * @method save_notebook
1890 * @method save_notebook
1891 */
1891 */
1892 Notebook.prototype.save_notebook = function (extra_settings) {
1892 Notebook.prototype.save_notebook = function (extra_settings) {
1893 // Create a JSON model to be sent to the server.
1893 // Create a JSON model to be sent to the server.
1894 var model = {};
1894 var model = {};
1895 model.name = this.notebook_name;
1895 model.name = this.notebook_name;
1896 model.path = this.notebook_path;
1896 model.path = this.notebook_path;
1897 model.content = this.toJSON();
1897 model.content = this.toJSON();
1898 model.content.nbformat = this.nbformat;
1898 model.content.nbformat = this.nbformat;
1899 model.content.nbformat_minor = this.nbformat_minor;
1899 model.content.nbformat_minor = this.nbformat_minor;
1900 // time the ajax call for autosave tuning purposes.
1900 // time the ajax call for autosave tuning purposes.
1901 var start = new Date().getTime();
1901 var start = new Date().getTime();
1902 // We do the call with settings so we can set cache to false.
1902 // We do the call with settings so we can set cache to false.
1903 var settings = {
1903 var settings = {
1904 processData : false,
1904 processData : false,
1905 cache : false,
1905 cache : false,
1906 type : "PUT",
1906 type : "PUT",
1907 data : JSON.stringify(model),
1907 data : JSON.stringify(model),
1908 headers : {'Content-Type': 'application/json'},
1908 headers : {'Content-Type': 'application/json'},
1909 success : $.proxy(this.save_notebook_success, this, start),
1909 success : $.proxy(this.save_notebook_success, this, start),
1910 error : $.proxy(this.save_notebook_error, this)
1910 error : $.proxy(this.save_notebook_error, this)
1911 };
1911 };
1912 if (extra_settings) {
1912 if (extra_settings) {
1913 for (var key in extra_settings) {
1913 for (var key in extra_settings) {
1914 settings[key] = extra_settings[key];
1914 settings[key] = extra_settings[key];
1915 }
1915 }
1916 }
1916 }
1917 this.events.trigger('notebook_saving.Notebook');
1917 this.events.trigger('notebook_saving.Notebook');
1918 var url = utils.url_join_encode(
1918 var url = utils.url_join_encode(
1919 this.base_url,
1919 this.base_url,
1920 'api/notebooks',
1920 'api/notebooks',
1921 this.notebook_path,
1921 this.notebook_path,
1922 this.notebook_name
1922 this.notebook_name
1923 );
1923 );
1924 $.ajax(url, settings);
1924 $.ajax(url, settings);
1925 };
1925 };
1926
1926
1927 /**
1927 /**
1928 * Success callback for saving a notebook.
1928 * Success callback for saving a notebook.
1929 *
1929 *
1930 * @method save_notebook_success
1930 * @method save_notebook_success
1931 * @param {Integer} start the time when the save request started
1931 * @param {Integer} start the time when the save request started
1932 * @param {Object} data JSON representation of a notebook
1932 * @param {Object} data JSON representation of a notebook
1933 * @param {String} status Description of response status
1933 * @param {String} status Description of response status
1934 * @param {jqXHR} xhr jQuery Ajax object
1934 * @param {jqXHR} xhr jQuery Ajax object
1935 */
1935 */
1936 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1936 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1937 this.set_dirty(false);
1937 this.set_dirty(false);
1938 this.events.trigger('notebook_saved.Notebook');
1938 this.events.trigger('notebook_saved.Notebook');
1939 this._update_autosave_interval(start);
1939 this._update_autosave_interval(start);
1940 if (this._checkpoint_after_save) {
1940 if (this._checkpoint_after_save) {
1941 this.create_checkpoint();
1941 this.create_checkpoint();
1942 this._checkpoint_after_save = false;
1942 this._checkpoint_after_save = false;
1943 }
1943 }
1944 };
1944 };
1945
1945
1946 /**
1946 /**
1947 * update the autosave interval based on how long the last save took
1947 * update the autosave interval based on how long the last save took
1948 *
1948 *
1949 * @method _update_autosave_interval
1949 * @method _update_autosave_interval
1950 * @param {Integer} timestamp when the save request started
1950 * @param {Integer} timestamp when the save request started
1951 */
1951 */
1952 Notebook.prototype._update_autosave_interval = function (start) {
1952 Notebook.prototype._update_autosave_interval = function (start) {
1953 var duration = (new Date().getTime() - start);
1953 var duration = (new Date().getTime() - start);
1954 if (this.autosave_interval) {
1954 if (this.autosave_interval) {
1955 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1955 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1956 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1956 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1957 // round to 10 seconds, otherwise we will be setting a new interval too often
1957 // round to 10 seconds, otherwise we will be setting a new interval too often
1958 interval = 10000 * Math.round(interval / 10000);
1958 interval = 10000 * Math.round(interval / 10000);
1959 // set new interval, if it's changed
1959 // set new interval, if it's changed
1960 if (interval != this.autosave_interval) {
1960 if (interval != this.autosave_interval) {
1961 this.set_autosave_interval(interval);
1961 this.set_autosave_interval(interval);
1962 }
1962 }
1963 }
1963 }
1964 };
1964 };
1965
1965
1966 /**
1966 /**
1967 * Failure callback for saving a notebook.
1967 * Failure callback for saving a notebook.
1968 *
1968 *
1969 * @method save_notebook_error
1969 * @method save_notebook_error
1970 * @param {jqXHR} xhr jQuery Ajax object
1970 * @param {jqXHR} xhr jQuery Ajax object
1971 * @param {String} status Description of response status
1971 * @param {String} status Description of response status
1972 * @param {String} error HTTP error message
1972 * @param {String} error HTTP error message
1973 */
1973 */
1974 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1974 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1975 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1975 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1976 };
1976 };
1977
1977
1978 /**
1978 /**
1979 * Explicitly trust the output of this notebook.
1979 * Explicitly trust the output of this notebook.
1980 *
1980 *
1981 * @method trust_notebook
1981 * @method trust_notebook
1982 */
1982 */
1983 Notebook.prototype.trust_notebook = function (extra_settings) {
1983 Notebook.prototype.trust_notebook = function (extra_settings) {
1984 var body = $("<div>").append($("<p>")
1984 var body = $("<div>").append($("<p>")
1985 .text("A trusted IPython notebook may execute hidden malicious code ")
1985 .text("A trusted IPython notebook may execute hidden malicious code ")
1986 .append($("<strong>")
1986 .append($("<strong>")
1987 .append(
1987 .append(
1988 $("<em>").text("when you open it")
1988 $("<em>").text("when you open it")
1989 )
1989 )
1990 ).append(".").append(
1990 ).append(".").append(
1991 " Selecting trust will immediately reload this notebook in a trusted state."
1991 " Selecting trust will immediately reload this notebook in a trusted state."
1992 ).append(
1992 ).append(
1993 " For more information, see the "
1993 " For more information, see the "
1994 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1994 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1995 .text("IPython security documentation")
1995 .text("IPython security documentation")
1996 ).append(".")
1996 ).append(".")
1997 );
1997 );
1998
1998
1999 var nb = this;
1999 var nb = this;
2000 dialog.modal({
2000 dialog.modal({
2001 notebook: this,
2001 notebook: this,
2002 keyboard_manager: this.keyboard_manager,
2002 keyboard_manager: this.keyboard_manager,
2003 title: "Trust this notebook?",
2003 title: "Trust this notebook?",
2004 body: body,
2004 body: body,
2005
2005
2006 buttons: {
2006 buttons: {
2007 Cancel : {},
2007 Cancel : {},
2008 Trust : {
2008 Trust : {
2009 class : "btn-danger",
2009 class : "btn-danger",
2010 click : function () {
2010 click : function () {
2011 var cells = nb.get_cells();
2011 var cells = nb.get_cells();
2012 for (var i = 0; i < cells.length; i++) {
2012 for (var i = 0; i < cells.length; i++) {
2013 var cell = cells[i];
2013 var cell = cells[i];
2014 if (cell.cell_type == 'code') {
2014 if (cell.cell_type == 'code') {
2015 cell.output_area.trusted = true;
2015 cell.output_area.trusted = true;
2016 }
2016 }
2017 }
2017 }
2018 this.events.on('notebook_saved.Notebook', function () {
2018 this.events.on('notebook_saved.Notebook', function () {
2019 window.location.reload();
2019 window.location.reload();
2020 });
2020 });
2021 nb.save_notebook();
2021 nb.save_notebook();
2022 }
2022 }
2023 }
2023 }
2024 }
2024 }
2025 });
2025 });
2026 };
2026 };
2027
2027
2028 Notebook.prototype.new_notebook = function(){
2028 Notebook.prototype.new_notebook = function(){
2029 var path = this.notebook_path;
2029 var path = this.notebook_path;
2030 var base_url = this.base_url;
2030 var base_url = this.base_url;
2031 var settings = {
2031 var settings = {
2032 processData : false,
2032 processData : false,
2033 cache : false,
2033 cache : false,
2034 type : "POST",
2034 type : "POST",
2035 dataType : "json",
2035 dataType : "json",
2036 async : false,
2036 async : false,
2037 success : function (data, status, xhr){
2037 success : function (data, status, xhr){
2038 var notebook_name = data.name;
2038 var notebook_name = data.name;
2039 window.open(
2039 window.open(
2040 utils.url_join_encode(
2040 utils.url_join_encode(
2041 base_url,
2041 base_url,
2042 'notebooks',
2042 'notebooks',
2043 path,
2043 path,
2044 notebook_name
2044 notebook_name
2045 ),
2045 ),
2046 '_blank'
2046 '_blank'
2047 );
2047 );
2048 },
2048 },
2049 error : utils.log_ajax_error,
2049 error : utils.log_ajax_error,
2050 };
2050 };
2051 var url = utils.url_join_encode(
2051 var url = utils.url_join_encode(
2052 base_url,
2052 base_url,
2053 'api/notebooks',
2053 'api/notebooks',
2054 path
2054 path
2055 );
2055 );
2056 $.ajax(url,settings);
2056 $.ajax(url,settings);
2057 };
2057 };
2058
2058
2059
2059
2060 Notebook.prototype.copy_notebook = function(){
2060 Notebook.prototype.copy_notebook = function(){
2061 var path = this.notebook_path;
2061 var path = this.notebook_path;
2062 var base_url = this.base_url;
2062 var base_url = this.base_url;
2063 var settings = {
2063 var settings = {
2064 processData : false,
2064 processData : false,
2065 cache : false,
2065 cache : false,
2066 type : "POST",
2066 type : "POST",
2067 dataType : "json",
2067 dataType : "json",
2068 data : JSON.stringify({copy_from : this.notebook_name}),
2068 data : JSON.stringify({copy_from : this.notebook_name}),
2069 async : false,
2069 async : false,
2070 success : function (data, status, xhr) {
2070 success : function (data, status, xhr) {
2071 window.open(utils.url_join_encode(
2071 window.open(utils.url_join_encode(
2072 base_url,
2072 base_url,
2073 'notebooks',
2073 'notebooks',
2074 data.path,
2074 data.path,
2075 data.name
2075 data.name
2076 ), '_blank');
2076 ), '_blank');
2077 },
2077 },
2078 error : utils.log_ajax_error,
2078 error : utils.log_ajax_error,
2079 };
2079 };
2080 var url = utils.url_join_encode(
2080 var url = utils.url_join_encode(
2081 base_url,
2081 base_url,
2082 'api/notebooks',
2082 'api/notebooks',
2083 path
2083 path
2084 );
2084 );
2085 $.ajax(url,settings);
2085 $.ajax(url,settings);
2086 };
2086 };
2087
2087
2088 Notebook.prototype.rename = function (nbname) {
2088 Notebook.prototype.rename = function (nbname) {
2089 var that = this;
2089 var that = this;
2090 if (!nbname.match(/\.ipynb$/)) {
2090 if (!nbname.match(/\.ipynb$/)) {
2091 nbname = nbname + ".ipynb";
2091 nbname = nbname + ".ipynb";
2092 }
2092 }
2093 var data = {name: nbname};
2093 var data = {name: nbname};
2094 var settings = {
2094 var settings = {
2095 processData : false,
2095 processData : false,
2096 cache : false,
2096 cache : false,
2097 type : "PATCH",
2097 type : "PATCH",
2098 data : JSON.stringify(data),
2098 data : JSON.stringify(data),
2099 dataType: "json",
2099 dataType: "json",
2100 headers : {'Content-Type': 'application/json'},
2100 headers : {'Content-Type': 'application/json'},
2101 success : $.proxy(that.rename_success, this),
2101 success : $.proxy(that.rename_success, this),
2102 error : $.proxy(that.rename_error, this)
2102 error : $.proxy(that.rename_error, this)
2103 };
2103 };
2104 this.events.trigger('rename_notebook.Notebook', data);
2104 this.events.trigger('rename_notebook.Notebook', data);
2105 var url = utils.url_join_encode(
2105 var url = utils.url_join_encode(
2106 this.base_url,
2106 this.base_url,
2107 'api/notebooks',
2107 'api/notebooks',
2108 this.notebook_path,
2108 this.notebook_path,
2109 this.notebook_name
2109 this.notebook_name
2110 );
2110 );
2111 $.ajax(url, settings);
2111 $.ajax(url, settings);
2112 };
2112 };
2113
2113
2114 Notebook.prototype.delete = function () {
2114 Notebook.prototype.delete = function () {
2115 var that = this;
2115 var that = this;
2116 var settings = {
2116 var settings = {
2117 processData : false,
2117 processData : false,
2118 cache : false,
2118 cache : false,
2119 type : "DELETE",
2119 type : "DELETE",
2120 dataType: "json",
2120 dataType: "json",
2121 error : utils.log_ajax_error,
2121 error : utils.log_ajax_error,
2122 };
2122 };
2123 var url = utils.url_join_encode(
2123 var url = utils.url_join_encode(
2124 this.base_url,
2124 this.base_url,
2125 'api/notebooks',
2125 'api/notebooks',
2126 this.notebook_path,
2126 this.notebook_path,
2127 this.notebook_name
2127 this.notebook_name
2128 );
2128 );
2129 $.ajax(url, settings);
2129 $.ajax(url, settings);
2130 };
2130 };
2131
2131
2132
2132
2133 Notebook.prototype.rename_success = function (json, status, xhr) {
2133 Notebook.prototype.rename_success = function (json, status, xhr) {
2134 var name = this.notebook_name = json.name;
2134 var name = this.notebook_name = json.name;
2135 var path = json.path;
2135 var path = json.path;
2136 this.session.rename_notebook(name, path);
2136 this.session.rename_notebook(name, path);
2137 this.events.trigger('notebook_renamed.Notebook', json);
2137 this.events.trigger('notebook_renamed.Notebook', json);
2138 };
2138 };
2139
2139
2140 Notebook.prototype.rename_error = function (xhr, status, error) {
2140 Notebook.prototype.rename_error = function (xhr, status, error) {
2141 var that = this;
2141 var that = this;
2142 var dialog_body = $('<div/>').append(
2142 var dialog_body = $('<div/>').append(
2143 $("<p/>").addClass("rename-message")
2143 $("<p/>").addClass("rename-message")
2144 .text('This notebook name already exists.')
2144 .text('This notebook name already exists.')
2145 );
2145 );
2146 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2146 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2147 dialog.modal({
2147 dialog.modal({
2148 notebook: this,
2148 notebook: this,
2149 keyboard_manager: this.keyboard_manager,
2149 keyboard_manager: this.keyboard_manager,
2150 title: "Notebook Rename Error!",
2150 title: "Notebook Rename Error!",
2151 body: dialog_body,
2151 body: dialog_body,
2152 buttons : {
2152 buttons : {
2153 "Cancel": {},
2153 "Cancel": {},
2154 "OK": {
2154 "OK": {
2155 class: "btn-primary",
2155 class: "btn-primary",
2156 click: function () {
2156 click: function () {
2157 this.save_widget.rename_notebook({notebook:that});
2157 this.save_widget.rename_notebook({notebook:that});
2158 }}
2158 }}
2159 },
2159 },
2160 open : function (event, ui) {
2160 open : function (event, ui) {
2161 var that = $(this);
2161 var that = $(this);
2162 // Upon ENTER, click the OK button.
2162 // Upon ENTER, click the OK button.
2163 that.find('input[type="text"]').keydown(function (event, ui) {
2163 that.find('input[type="text"]').keydown(function (event, ui) {
2164 if (event.which === this.keyboard.keycodes.enter) {
2164 if (event.which === this.keyboard.keycodes.enter) {
2165 that.find('.btn-primary').first().click();
2165 that.find('.btn-primary').first().click();
2166 }
2166 }
2167 });
2167 });
2168 that.find('input[type="text"]').focus();
2168 that.find('input[type="text"]').focus();
2169 }
2169 }
2170 });
2170 });
2171 };
2171 };
2172
2172
2173 /**
2173 /**
2174 * Request a notebook's data from the server.
2174 * Request a notebook's data from the server.
2175 *
2175 *
2176 * @method load_notebook
2176 * @method load_notebook
2177 * @param {String} notebook_name and path A notebook to load
2177 * @param {String} notebook_name and path A notebook to load
2178 */
2178 */
2179 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2179 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2180 var that = this;
2180 var that = this;
2181 this.notebook_name = notebook_name;
2181 this.notebook_name = notebook_name;
2182 this.notebook_path = notebook_path;
2182 this.notebook_path = notebook_path;
2183 // We do the call with settings so we can set cache to false.
2183 // We do the call with settings so we can set cache to false.
2184 var settings = {
2184 var settings = {
2185 processData : false,
2185 processData : false,
2186 cache : false,
2186 cache : false,
2187 type : "GET",
2187 type : "GET",
2188 dataType : "json",
2188 dataType : "json",
2189 success : $.proxy(this.load_notebook_success,this),
2189 success : $.proxy(this.load_notebook_success,this),
2190 error : $.proxy(this.load_notebook_error,this),
2190 error : $.proxy(this.load_notebook_error,this),
2191 };
2191 };
2192 this.events.trigger('notebook_loading.Notebook');
2192 this.events.trigger('notebook_loading.Notebook');
2193 var url = utils.url_join_encode(
2193 var url = utils.url_join_encode(
2194 this.base_url,
2194 this.base_url,
2195 'api/notebooks',
2195 'api/notebooks',
2196 this.notebook_path,
2196 this.notebook_path,
2197 this.notebook_name
2197 this.notebook_name
2198 );
2198 );
2199 $.ajax(url, settings);
2199 $.ajax(url, settings);
2200 };
2200 };
2201
2201
2202 /**
2202 /**
2203 * Success callback for loading a notebook from the server.
2203 * Success callback for loading a notebook from the server.
2204 *
2204 *
2205 * Load notebook data from the JSON response.
2205 * Load notebook data from the JSON response.
2206 *
2206 *
2207 * @method load_notebook_success
2207 * @method load_notebook_success
2208 * @param {Object} data JSON representation of a notebook
2208 * @param {Object} data JSON representation of a notebook
2209 * @param {String} status Description of response status
2209 * @param {String} status Description of response status
2210 * @param {jqXHR} xhr jQuery Ajax object
2210 * @param {jqXHR} xhr jQuery Ajax object
2211 */
2211 */
2212 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2212 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2213 this.fromJSON(data);
2213 this.fromJSON(data);
2214 if (this.ncells() === 0) {
2214 if (this.ncells() === 0) {
2215 this.insert_cell_below('code');
2215 this.insert_cell_below('code');
2216 this.edit_mode(0);
2216 this.edit_mode(0);
2217 } else {
2217 } else {
2218 this.select(0);
2218 this.select(0);
2219 this.handle_command_mode(this.get_cell(0));
2219 this.handle_command_mode(this.get_cell(0));
2220 }
2220 }
2221 this.set_dirty(false);
2221 this.set_dirty(false);
2222 this.scroll_to_top();
2222 this.scroll_to_top();
2223 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2223 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2224 var msg = "This notebook has been converted from an older " +
2224 var msg = "This notebook has been converted from an older " +
2225 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2225 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2226 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2226 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2227 "newer notebook format will be used and older versions of IPython " +
2227 "newer notebook format will be used and older versions of IPython " +
2228 "may not be able to read it. To keep the older version, close the " +
2228 "may not be able to read it. To keep the older version, close the " +
2229 "notebook without saving it.";
2229 "notebook without saving it.";
2230 dialog.modal({
2230 dialog.modal({
2231 notebook: this,
2231 notebook: this,
2232 keyboard_manager: this.keyboard_manager,
2232 keyboard_manager: this.keyboard_manager,
2233 title : "Notebook converted",
2233 title : "Notebook converted",
2234 body : msg,
2234 body : msg,
2235 buttons : {
2235 buttons : {
2236 OK : {
2236 OK : {
2237 class : "btn-primary"
2237 class : "btn-primary"
2238 }
2238 }
2239 }
2239 }
2240 });
2240 });
2241 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2241 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2242 var that = this;
2242 var that = this;
2243 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2243 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2244 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2244 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2245 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2245 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2246 this_vs + ". You can still work with this notebook, but some features " +
2246 this_vs + ". You can still work with this notebook, but some features " +
2247 "introduced in later notebook versions may not be available.";
2247 "introduced in later notebook versions may not be available.";
2248
2248
2249 dialog.modal({
2249 dialog.modal({
2250 notebook: this,
2250 notebook: this,
2251 keyboard_manager: this.keyboard_manager,
2251 keyboard_manager: this.keyboard_manager,
2252 title : "Newer Notebook",
2252 title : "Newer Notebook",
2253 body : msg,
2253 body : msg,
2254 buttons : {
2254 buttons : {
2255 OK : {
2255 OK : {
2256 class : "btn-danger"
2256 class : "btn-danger"
2257 }
2257 }
2258 }
2258 }
2259 });
2259 });
2260
2260
2261 }
2261 }
2262
2262
2263 // Create the session after the notebook is completely loaded to prevent
2263 // Create the session after the notebook is completely loaded to prevent
2264 // code execution upon loading, which is a security risk.
2264 // code execution upon loading, which is a security risk.
2265 if (this.session === null) {
2265 if (this.session === null) {
2266 var kernelspec = this.metadata.kernelspec || {};
2266 var kernelspec = this.metadata.kernelspec || {};
2267 var kernel_name = kernelspec.name || this.default_kernel_name;
2267 var kernel_name = kernelspec.name || this.default_kernel_name;
2268
2268
2269 this.start_session(kernel_name);
2269 this.start_session(kernel_name);
2270 }
2270 }
2271 // load our checkpoint list
2271 // load our checkpoint list
2272 this.list_checkpoints();
2272 this.list_checkpoints();
2273
2273
2274 // load toolbar state
2274 // load toolbar state
2275 if (this.metadata.celltoolbar) {
2275 if (this.metadata.celltoolbar) {
2276 celltoolbar.CellToolbar.global_show();
2276 celltoolbar.CellToolbar.global_show();
2277 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar, this.events);
2277 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2278 } else {
2278 } else {
2279 celltoolbar.CellToolbar.global_hide();
2279 celltoolbar.CellToolbar.global_hide();
2280 }
2280 }
2281
2281
2282 // now that we're fully loaded, it is safe to restore save functionality
2282 // now that we're fully loaded, it is safe to restore save functionality
2283 delete(this.save_notebook);
2283 delete(this.save_notebook);
2284 this.events.trigger('notebook_loaded.Notebook');
2284 this.events.trigger('notebook_loaded.Notebook');
2285 };
2285 };
2286
2286
2287 /**
2287 /**
2288 * Failure callback for loading a notebook from the server.
2288 * Failure callback for loading a notebook from the server.
2289 *
2289 *
2290 * @method load_notebook_error
2290 * @method load_notebook_error
2291 * @param {jqXHR} xhr jQuery Ajax object
2291 * @param {jqXHR} xhr jQuery Ajax object
2292 * @param {String} status Description of response status
2292 * @param {String} status Description of response status
2293 * @param {String} error HTTP error message
2293 * @param {String} error HTTP error message
2294 */
2294 */
2295 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2295 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2296 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2296 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2297 var msg;
2297 var msg;
2298 if (xhr.status === 400) {
2298 if (xhr.status === 400) {
2299 msg = error;
2299 msg = error;
2300 } else if (xhr.status === 500) {
2300 } else if (xhr.status === 500) {
2301 msg = "An unknown error occurred while loading this notebook. " +
2301 msg = "An unknown error occurred while loading this notebook. " +
2302 "This version can load notebook formats " +
2302 "This version can load notebook formats " +
2303 "v" + this.nbformat + " or earlier.";
2303 "v" + this.nbformat + " or earlier.";
2304 }
2304 }
2305 dialog.modal({
2305 dialog.modal({
2306 notebook: this,
2306 notebook: this,
2307 keyboard_manager: this.keyboard_manager,
2307 keyboard_manager: this.keyboard_manager,
2308 title: "Error loading notebook",
2308 title: "Error loading notebook",
2309 body : msg,
2309 body : msg,
2310 buttons : {
2310 buttons : {
2311 "OK": {}
2311 "OK": {}
2312 }
2312 }
2313 });
2313 });
2314 };
2314 };
2315
2315
2316 /********************* checkpoint-related *********************/
2316 /********************* checkpoint-related *********************/
2317
2317
2318 /**
2318 /**
2319 * Save the notebook then immediately create a checkpoint.
2319 * Save the notebook then immediately create a checkpoint.
2320 *
2320 *
2321 * @method save_checkpoint
2321 * @method save_checkpoint
2322 */
2322 */
2323 Notebook.prototype.save_checkpoint = function () {
2323 Notebook.prototype.save_checkpoint = function () {
2324 this._checkpoint_after_save = true;
2324 this._checkpoint_after_save = true;
2325 this.save_notebook();
2325 this.save_notebook();
2326 };
2326 };
2327
2327
2328 /**
2328 /**
2329 * Add a checkpoint for this notebook.
2329 * Add a checkpoint for this notebook.
2330 * for use as a callback from checkpoint creation.
2330 * for use as a callback from checkpoint creation.
2331 *
2331 *
2332 * @method add_checkpoint
2332 * @method add_checkpoint
2333 */
2333 */
2334 Notebook.prototype.add_checkpoint = function (checkpoint) {
2334 Notebook.prototype.add_checkpoint = function (checkpoint) {
2335 var found = false;
2335 var found = false;
2336 for (var i = 0; i < this.checkpoints.length; i++) {
2336 for (var i = 0; i < this.checkpoints.length; i++) {
2337 var existing = this.checkpoints[i];
2337 var existing = this.checkpoints[i];
2338 if (existing.id == checkpoint.id) {
2338 if (existing.id == checkpoint.id) {
2339 found = true;
2339 found = true;
2340 this.checkpoints[i] = checkpoint;
2340 this.checkpoints[i] = checkpoint;
2341 break;
2341 break;
2342 }
2342 }
2343 }
2343 }
2344 if (!found) {
2344 if (!found) {
2345 this.checkpoints.push(checkpoint);
2345 this.checkpoints.push(checkpoint);
2346 }
2346 }
2347 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2347 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2348 };
2348 };
2349
2349
2350 /**
2350 /**
2351 * List checkpoints for this notebook.
2351 * List checkpoints for this notebook.
2352 *
2352 *
2353 * @method list_checkpoints
2353 * @method list_checkpoints
2354 */
2354 */
2355 Notebook.prototype.list_checkpoints = function () {
2355 Notebook.prototype.list_checkpoints = function () {
2356 var url = utils.url_join_encode(
2356 var url = utils.url_join_encode(
2357 this.base_url,
2357 this.base_url,
2358 'api/notebooks',
2358 'api/notebooks',
2359 this.notebook_path,
2359 this.notebook_path,
2360 this.notebook_name,
2360 this.notebook_name,
2361 'checkpoints'
2361 'checkpoints'
2362 );
2362 );
2363 $.get(url).done(
2363 $.get(url).done(
2364 $.proxy(this.list_checkpoints_success, this)
2364 $.proxy(this.list_checkpoints_success, this)
2365 ).fail(
2365 ).fail(
2366 $.proxy(this.list_checkpoints_error, this)
2366 $.proxy(this.list_checkpoints_error, this)
2367 );
2367 );
2368 };
2368 };
2369
2369
2370 /**
2370 /**
2371 * Success callback for listing checkpoints.
2371 * Success callback for listing checkpoints.
2372 *
2372 *
2373 * @method list_checkpoint_success
2373 * @method list_checkpoint_success
2374 * @param {Object} data JSON representation of a checkpoint
2374 * @param {Object} data JSON representation of a checkpoint
2375 * @param {String} status Description of response status
2375 * @param {String} status Description of response status
2376 * @param {jqXHR} xhr jQuery Ajax object
2376 * @param {jqXHR} xhr jQuery Ajax object
2377 */
2377 */
2378 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2378 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2379 data = $.parseJSON(data);
2379 data = $.parseJSON(data);
2380 this.checkpoints = data;
2380 this.checkpoints = data;
2381 if (data.length) {
2381 if (data.length) {
2382 this.last_checkpoint = data[data.length - 1];
2382 this.last_checkpoint = data[data.length - 1];
2383 } else {
2383 } else {
2384 this.last_checkpoint = null;
2384 this.last_checkpoint = null;
2385 }
2385 }
2386 this.events.trigger('checkpoints_listed.Notebook', [data]);
2386 this.events.trigger('checkpoints_listed.Notebook', [data]);
2387 };
2387 };
2388
2388
2389 /**
2389 /**
2390 * Failure callback for listing a checkpoint.
2390 * Failure callback for listing a checkpoint.
2391 *
2391 *
2392 * @method list_checkpoint_error
2392 * @method list_checkpoint_error
2393 * @param {jqXHR} xhr jQuery Ajax object
2393 * @param {jqXHR} xhr jQuery Ajax object
2394 * @param {String} status Description of response status
2394 * @param {String} status Description of response status
2395 * @param {String} error_msg HTTP error message
2395 * @param {String} error_msg HTTP error message
2396 */
2396 */
2397 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2397 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2398 this.events.trigger('list_checkpoints_failed.Notebook');
2398 this.events.trigger('list_checkpoints_failed.Notebook');
2399 };
2399 };
2400
2400
2401 /**
2401 /**
2402 * Create a checkpoint of this notebook on the server from the most recent save.
2402 * Create a checkpoint of this notebook on the server from the most recent save.
2403 *
2403 *
2404 * @method create_checkpoint
2404 * @method create_checkpoint
2405 */
2405 */
2406 Notebook.prototype.create_checkpoint = function () {
2406 Notebook.prototype.create_checkpoint = function () {
2407 var url = utils.url_join_encode(
2407 var url = utils.url_join_encode(
2408 this.base_url,
2408 this.base_url,
2409 'api/notebooks',
2409 'api/notebooks',
2410 this.notebook_path,
2410 this.notebook_path,
2411 this.notebook_name,
2411 this.notebook_name,
2412 'checkpoints'
2412 'checkpoints'
2413 );
2413 );
2414 $.post(url).done(
2414 $.post(url).done(
2415 $.proxy(this.create_checkpoint_success, this)
2415 $.proxy(this.create_checkpoint_success, this)
2416 ).fail(
2416 ).fail(
2417 $.proxy(this.create_checkpoint_error, this)
2417 $.proxy(this.create_checkpoint_error, this)
2418 );
2418 );
2419 };
2419 };
2420
2420
2421 /**
2421 /**
2422 * Success callback for creating a checkpoint.
2422 * Success callback for creating a checkpoint.
2423 *
2423 *
2424 * @method create_checkpoint_success
2424 * @method create_checkpoint_success
2425 * @param {Object} data JSON representation of a checkpoint
2425 * @param {Object} data JSON representation of a checkpoint
2426 * @param {String} status Description of response status
2426 * @param {String} status Description of response status
2427 * @param {jqXHR} xhr jQuery Ajax object
2427 * @param {jqXHR} xhr jQuery Ajax object
2428 */
2428 */
2429 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2429 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2430 data = $.parseJSON(data);
2430 data = $.parseJSON(data);
2431 this.add_checkpoint(data);
2431 this.add_checkpoint(data);
2432 this.events.trigger('checkpoint_created.Notebook', data);
2432 this.events.trigger('checkpoint_created.Notebook', data);
2433 };
2433 };
2434
2434
2435 /**
2435 /**
2436 * Failure callback for creating a checkpoint.
2436 * Failure callback for creating a checkpoint.
2437 *
2437 *
2438 * @method create_checkpoint_error
2438 * @method create_checkpoint_error
2439 * @param {jqXHR} xhr jQuery Ajax object
2439 * @param {jqXHR} xhr jQuery Ajax object
2440 * @param {String} status Description of response status
2440 * @param {String} status Description of response status
2441 * @param {String} error_msg HTTP error message
2441 * @param {String} error_msg HTTP error message
2442 */
2442 */
2443 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2443 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2444 this.events.trigger('checkpoint_failed.Notebook');
2444 this.events.trigger('checkpoint_failed.Notebook');
2445 };
2445 };
2446
2446
2447 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2447 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2448 var that = this;
2448 var that = this;
2449 checkpoint = checkpoint || this.last_checkpoint;
2449 checkpoint = checkpoint || this.last_checkpoint;
2450 if ( ! checkpoint ) {
2450 if ( ! checkpoint ) {
2451 console.log("restore dialog, but no checkpoint to restore to!");
2451 console.log("restore dialog, but no checkpoint to restore to!");
2452 return;
2452 return;
2453 }
2453 }
2454 var body = $('<div/>').append(
2454 var body = $('<div/>').append(
2455 $('<p/>').addClass("p-space").text(
2455 $('<p/>').addClass("p-space").text(
2456 "Are you sure you want to revert the notebook to " +
2456 "Are you sure you want to revert the notebook to " +
2457 "the latest checkpoint?"
2457 "the latest checkpoint?"
2458 ).append(
2458 ).append(
2459 $("<strong/>").text(
2459 $("<strong/>").text(
2460 " This cannot be undone."
2460 " This cannot be undone."
2461 )
2461 )
2462 )
2462 )
2463 ).append(
2463 ).append(
2464 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2464 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2465 ).append(
2465 ).append(
2466 $('<p/>').addClass("p-space").text(
2466 $('<p/>').addClass("p-space").text(
2467 Date(checkpoint.last_modified)
2467 Date(checkpoint.last_modified)
2468 ).css("text-align", "center")
2468 ).css("text-align", "center")
2469 );
2469 );
2470
2470
2471 dialog.modal({
2471 dialog.modal({
2472 notebook: this,
2472 notebook: this,
2473 keyboard_manager: this.keyboard_manager,
2473 keyboard_manager: this.keyboard_manager,
2474 title : "Revert notebook to checkpoint",
2474 title : "Revert notebook to checkpoint",
2475 body : body,
2475 body : body,
2476 buttons : {
2476 buttons : {
2477 Revert : {
2477 Revert : {
2478 class : "btn-danger",
2478 class : "btn-danger",
2479 click : function () {
2479 click : function () {
2480 that.restore_checkpoint(checkpoint.id);
2480 that.restore_checkpoint(checkpoint.id);
2481 }
2481 }
2482 },
2482 },
2483 Cancel : {}
2483 Cancel : {}
2484 }
2484 }
2485 });
2485 });
2486 };
2486 };
2487
2487
2488 /**
2488 /**
2489 * Restore the notebook to a checkpoint state.
2489 * Restore the notebook to a checkpoint state.
2490 *
2490 *
2491 * @method restore_checkpoint
2491 * @method restore_checkpoint
2492 * @param {String} checkpoint ID
2492 * @param {String} checkpoint ID
2493 */
2493 */
2494 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2494 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2495 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2495 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2496 var url = utils.url_join_encode(
2496 var url = utils.url_join_encode(
2497 this.base_url,
2497 this.base_url,
2498 'api/notebooks',
2498 'api/notebooks',
2499 this.notebook_path,
2499 this.notebook_path,
2500 this.notebook_name,
2500 this.notebook_name,
2501 'checkpoints',
2501 'checkpoints',
2502 checkpoint
2502 checkpoint
2503 );
2503 );
2504 $.post(url).done(
2504 $.post(url).done(
2505 $.proxy(this.restore_checkpoint_success, this)
2505 $.proxy(this.restore_checkpoint_success, this)
2506 ).fail(
2506 ).fail(
2507 $.proxy(this.restore_checkpoint_error, this)
2507 $.proxy(this.restore_checkpoint_error, this)
2508 );
2508 );
2509 };
2509 };
2510
2510
2511 /**
2511 /**
2512 * Success callback for restoring a notebook to a checkpoint.
2512 * Success callback for restoring a notebook to a checkpoint.
2513 *
2513 *
2514 * @method restore_checkpoint_success
2514 * @method restore_checkpoint_success
2515 * @param {Object} data (ignored, should be empty)
2515 * @param {Object} data (ignored, should be empty)
2516 * @param {String} status Description of response status
2516 * @param {String} status Description of response status
2517 * @param {jqXHR} xhr jQuery Ajax object
2517 * @param {jqXHR} xhr jQuery Ajax object
2518 */
2518 */
2519 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2519 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2520 this.events.trigger('checkpoint_restored.Notebook');
2520 this.events.trigger('checkpoint_restored.Notebook');
2521 this.load_notebook(this.notebook_name, this.notebook_path);
2521 this.load_notebook(this.notebook_name, this.notebook_path);
2522 };
2522 };
2523
2523
2524 /**
2524 /**
2525 * Failure callback for restoring a notebook to a checkpoint.
2525 * Failure callback for restoring a notebook to a checkpoint.
2526 *
2526 *
2527 * @method restore_checkpoint_error
2527 * @method restore_checkpoint_error
2528 * @param {jqXHR} xhr jQuery Ajax object
2528 * @param {jqXHR} xhr jQuery Ajax object
2529 * @param {String} status Description of response status
2529 * @param {String} status Description of response status
2530 * @param {String} error_msg HTTP error message
2530 * @param {String} error_msg HTTP error message
2531 */
2531 */
2532 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2532 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2533 this.events.trigger('checkpoint_restore_failed.Notebook');
2533 this.events.trigger('checkpoint_restore_failed.Notebook');
2534 };
2534 };
2535
2535
2536 /**
2536 /**
2537 * Delete a notebook checkpoint.
2537 * Delete a notebook checkpoint.
2538 *
2538 *
2539 * @method delete_checkpoint
2539 * @method delete_checkpoint
2540 * @param {String} checkpoint ID
2540 * @param {String} checkpoint ID
2541 */
2541 */
2542 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2542 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2543 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2543 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2544 var url = utils.url_join_encode(
2544 var url = utils.url_join_encode(
2545 this.base_url,
2545 this.base_url,
2546 'api/notebooks',
2546 'api/notebooks',
2547 this.notebook_path,
2547 this.notebook_path,
2548 this.notebook_name,
2548 this.notebook_name,
2549 'checkpoints',
2549 'checkpoints',
2550 checkpoint
2550 checkpoint
2551 );
2551 );
2552 $.ajax(url, {
2552 $.ajax(url, {
2553 type: 'DELETE',
2553 type: 'DELETE',
2554 success: $.proxy(this.delete_checkpoint_success, this),
2554 success: $.proxy(this.delete_checkpoint_success, this),
2555 error: $.proxy(this.delete_checkpoint_error, this)
2555 error: $.proxy(this.delete_checkpoint_error, this)
2556 });
2556 });
2557 };
2557 };
2558
2558
2559 /**
2559 /**
2560 * Success callback for deleting a notebook checkpoint
2560 * Success callback for deleting a notebook checkpoint
2561 *
2561 *
2562 * @method delete_checkpoint_success
2562 * @method delete_checkpoint_success
2563 * @param {Object} data (ignored, should be empty)
2563 * @param {Object} data (ignored, should be empty)
2564 * @param {String} status Description of response status
2564 * @param {String} status Description of response status
2565 * @param {jqXHR} xhr jQuery Ajax object
2565 * @param {jqXHR} xhr jQuery Ajax object
2566 */
2566 */
2567 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2567 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2568 this.events.trigger('checkpoint_deleted.Notebook', data);
2568 this.events.trigger('checkpoint_deleted.Notebook', data);
2569 this.load_notebook(this.notebook_name, this.notebook_path);
2569 this.load_notebook(this.notebook_name, this.notebook_path);
2570 };
2570 };
2571
2571
2572 /**
2572 /**
2573 * Failure callback for deleting a notebook checkpoint.
2573 * Failure callback for deleting a notebook checkpoint.
2574 *
2574 *
2575 * @method delete_checkpoint_error
2575 * @method delete_checkpoint_error
2576 * @param {jqXHR} xhr jQuery Ajax object
2576 * @param {jqXHR} xhr jQuery Ajax object
2577 * @param {String} status Description of response status
2577 * @param {String} status Description of response status
2578 * @param {String} error_msg HTTP error message
2578 * @param {String} error_msg HTTP error message
2579 */
2579 */
2580 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2580 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2581 this.events.trigger('checkpoint_delete_failed.Notebook');
2581 this.events.trigger('checkpoint_delete_failed.Notebook');
2582 };
2582 };
2583
2583
2584
2584
2585 // For backwards compatability.
2585 // For backwards compatability.
2586 IPython.Notebook = Notebook;
2586 IPython.Notebook = Notebook;
2587
2587
2588 return {'Notebook': Notebook};
2588 return {'Notebook': Notebook};
2589 });
2589 });
@@ -1,447 +1,446
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'notebook/js/cell',
7 'notebook/js/cell',
8 'base/js/security',
8 'base/js/security',
9 'notebook/js/mathjaxutils',
9 'notebook/js/mathjaxutils',
10 'notebook/js/celltoolbar',
10 'notebook/js/celltoolbar',
11 'components/marked/lib/marked',
11 'components/marked/lib/marked',
12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 "use strict";
13 "use strict";
14 var Cell = cell.Cell;
14 var Cell = cell.Cell;
15
15
16 var TextCell = function (options) {
16 var TextCell = function (options) {
17 // Constructor
17 // Constructor
18 //
18 //
19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 // and cell type is 'text' cell start as not redered.
20 // and cell type is 'text' cell start as not redered.
21 //
21 //
22 // Parameters:
22 // Parameters:
23 // options: dictionary
23 // options: dictionary
24 // Dictionary of keyword arguments.
24 // Dictionary of keyword arguments.
25 // events: $(Events) instance
25 // events: $(Events) instance
26 // config: dictionary
26 // config: dictionary
27 // keyboard_manager: KeyboardManager instance
27 // keyboard_manager: KeyboardManager instance
28 // notebook: Notebook instance
28 // notebook: Notebook instance
29 options = options || {};
29 options = options || {};
30
30
31 // in all TextCell/Cell subclasses
31 // in all TextCell/Cell subclasses
32 // do not assign most of members here, just pass it down
32 // do not assign most of members here, just pass it down
33 // in the options dict potentially overwriting what you wish.
33 // in the options dict potentially overwriting what you wish.
34 // they will be assigned in the base class.
34 // they will be assigned in the base class.
35 this.notebook = options.notebook;
35 this.notebook = options.notebook;
36 this.events = options.events;
36 this.events = options.events;
37 this.config = options.config;
37 this.config = options.config;
38
38
39 // we cannot put this as a class key as it has handle to "this".
39 // we cannot put this as a class key as it has handle to "this".
40 var cm_overwrite_options = {
40 var cm_overwrite_options = {
41 onKeyEvent: $.proxy(this.handle_keyevent,this)
41 onKeyEvent: $.proxy(this.handle_keyevent,this)
42 };
42 };
43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
44 Cell.apply(this, [{
44 Cell.apply(this, [{
45 config: config,
45 config: config,
46 keyboard_manager: options.keyboard_manager,
46 keyboard_manager: options.keyboard_manager,
47 events: this.events}]);
47 events: this.events}]);
48
48
49 this.cell_type = this.cell_type || 'text';
49 this.cell_type = this.cell_type || 'text';
50 mathjaxutils = mathjaxutils;
50 mathjaxutils = mathjaxutils;
51 this.rendered = false;
51 this.rendered = false;
52 };
52 };
53
53
54 TextCell.prototype = new Cell();
54 TextCell.prototype = new Cell();
55
55
56 TextCell.options_default = {
56 TextCell.options_default = {
57 cm_config : {
57 cm_config : {
58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
59 mode: 'htmlmixed',
59 mode: 'htmlmixed',
60 lineWrapping : true,
60 lineWrapping : true,
61 }
61 }
62 };
62 };
63
63
64
64
65 /**
65 /**
66 * Create the DOM element of the TextCell
66 * Create the DOM element of the TextCell
67 * @method create_element
67 * @method create_element
68 * @private
68 * @private
69 */
69 */
70 TextCell.prototype.create_element = function () {
70 TextCell.prototype.create_element = function () {
71 Cell.prototype.create_element.apply(this, arguments);
71 Cell.prototype.create_element.apply(this, arguments);
72
72
73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
74 cell.attr('tabindex','2');
74 cell.attr('tabindex','2');
75
75
76 var prompt = $('<div/>').addClass('prompt input_prompt');
76 var prompt = $('<div/>').addClass('prompt input_prompt');
77 cell.append(prompt);
77 cell.append(prompt);
78 var inner_cell = $('<div/>').addClass('inner_cell');
78 var inner_cell = $('<div/>').addClass('inner_cell');
79 this.celltoolbar = new celltoolbar.CellToolbar({
79 this.celltoolbar = new celltoolbar.CellToolbar({
80 cell: this,
80 cell: this,
81 events: this.events,
82 notebook: this.notebook});
81 notebook: this.notebook});
83 inner_cell.append(this.celltoolbar.element);
82 inner_cell.append(this.celltoolbar.element);
84 var input_area = $('<div/>').addClass('input_area');
83 var input_area = $('<div/>').addClass('input_area');
85 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
84 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
86 // The tabindex=-1 makes this div focusable.
85 // The tabindex=-1 makes this div focusable.
87 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
88 addClass('rendered_html').attr('tabindex','-1');
87 addClass('rendered_html').attr('tabindex','-1');
89 inner_cell.append(input_area).append(render_area);
88 inner_cell.append(input_area).append(render_area);
90 cell.append(inner_cell);
89 cell.append(inner_cell);
91 this.element = cell;
90 this.element = cell;
92 };
91 };
93
92
94
93
95 /**
94 /**
96 * Bind the DOM evet to cell actions
95 * Bind the DOM evet to cell actions
97 * Need to be called after TextCell.create_element
96 * Need to be called after TextCell.create_element
98 * @private
97 * @private
99 * @method bind_event
98 * @method bind_event
100 */
99 */
101 TextCell.prototype.bind_events = function () {
100 TextCell.prototype.bind_events = function () {
102 Cell.prototype.bind_events.apply(this);
101 Cell.prototype.bind_events.apply(this);
103 var that = this;
102 var that = this;
104
103
105 this.element.dblclick(function () {
104 this.element.dblclick(function () {
106 if (that.selected === false) {
105 if (that.selected === false) {
107 this.events.trigger('select.Cell', {'cell':that});
106 this.events.trigger('select.Cell', {'cell':that});
108 }
107 }
109 var cont = that.unrender();
108 var cont = that.unrender();
110 if (cont) {
109 if (cont) {
111 that.focus_editor();
110 that.focus_editor();
112 }
111 }
113 });
112 });
114 };
113 };
115
114
116 // Cell level actions
115 // Cell level actions
117
116
118 TextCell.prototype.select = function () {
117 TextCell.prototype.select = function () {
119 var cont = Cell.prototype.select.apply(this);
118 var cont = Cell.prototype.select.apply(this);
120 if (cont) {
119 if (cont) {
121 if (this.mode === 'edit') {
120 if (this.mode === 'edit') {
122 this.code_mirror.refresh();
121 this.code_mirror.refresh();
123 }
122 }
124 }
123 }
125 return cont;
124 return cont;
126 };
125 };
127
126
128 TextCell.prototype.unrender = function () {
127 TextCell.prototype.unrender = function () {
129 if (this.read_only) return;
128 if (this.read_only) return;
130 var cont = Cell.prototype.unrender.apply(this);
129 var cont = Cell.prototype.unrender.apply(this);
131 if (cont) {
130 if (cont) {
132 var text_cell = this.element;
131 var text_cell = this.element;
133 var output = text_cell.find("div.text_cell_render");
132 var output = text_cell.find("div.text_cell_render");
134 output.hide();
133 output.hide();
135 text_cell.find('div.input_area').show();
134 text_cell.find('div.input_area').show();
136 if (this.get_text() === this.placeholder) {
135 if (this.get_text() === this.placeholder) {
137 this.set_text('');
136 this.set_text('');
138 }
137 }
139 this.refresh();
138 this.refresh();
140 }
139 }
141 if (this.celltoolbar.ui_controls_list.length) {
140 if (this.celltoolbar.ui_controls_list.length) {
142 this.celltoolbar.show();
141 this.celltoolbar.show();
143 }
142 }
144 return cont;
143 return cont;
145 };
144 };
146
145
147 TextCell.prototype.execute = function () {
146 TextCell.prototype.execute = function () {
148 this.render();
147 this.render();
149 };
148 };
150
149
151 /**
150 /**
152 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
151 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
153 * @method get_text
152 * @method get_text
154 * @retrun {string} CodeMirror current text value
153 * @retrun {string} CodeMirror current text value
155 */
154 */
156 TextCell.prototype.get_text = function() {
155 TextCell.prototype.get_text = function() {
157 return this.code_mirror.getValue();
156 return this.code_mirror.getValue();
158 };
157 };
159
158
160 /**
159 /**
161 * @param {string} text - Codemiror text value
160 * @param {string} text - Codemiror text value
162 * @see TextCell#get_text
161 * @see TextCell#get_text
163 * @method set_text
162 * @method set_text
164 * */
163 * */
165 TextCell.prototype.set_text = function(text) {
164 TextCell.prototype.set_text = function(text) {
166 this.code_mirror.setValue(text);
165 this.code_mirror.setValue(text);
167 this.code_mirror.refresh();
166 this.code_mirror.refresh();
168 };
167 };
169
168
170 /**
169 /**
171 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
170 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
172 * @method get_rendered
171 * @method get_rendered
173 * */
172 * */
174 TextCell.prototype.get_rendered = function() {
173 TextCell.prototype.get_rendered = function() {
175 return this.element.find('div.text_cell_render').html();
174 return this.element.find('div.text_cell_render').html();
176 };
175 };
177
176
178 /**
177 /**
179 * @method set_rendered
178 * @method set_rendered
180 */
179 */
181 TextCell.prototype.set_rendered = function(text) {
180 TextCell.prototype.set_rendered = function(text) {
182 this.element.find('div.text_cell_render').html(text);
181 this.element.find('div.text_cell_render').html(text);
183 this.celltoolbar.hide();
182 this.celltoolbar.hide();
184 };
183 };
185
184
186
185
187 /**
186 /**
188 * Create Text cell from JSON
187 * Create Text cell from JSON
189 * @param {json} data - JSON serialized text-cell
188 * @param {json} data - JSON serialized text-cell
190 * @method fromJSON
189 * @method fromJSON
191 */
190 */
192 TextCell.prototype.fromJSON = function (data) {
191 TextCell.prototype.fromJSON = function (data) {
193 Cell.prototype.fromJSON.apply(this, arguments);
192 Cell.prototype.fromJSON.apply(this, arguments);
194 if (data.cell_type === this.cell_type) {
193 if (data.cell_type === this.cell_type) {
195 if (data.source !== undefined) {
194 if (data.source !== undefined) {
196 this.set_text(data.source);
195 this.set_text(data.source);
197 // make this value the starting point, so that we can only undo
196 // make this value the starting point, so that we can only undo
198 // to this state, instead of a blank cell
197 // to this state, instead of a blank cell
199 this.code_mirror.clearHistory();
198 this.code_mirror.clearHistory();
200 // TODO: This HTML needs to be treated as potentially dangerous
199 // TODO: This HTML needs to be treated as potentially dangerous
201 // user input and should be handled before set_rendered.
200 // user input and should be handled before set_rendered.
202 this.set_rendered(data.rendered || '');
201 this.set_rendered(data.rendered || '');
203 this.rendered = false;
202 this.rendered = false;
204 this.render();
203 this.render();
205 }
204 }
206 }
205 }
207 };
206 };
208
207
209 /** Generate JSON from cell
208 /** Generate JSON from cell
210 * @return {object} cell data serialised to json
209 * @return {object} cell data serialised to json
211 */
210 */
212 TextCell.prototype.toJSON = function () {
211 TextCell.prototype.toJSON = function () {
213 var data = Cell.prototype.toJSON.apply(this);
212 var data = Cell.prototype.toJSON.apply(this);
214 data.source = this.get_text();
213 data.source = this.get_text();
215 if (data.source == this.placeholder) {
214 if (data.source == this.placeholder) {
216 data.source = "";
215 data.source = "";
217 }
216 }
218 return data;
217 return data;
219 };
218 };
220
219
221
220
222 var MarkdownCell = function (options) {
221 var MarkdownCell = function (options) {
223 // Constructor
222 // Constructor
224 //
223 //
225 // Parameters:
224 // Parameters:
226 // options: dictionary
225 // options: dictionary
227 // Dictionary of keyword arguments.
226 // Dictionary of keyword arguments.
228 // events: $(Events) instance
227 // events: $(Events) instance
229 // config: dictionary
228 // config: dictionary
230 // keyboard_manager: KeyboardManager instance
229 // keyboard_manager: KeyboardManager instance
231 // notebook: Notebook instance
230 // notebook: Notebook instance
232 options = options || {};
231 options = options || {};
233 var config = this.mergeopt(MarkdownCell, options.config);
232 var config = this.mergeopt(MarkdownCell, options.config);
234 TextCell.apply(this, [$.extend({}, options, {config: config})]);
233 TextCell.apply(this, [$.extend({}, options, {config: config})]);
235
234
236 this.cell_type = 'markdown';
235 this.cell_type = 'markdown';
237 };
236 };
238
237
239 MarkdownCell.options_default = {
238 MarkdownCell.options_default = {
240 cm_config: {
239 cm_config: {
241 mode: 'ipythongfm'
240 mode: 'ipythongfm'
242 },
241 },
243 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
242 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
244 };
243 };
245
244
246 MarkdownCell.prototype = new TextCell();
245 MarkdownCell.prototype = new TextCell();
247
246
248 /**
247 /**
249 * @method render
248 * @method render
250 */
249 */
251 MarkdownCell.prototype.render = function () {
250 MarkdownCell.prototype.render = function () {
252 var cont = TextCell.prototype.render.apply(this);
251 var cont = TextCell.prototype.render.apply(this);
253 if (cont) {
252 if (cont) {
254 var text = this.get_text();
253 var text = this.get_text();
255 var math = null;
254 var math = null;
256 if (text === "") { text = this.placeholder; }
255 if (text === "") { text = this.placeholder; }
257 var text_and_math = mathjaxutils.remove_math(text);
256 var text_and_math = mathjaxutils.remove_math(text);
258 text = text_and_math[0];
257 text = text_and_math[0];
259 math = text_and_math[1];
258 math = text_and_math[1];
260 var html = marked.parser(marked.lexer(text));
259 var html = marked.parser(marked.lexer(text));
261 html = mathjaxutils.replace_math(html, math);
260 html = mathjaxutils.replace_math(html, math);
262 html = security.sanitize_html(html);
261 html = security.sanitize_html(html);
263 html = $($.parseHTML(html));
262 html = $($.parseHTML(html));
264 // links in markdown cells should open in new tabs
263 // links in markdown cells should open in new tabs
265 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
264 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
266 this.set_rendered(html);
265 this.set_rendered(html);
267 this.element.find('div.input_area').hide();
266 this.element.find('div.input_area').hide();
268 this.element.find("div.text_cell_render").show();
267 this.element.find("div.text_cell_render").show();
269 this.typeset();
268 this.typeset();
270 }
269 }
271 return cont;
270 return cont;
272 };
271 };
273
272
274
273
275 var RawCell = function (options) {
274 var RawCell = function (options) {
276 // Constructor
275 // Constructor
277 //
276 //
278 // Parameters:
277 // Parameters:
279 // options: dictionary
278 // options: dictionary
280 // Dictionary of keyword arguments.
279 // Dictionary of keyword arguments.
281 // events: $(Events) instance
280 // events: $(Events) instance
282 // config: dictionary
281 // config: dictionary
283 // keyboard_manager: KeyboardManager instance
282 // keyboard_manager: KeyboardManager instance
284 // notebook: Notebook instance
283 // notebook: Notebook instance
285 options = options || {};
284 options = options || {};
286 var config = this.mergeopt(RawCell, options.config);
285 var config = this.mergeopt(RawCell, options.config);
287 TextCell.apply(this, [$.extend({}, options, {config: config})]);
286 TextCell.apply(this, [$.extend({}, options, {config: config})]);
288
287
289 // RawCell should always hide its rendered div
288 // RawCell should always hide its rendered div
290 this.element.find('div.text_cell_render').hide();
289 this.element.find('div.text_cell_render').hide();
291 this.cell_type = 'raw';
290 this.cell_type = 'raw';
292 };
291 };
293
292
294 RawCell.options_default = {
293 RawCell.options_default = {
295 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
294 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
296 "It will not be rendered in the notebook. " +
295 "It will not be rendered in the notebook. " +
297 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
296 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
298 };
297 };
299
298
300 RawCell.prototype = new TextCell();
299 RawCell.prototype = new TextCell();
301
300
302 /** @method bind_events **/
301 /** @method bind_events **/
303 RawCell.prototype.bind_events = function () {
302 RawCell.prototype.bind_events = function () {
304 TextCell.prototype.bind_events.apply(this);
303 TextCell.prototype.bind_events.apply(this);
305 var that = this;
304 var that = this;
306 this.element.focusout(function() {
305 this.element.focusout(function() {
307 that.auto_highlight();
306 that.auto_highlight();
308 that.render();
307 that.render();
309 });
308 });
310
309
311 this.code_mirror.on('focus', function() { that.unrender(); });
310 this.code_mirror.on('focus', function() { that.unrender(); });
312 };
311 };
313
312
314 /**
313 /**
315 * Trigger autodetection of highlight scheme for current cell
314 * Trigger autodetection of highlight scheme for current cell
316 * @method auto_highlight
315 * @method auto_highlight
317 */
316 */
318 RawCell.prototype.auto_highlight = function () {
317 RawCell.prototype.auto_highlight = function () {
319 this._auto_highlight(this.config.raw_cell_highlight);
318 this._auto_highlight(this.config.raw_cell_highlight);
320 };
319 };
321
320
322 /** @method render **/
321 /** @method render **/
323 RawCell.prototype.render = function () {
322 RawCell.prototype.render = function () {
324 var cont = TextCell.prototype.render.apply(this);
323 var cont = TextCell.prototype.render.apply(this);
325 if (cont){
324 if (cont){
326 var text = this.get_text();
325 var text = this.get_text();
327 if (text === "") { text = this.placeholder; }
326 if (text === "") { text = this.placeholder; }
328 this.set_text(text);
327 this.set_text(text);
329 this.element.removeClass('rendered');
328 this.element.removeClass('rendered');
330 }
329 }
331 return cont;
330 return cont;
332 };
331 };
333
332
334
333
335 var HeadingCell = function (options) {
334 var HeadingCell = function (options) {
336 // Constructor
335 // Constructor
337 //
336 //
338 // Parameters:
337 // Parameters:
339 // options: dictionary
338 // options: dictionary
340 // Dictionary of keyword arguments.
339 // Dictionary of keyword arguments.
341 // events: $(Events) instance
340 // events: $(Events) instance
342 // config: dictionary
341 // config: dictionary
343 // keyboard_manager: KeyboardManager instance
342 // keyboard_manager: KeyboardManager instance
344 // notebook: Notebook instance
343 // notebook: Notebook instance
345 options = options || {};
344 options = options || {};
346 var config = this.mergeopt(HeadingCell, options.config);
345 var config = this.mergeopt(HeadingCell, options.config);
347 TextCell.apply(this, [$.extend({}, options, {config: config})]);
346 TextCell.apply(this, [$.extend({}, options, {config: config})]);
348
347
349 this.level = 1;
348 this.level = 1;
350 this.cell_type = 'heading';
349 this.cell_type = 'heading';
351 };
350 };
352
351
353 HeadingCell.options_default = {
352 HeadingCell.options_default = {
354 placeholder: "Type Heading Here"
353 placeholder: "Type Heading Here"
355 };
354 };
356
355
357 HeadingCell.prototype = new TextCell();
356 HeadingCell.prototype = new TextCell();
358
357
359 /** @method fromJSON */
358 /** @method fromJSON */
360 HeadingCell.prototype.fromJSON = function (data) {
359 HeadingCell.prototype.fromJSON = function (data) {
361 if (data.level !== undefined){
360 if (data.level !== undefined){
362 this.level = data.level;
361 this.level = data.level;
363 }
362 }
364 TextCell.prototype.fromJSON.apply(this, arguments);
363 TextCell.prototype.fromJSON.apply(this, arguments);
365 };
364 };
366
365
367
366
368 /** @method toJSON */
367 /** @method toJSON */
369 HeadingCell.prototype.toJSON = function () {
368 HeadingCell.prototype.toJSON = function () {
370 var data = TextCell.prototype.toJSON.apply(this);
369 var data = TextCell.prototype.toJSON.apply(this);
371 data.level = this.get_level();
370 data.level = this.get_level();
372 return data;
371 return data;
373 };
372 };
374
373
375 /**
374 /**
376 * Change heading level of cell, and re-render
375 * Change heading level of cell, and re-render
377 * @method set_level
376 * @method set_level
378 */
377 */
379 HeadingCell.prototype.set_level = function (level) {
378 HeadingCell.prototype.set_level = function (level) {
380 this.level = level;
379 this.level = level;
381 if (this.rendered) {
380 if (this.rendered) {
382 this.rendered = false;
381 this.rendered = false;
383 this.render();
382 this.render();
384 }
383 }
385 };
384 };
386
385
387 /** The depth of header cell, based on html (h1 to h6)
386 /** The depth of header cell, based on html (h1 to h6)
388 * @method get_level
387 * @method get_level
389 * @return {integer} level - for 1 to 6
388 * @return {integer} level - for 1 to 6
390 */
389 */
391 HeadingCell.prototype.get_level = function () {
390 HeadingCell.prototype.get_level = function () {
392 return this.level;
391 return this.level;
393 };
392 };
394
393
395
394
396 HeadingCell.prototype.get_rendered = function () {
395 HeadingCell.prototype.get_rendered = function () {
397 var r = this.element.find("div.text_cell_render");
396 var r = this.element.find("div.text_cell_render");
398 return r.children().first().html();
397 return r.children().first().html();
399 };
398 };
400
399
401 HeadingCell.prototype.render = function () {
400 HeadingCell.prototype.render = function () {
402 var cont = TextCell.prototype.render.apply(this);
401 var cont = TextCell.prototype.render.apply(this);
403 if (cont) {
402 if (cont) {
404 var text = this.get_text();
403 var text = this.get_text();
405 var math = null;
404 var math = null;
406 // Markdown headings must be a single line
405 // Markdown headings must be a single line
407 text = text.replace(/\n/g, ' ');
406 text = text.replace(/\n/g, ' ');
408 if (text === "") { text = this.placeholder; }
407 if (text === "") { text = this.placeholder; }
409 text = new Array(this.level + 1).join("#") + " " + text;
408 text = new Array(this.level + 1).join("#") + " " + text;
410 var text_and_math = mathjaxutils.remove_math(text);
409 var text_and_math = mathjaxutils.remove_math(text);
411 text = text_and_math[0];
410 text = text_and_math[0];
412 math = text_and_math[1];
411 math = text_and_math[1];
413 var html = marked.parser(marked.lexer(text));
412 var html = marked.parser(marked.lexer(text));
414 html = mathjaxutils.replace_math(html, math);
413 html = mathjaxutils.replace_math(html, math);
415 html = security.sanitize_html(html);
414 html = security.sanitize_html(html);
416 var h = $($.parseHTML(html));
415 var h = $($.parseHTML(html));
417 // add id and linkback anchor
416 // add id and linkback anchor
418 var hash = h.text().replace(/ /g, '-');
417 var hash = h.text().replace(/ /g, '-');
419 h.attr('id', hash);
418 h.attr('id', hash);
420 h.append(
419 h.append(
421 $('<a/>')
420 $('<a/>')
422 .addClass('anchor-link')
421 .addClass('anchor-link')
423 .attr('href', '#' + hash)
422 .attr('href', '#' + hash)
424 .text('¶')
423 .text('¶')
425 );
424 );
426 this.set_rendered(h);
425 this.set_rendered(h);
427 this.element.find('div.input_area').hide();
426 this.element.find('div.input_area').hide();
428 this.element.find("div.text_cell_render").show();
427 this.element.find("div.text_cell_render").show();
429 this.typeset();
428 this.typeset();
430 }
429 }
431 return cont;
430 return cont;
432 };
431 };
433
432
434 // Backwards compatability.
433 // Backwards compatability.
435 IPython.TextCell = TextCell;
434 IPython.TextCell = TextCell;
436 IPython.MarkdownCell = MarkdownCell;
435 IPython.MarkdownCell = MarkdownCell;
437 IPython.RawCell = RawCell;
436 IPython.RawCell = RawCell;
438 IPython.HeadingCell = HeadingCell;
437 IPython.HeadingCell = HeadingCell;
439
438
440 var textcell = {
439 var textcell = {
441 'TextCell': TextCell,
440 'TextCell': TextCell,
442 'MarkdownCell': MarkdownCell,
441 'MarkdownCell': MarkdownCell,
443 'RawCell': RawCell,
442 'RawCell': RawCell,
444 'HeadingCell': HeadingCell,
443 'HeadingCell': HeadingCell,
445 };
444 };
446 return textcell;
445 return textcell;
447 });
446 });
General Comments 0
You need to be logged in to leave comments. Login now