##// END OF EJS Templates
Added some nice comments,...
jon -
Show More
@@ -1,564 +1,563 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11 // monkey patch CM to be able to syntax highlight cell magics
12 12 // bug reported upstream,
13 13 // see https://github.com/marijnh/CodeMirror2/issues/670
14 14 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
15 15 CodeMirror.modes.null = function() {
16 16 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
17 17 };
18 18 }
19 19
20 20 CodeMirror.patchedGetMode = function(config, mode){
21 21 var cmmode = CodeMirror.getMode(config, mode);
22 22 if(cmmode.indent === null) {
23 23 console.log('patch mode "' , mode, '" on the fly');
24 24 cmmode.indent = function(){return 0;};
25 25 }
26 26 return cmmode;
27 27 };
28 28 // end monkey patching CodeMirror
29 29
30 /**
31 * The Base `Cell` class from which to inherit
32 * @class Cell
33 **/
34
35 /*
36 * @constructor
37 *
38 * @param {object|undefined} [options]
39 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
40 */
41 30 var Cell = function (options) {
31 // Constructor
32 //
33 // The Base `Cell` class from which to inherit.
34 //
35 // Parameters:
36 // options: dictionary
37 // Dictionary of keyword arguments.
38 // events: $(Events) instance
39 // config: dictionary
40 // keyboard_manager: KeyboardManager instance
42 41 options = options || {};
43 42 this.keyboard_manager = options.keyboard_manager;
44 43 this.events = options.events;
45 44 var config = this.mergeopt(Cell, options.config);
46 45 // superclass default overwrite our default
47 46
48 47 this.placeholder = config.placeholder || '';
49 48 this.read_only = config.cm_config.readOnly;
50 49 this.selected = false;
51 50 this.rendered = false;
52 51 this.mode = 'command';
53 52 this.metadata = {};
54 53 // load this from metadata later ?
55 54 this.user_highlight = 'auto';
56 55 this.cm_config = config.cm_config;
57 56 this.cell_id = utils.uuid();
58 57 this._options = config;
59 58
60 59 // For JS VM engines optimization, attributes should be all set (even
61 60 // to null) in the constructor, and if possible, if different subclass
62 61 // have new attributes with same name, they should be created in the
63 62 // same order. Easiest is to create and set to null in parent class.
64 63
65 64 this.element = null;
66 65 this.cell_type = this.cell_type || null;
67 66 this.code_mirror = null;
68 67
69 68 this.create_element();
70 69 if (this.element !== null) {
71 70 this.element.data("cell", this);
72 71 this.bind_events();
73 72 this.init_classes();
74 73 }
75 74 };
76 75
77 76 Cell.options_default = {
78 77 cm_config : {
79 78 indentUnit : 4,
80 79 readOnly: false,
81 80 theme: "default",
82 81 extraKeys: {
83 82 "Cmd-Right":"goLineRight",
84 83 "End":"goLineRight",
85 84 "Cmd-Left":"goLineLeft"
86 85 }
87 86 }
88 87 };
89 88
90 89 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
91 90 // by disabling drag/drop altogether on Safari
92 91 // https://github.com/marijnh/CodeMirror/issues/332
93 92 if (utils.browser[0] == "Safari") {
94 93 Cell.options_default.cm_config.dragDrop = false;
95 94 }
96 95
97 96 Cell.prototype.mergeopt = function(_class, options, overwrite){
98 97 options = options || {};
99 98 overwrite = overwrite || {};
100 99 return $.extend(true, {}, _class.options_default, options, overwrite);
101 100 };
102 101
103 102 /**
104 103 * Empty. Subclasses must implement create_element.
105 104 * This should contain all the code to create the DOM element in notebook
106 105 * and will be called by Base Class constructor.
107 106 * @method create_element
108 107 */
109 108 Cell.prototype.create_element = function () {
110 109 };
111 110
112 111 Cell.prototype.init_classes = function () {
113 112 // Call after this.element exists to initialize the css classes
114 113 // related to selected, rendered and mode.
115 114 if (this.selected) {
116 115 this.element.addClass('selected');
117 116 } else {
118 117 this.element.addClass('unselected');
119 118 }
120 119 if (this.rendered) {
121 120 this.element.addClass('rendered');
122 121 } else {
123 122 this.element.addClass('unrendered');
124 123 }
125 124 if (this.mode === 'edit') {
126 125 this.element.addClass('edit_mode');
127 126 } else {
128 127 this.element.addClass('command_mode');
129 128 }
130 129 };
131 130
132 131 /**
133 132 * Subclasses can implement override bind_events.
134 133 * Be carefull to call the parent method when overwriting as it fires event.
135 134 * this will be triggerd after create_element in constructor.
136 135 * @method bind_events
137 136 */
138 137 Cell.prototype.bind_events = function () {
139 138 var that = this;
140 139 // We trigger events so that Cell doesn't have to depend on Notebook.
141 140 that.element.click(function (event) {
142 141 if (!that.selected) {
143 142 that.events.trigger('select.Cell', {'cell':that});
144 143 }
145 144 });
146 145 that.element.focusin(function (event) {
147 146 if (!that.selected) {
148 147 that.events.trigger('select.Cell', {'cell':that});
149 148 }
150 149 });
151 150 if (this.code_mirror) {
152 151 this.code_mirror.on("change", function(cm, change) {
153 152 that.events.trigger("set_dirty.Notebook", {value: true});
154 153 });
155 154 }
156 155 if (this.code_mirror) {
157 156 this.code_mirror.on('focus', function(cm, change) {
158 157 that.events.trigger('edit_mode.Cell', {cell: that});
159 158 });
160 159 }
161 160 if (this.code_mirror) {
162 161 this.code_mirror.on('blur', function(cm, change) {
163 162 that.events.trigger('command_mode.Cell', {cell: that});
164 163 });
165 164 }
166 165 };
167 166
168 167 /**
169 168 * This method gets called in CodeMirror's onKeyDown/onKeyPress
170 169 * handlers and is used to provide custom key handling.
171 170 *
172 171 * To have custom handling, subclasses should override this method, but still call it
173 172 * in order to process the Edit mode keyboard shortcuts.
174 173 *
175 174 * @method handle_codemirror_keyevent
176 175 * @param {CodeMirror} editor - The codemirror instance bound to the cell
177 176 * @param {event} event - key press event which either should or should not be handled by CodeMirror
178 177 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
179 178 */
180 179 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
181 180 var that = this;
182 181 var shortcuts = this.keyboard_manager.edit_shortcuts;
183 182
184 183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
185 184 // manager will handle it
186 185 if (shortcuts.handles(event)) { return true; }
187 186
188 187 return false;
189 188 };
190 189
191 190
192 191 /**
193 192 * Triger typsetting of math by mathjax on current cell element
194 193 * @method typeset
195 194 */
196 195 Cell.prototype.typeset = function () {
197 196 if (window.MathJax) {
198 197 var cell_math = this.element.get(0);
199 198 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
200 199 }
201 200 };
202 201
203 202 /**
204 203 * handle cell level logic when a cell is selected
205 204 * @method select
206 205 * @return is the action being taken
207 206 */
208 207 Cell.prototype.select = function () {
209 208 if (!this.selected) {
210 209 this.element.addClass('selected');
211 210 this.element.removeClass('unselected');
212 211 this.selected = true;
213 212 return true;
214 213 } else {
215 214 return false;
216 215 }
217 216 };
218 217
219 218 /**
220 219 * handle cell level logic when a cell is unselected
221 220 * @method unselect
222 221 * @return is the action being taken
223 222 */
224 223 Cell.prototype.unselect = function () {
225 224 if (this.selected) {
226 225 this.element.addClass('unselected');
227 226 this.element.removeClass('selected');
228 227 this.selected = false;
229 228 return true;
230 229 } else {
231 230 return false;
232 231 }
233 232 };
234 233
235 234 /**
236 235 * handle cell level logic when a cell is rendered
237 236 * @method render
238 237 * @return is the action being taken
239 238 */
240 239 Cell.prototype.render = function () {
241 240 if (!this.rendered) {
242 241 this.element.addClass('rendered');
243 242 this.element.removeClass('unrendered');
244 243 this.rendered = true;
245 244 return true;
246 245 } else {
247 246 return false;
248 247 }
249 248 };
250 249
251 250 /**
252 251 * handle cell level logic when a cell is unrendered
253 252 * @method unrender
254 253 * @return is the action being taken
255 254 */
256 255 Cell.prototype.unrender = function () {
257 256 if (this.rendered) {
258 257 this.element.addClass('unrendered');
259 258 this.element.removeClass('rendered');
260 259 this.rendered = false;
261 260 return true;
262 261 } else {
263 262 return false;
264 263 }
265 264 };
266 265
267 266 /**
268 267 * Delegates keyboard shortcut handling to either IPython keyboard
269 268 * manager when in command mode, or CodeMirror when in edit mode
270 269 *
271 270 * @method handle_keyevent
272 271 * @param {CodeMirror} editor - The codemirror instance bound to the cell
273 272 * @param {event} - key event to be handled
274 273 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
275 274 */
276 275 Cell.prototype.handle_keyevent = function (editor, event) {
277 276
278 277 // console.log('CM', this.mode, event.which, event.type)
279 278
280 279 if (this.mode === 'command') {
281 280 return true;
282 281 } else if (this.mode === 'edit') {
283 282 return this.handle_codemirror_keyevent(editor, event);
284 283 }
285 284 };
286 285
287 286 /**
288 287 * @method at_top
289 288 * @return {Boolean}
290 289 */
291 290 Cell.prototype.at_top = function () {
292 291 var cm = this.code_mirror;
293 292 var cursor = cm.getCursor();
294 293 if (cursor.line === 0 && cursor.ch === 0) {
295 294 return true;
296 295 }
297 296 return false;
298 297 };
299 298
300 299 /**
301 300 * @method at_bottom
302 301 * @return {Boolean}
303 302 * */
304 303 Cell.prototype.at_bottom = function () {
305 304 var cm = this.code_mirror;
306 305 var cursor = cm.getCursor();
307 306 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
308 307 return true;
309 308 }
310 309 return false;
311 310 };
312 311
313 312 /**
314 313 * enter the command mode for the cell
315 314 * @method command_mode
316 315 * @return is the action being taken
317 316 */
318 317 Cell.prototype.command_mode = function () {
319 318 if (this.mode !== 'command') {
320 319 this.element.addClass('command_mode');
321 320 this.element.removeClass('edit_mode');
322 321 this.mode = 'command';
323 322 return true;
324 323 } else {
325 324 return false;
326 325 }
327 326 };
328 327
329 328 /**
330 329 * enter the edit mode for the cell
331 330 * @method command_mode
332 331 * @return is the action being taken
333 332 */
334 333 Cell.prototype.edit_mode = function () {
335 334 if (this.mode !== 'edit') {
336 335 this.element.addClass('edit_mode');
337 336 this.element.removeClass('command_mode');
338 337 this.mode = 'edit';
339 338 return true;
340 339 } else {
341 340 return false;
342 341 }
343 342 };
344 343
345 344 /**
346 345 * Focus the cell in the DOM sense
347 346 * @method focus_cell
348 347 */
349 348 Cell.prototype.focus_cell = function () {
350 349 this.element.focus();
351 350 };
352 351
353 352 /**
354 353 * Focus the editor area so a user can type
355 354 *
356 355 * NOTE: If codemirror is focused via a mouse click event, you don't want to
357 356 * call this because it will cause a page jump.
358 357 * @method focus_editor
359 358 */
360 359 Cell.prototype.focus_editor = function () {
361 360 this.refresh();
362 361 this.code_mirror.focus();
363 362 };
364 363
365 364 /**
366 365 * Refresh codemirror instance
367 366 * @method refresh
368 367 */
369 368 Cell.prototype.refresh = function () {
370 369 this.code_mirror.refresh();
371 370 };
372 371
373 372 /**
374 373 * should be overritten by subclass
375 374 * @method get_text
376 375 */
377 376 Cell.prototype.get_text = function () {
378 377 };
379 378
380 379 /**
381 380 * should be overritten by subclass
382 381 * @method set_text
383 382 * @param {string} text
384 383 */
385 384 Cell.prototype.set_text = function (text) {
386 385 };
387 386
388 387 /**
389 388 * should be overritten by subclass
390 389 * serialise cell to json.
391 390 * @method toJSON
392 391 **/
393 392 Cell.prototype.toJSON = function () {
394 393 var data = {};
395 394 data.metadata = this.metadata;
396 395 data.cell_type = this.cell_type;
397 396 return data;
398 397 };
399 398
400 399
401 400 /**
402 401 * should be overritten by subclass
403 402 * @method fromJSON
404 403 **/
405 404 Cell.prototype.fromJSON = function (data) {
406 405 if (data.metadata !== undefined) {
407 406 this.metadata = data.metadata;
408 407 }
409 408 this.celltoolbar.rebuild();
410 409 };
411 410
412 411
413 412 /**
414 413 * can the cell be split into two cells
415 414 * @method is_splittable
416 415 **/
417 416 Cell.prototype.is_splittable = function () {
418 417 return true;
419 418 };
420 419
421 420
422 421 /**
423 422 * can the cell be merged with other cells
424 423 * @method is_mergeable
425 424 **/
426 425 Cell.prototype.is_mergeable = function () {
427 426 return true;
428 427 };
429 428
430 429
431 430 /**
432 431 * @return {String} - the text before the cursor
433 432 * @method get_pre_cursor
434 433 **/
435 434 Cell.prototype.get_pre_cursor = function () {
436 435 var cursor = this.code_mirror.getCursor();
437 436 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
438 437 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
439 438 return text;
440 439 };
441 440
442 441
443 442 /**
444 443 * @return {String} - the text after the cursor
445 444 * @method get_post_cursor
446 445 **/
447 446 Cell.prototype.get_post_cursor = function () {
448 447 var cursor = this.code_mirror.getCursor();
449 448 var last_line_num = this.code_mirror.lineCount()-1;
450 449 var last_line_len = this.code_mirror.getLine(last_line_num).length;
451 450 var end = {line:last_line_num, ch:last_line_len};
452 451 var text = this.code_mirror.getRange(cursor, end);
453 452 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
454 453 return text;
455 454 };
456 455
457 456 /**
458 457 * Show/Hide CodeMirror LineNumber
459 458 * @method show_line_numbers
460 459 *
461 460 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
462 461 **/
463 462 Cell.prototype.show_line_numbers = function (value) {
464 463 this.code_mirror.setOption('lineNumbers', value);
465 464 this.code_mirror.refresh();
466 465 };
467 466
468 467 /**
469 468 * Toggle CodeMirror LineNumber
470 469 * @method toggle_line_numbers
471 470 **/
472 471 Cell.prototype.toggle_line_numbers = function () {
473 472 var val = this.code_mirror.getOption('lineNumbers');
474 473 this.show_line_numbers(!val);
475 474 };
476 475
477 476 /**
478 477 * Force codemirror highlight mode
479 478 * @method force_highlight
480 479 * @param {object} - CodeMirror mode
481 480 **/
482 481 Cell.prototype.force_highlight = function(mode) {
483 482 this.user_highlight = mode;
484 483 this.auto_highlight();
485 484 };
486 485
487 486 /**
488 487 * Try to autodetect cell highlight mode, or use selected mode
489 488 * @methods _auto_highlight
490 489 * @private
491 490 * @param {String|object|undefined} - CodeMirror mode | 'auto'
492 491 **/
493 492 Cell.prototype._auto_highlight = function (modes) {
494 493 //Here we handle manually selected modes
495 494 var mode;
496 495 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
497 496 {
498 497 mode = this.user_highlight;
499 498 CodeMirror.autoLoadMode(this.code_mirror, mode);
500 499 this.code_mirror.setOption('mode', mode);
501 500 return;
502 501 }
503 502 var current_mode = this.code_mirror.getOption('mode', mode);
504 503 var first_line = this.code_mirror.getLine(0);
505 504 // loop on every pairs
506 505 for(mode in modes) {
507 506 var regs = modes[mode].reg;
508 507 // only one key every time but regexp can't be keys...
509 508 for(var i=0; i<regs.length; i++) {
510 509 // here we handle non magic_modes
511 510 if(first_line.match(regs[i]) !== null) {
512 511 if(current_mode == mode){
513 512 return;
514 513 }
515 514 if (mode.search('magic_') !== 0) {
516 515 this.code_mirror.setOption('mode', mode);
517 516 CodeMirror.autoLoadMode(this.code_mirror, mode);
518 517 return;
519 518 }
520 519 var open = modes[mode].open || "%%";
521 520 var close = modes[mode].close || "%%end";
522 521 var mmode = mode;
523 522 mode = mmode.substr(6);
524 523 if(current_mode == mode){
525 524 return;
526 525 }
527 526 CodeMirror.autoLoadMode(this.code_mirror, mode);
528 527 // create on the fly a mode that swhitch between
529 528 // plain/text and smth else otherwise `%%` is
530 529 // source of some highlight issues.
531 530 // we use patchedGetMode to circumvent a bug in CM
532 531 CodeMirror.defineMode(mmode , function(config) {
533 532 return CodeMirror.multiplexingMode(
534 533 CodeMirror.patchedGetMode(config, 'text/plain'),
535 534 // always set someting on close
536 535 {open: open, close: close,
537 536 mode: CodeMirror.patchedGetMode(config, mode),
538 537 delimStyle: "delimit"
539 538 }
540 539 );
541 540 });
542 541 this.code_mirror.setOption('mode', mmode);
543 542 return;
544 543 }
545 544 }
546 545 }
547 546 // fallback on default
548 547 var default_mode;
549 548 try {
550 549 default_mode = this._options.cm_config.mode;
551 550 } catch(e) {
552 551 default_mode = 'text/plain';
553 552 }
554 553 if( current_mode === default_mode){
555 554 return;
556 555 }
557 556 this.code_mirror.setOption('mode', default_mode);
558 557 };
559 558
560 559 // Backwards compatability.
561 560 IPython.Cell = Cell;
562 561
563 562 return {'Cell': Cell};
564 563 });
@@ -1,519 +1,519 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'notebook/js/tooltip',
9 9 'base/js/keyboard',
10 10 'notebook/js/cell',
11 11 'notebook/js/outputarea',
12 12 'notebook/js/completer',
13 13 'notebook/js/celltoolbar',
14 14 ], function(IPython, $, utils, tooltip, keyboard, cell, outputarea, completer, celltoolbar) {
15 15 "use strict";
16 16 var Cell = cell.Cell;
17 17
18 18 /* local util for codemirror */
19 19 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
20 20
21 21 /**
22 22 *
23 23 * function to delete until previous non blanking space character
24 24 * or first multiple of 4 tabstop.
25 25 * @private
26 26 */
27 27 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 28 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 29 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
30 30 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 31 var tabsize = cm.getOption('tabSize');
32 32 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 33 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
34 34 var select = cm.getRange(from,cur);
35 35 if( select.match(/^\ +$/) !== null){
36 36 cm.replaceRange("",from,cur);
37 37 } else {
38 38 cm.deleteH(-1,"char");
39 39 }
40 40 };
41 41
42 42 var keycodes = keyboard.keycodes;
43 43
44 /**
45 * A Cell conceived to write code.
46 *
47 * The kernel doesn't have to be set at creation time, in that case
48 * it will be null and set_kernel has to be called later.
49 * @class CodeCell
50 * @extends Cell
51 *
52 * @constructor
53 * @param {Object|null} kernel
54 * @param {object|undefined} [options]
55 * @param [options.cm_config] {object} config to pass to CodeMirror
56 */
57 44 var CodeCell = function (kernel, options) {
58 options = options || {};
45 // Constructor
46 //
47 // A Cell conceived to write code.
48 //
49 // Parameters:
50 // kernel: Kernel instance
51 // The kernel doesn't have to be set at creation time, in that case
52 // it will be null and set_kernel has to be called later.
53 // options: dictionary
54 // Dictionary of keyword arguments.
55 // events: $(Events) instance
56 // config: dictionary
57 // keyboard_manager: KeyboardManager instance
58 // notebook: Notebook instance
59 59 this.kernel = kernel || null;
60 60 this.notebook = options.notebook;
61 61 this.collapsed = false;
62 62 this.events = options.events;
63 63 this.tooltip = new tooltip.Tooltip(this.events);
64 64 this.config = options.config;
65 65
66 66 // create all attributed in constructor function
67 67 // even if null for V8 VM optimisation
68 68 this.input_prompt_number = null;
69 69 this.celltoolbar = null;
70 70 this.output_area = null;
71 71 this.last_msg_id = null;
72 72 this.completer = null;
73 73
74 74
75 75 var cm_overwrite_options = {
76 76 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 77 };
78 78
79 79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
80 80 Cell.apply(this,[{
81 81 config: config,
82 82 keyboard_manager: options.keyboard_manager,
83 83 events: this.events}]);
84 84
85 85 // Attributes we want to override in this subclass.
86 86 this.cell_type = "code";
87 87
88 88 var that = this;
89 89 this.element.focusout(
90 90 function() { that.auto_highlight(); }
91 91 );
92 92 };
93 93
94 94 CodeCell.options_default = {
95 95 cm_config : {
96 96 extraKeys: {
97 97 "Tab" : "indentMore",
98 98 "Shift-Tab" : "indentLess",
99 99 "Backspace" : "delSpaceToPrevTabStop",
100 100 "Cmd-/" : "toggleComment",
101 101 "Ctrl-/" : "toggleComment"
102 102 },
103 103 mode: 'ipython',
104 104 theme: 'ipython',
105 105 matchBrackets: true,
106 106 // don't auto-close strings because of CodeMirror #2385
107 107 autoCloseBrackets: "()[]{}"
108 108 }
109 109 };
110 110
111 111 CodeCell.msg_cells = {};
112 112
113 113 CodeCell.prototype = new Cell();
114 114
115 115 /**
116 116 * @method auto_highlight
117 117 */
118 118 CodeCell.prototype.auto_highlight = function () {
119 119 this._auto_highlight(this.config.cell_magic_highlight);
120 120 };
121 121
122 122 /** @method create_element */
123 123 CodeCell.prototype.create_element = function () {
124 124 Cell.prototype.create_element.apply(this, arguments);
125 125
126 126 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
127 127 cell.attr('tabindex','2');
128 128
129 129 var input = $('<div></div>').addClass('input');
130 130 var prompt = $('<div/>').addClass('prompt input_prompt');
131 131 var inner_cell = $('<div/>').addClass('inner_cell');
132 132 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
133 133 inner_cell.append(this.celltoolbar.element);
134 134 var input_area = $('<div/>').addClass('input_area');
135 135 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
136 136 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
137 137 inner_cell.append(input_area);
138 138 input.append(prompt).append(inner_cell);
139 139
140 140 var widget_area = $('<div/>')
141 141 .addClass('widget-area')
142 142 .hide();
143 143 this.widget_area = widget_area;
144 144 var widget_prompt = $('<div/>')
145 145 .addClass('prompt')
146 146 .appendTo(widget_area);
147 147 var widget_subarea = $('<div/>')
148 148 .addClass('widget-subarea')
149 149 .appendTo(widget_area);
150 150 this.widget_subarea = widget_subarea;
151 151 var widget_clear_buton = $('<button />')
152 152 .addClass('close')
153 153 .html('&times;')
154 154 .click(function() {
155 155 widget_area.slideUp('', function(){ widget_subarea.html(''); });
156 156 })
157 157 .appendTo(widget_prompt);
158 158
159 159 var output = $('<div></div>');
160 160 cell.append(input).append(widget_area).append(output);
161 161 this.element = cell;
162 162 this.output_area = new outputarea.OutputArea(output, true, this.events, this.keyboard_manager);
163 163 this.completer = new completer.Completer(this, this.events);
164 164 };
165 165
166 166 /** @method bind_events */
167 167 CodeCell.prototype.bind_events = function () {
168 168 Cell.prototype.bind_events.apply(this);
169 169 var that = this;
170 170
171 171 this.element.focusout(
172 172 function() { that.auto_highlight(); }
173 173 );
174 174 };
175 175
176 176
177 177 /**
178 178 * This method gets called in CodeMirror's onKeyDown/onKeyPress
179 179 * handlers and is used to provide custom key handling. Its return
180 180 * value is used to determine if CodeMirror should ignore the event:
181 181 * true = ignore, false = don't ignore.
182 182 * @method handle_codemirror_keyevent
183 183 */
184 184 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
185 185
186 186 var that = this;
187 187 // whatever key is pressed, first, cancel the tooltip request before
188 188 // they are sent, and remove tooltip if any, except for tab again
189 189 var tooltip_closed = null;
190 190 if (event.type === 'keydown' && event.which != keycodes.tab ) {
191 191 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
192 192 }
193 193
194 194 var cur = editor.getCursor();
195 195 if (event.keyCode === keycodes.enter){
196 196 this.auto_highlight();
197 197 }
198 198
199 199 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
200 200 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
201 201 // browser and keyboard layout !
202 202 // Pressing '(' , request tooltip, don't forget to reappend it
203 203 // The second argument says to hide the tooltip if the docstring
204 204 // is actually empty
205 205 this.tooltip.pending(that, true);
206 206 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
207 207 // If tooltip is active, cancel it. The call to
208 208 // remove_and_cancel_tooltip above doesn't pass, force=true.
209 209 // Because of this it won't actually close the tooltip
210 210 // if it is in sticky mode. Thus, we have to check again if it is open
211 211 // and close it with force=true.
212 212 if (!this.tooltip._hidden) {
213 213 this.tooltip.remove_and_cancel_tooltip(true);
214 214 }
215 215 // If we closed the tooltip, don't let CM or the global handlers
216 216 // handle this event.
217 217 event.stop();
218 218 return true;
219 219 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
220 220 if (editor.somethingSelected()){
221 221 var anchor = editor.getCursor("anchor");
222 222 var head = editor.getCursor("head");
223 223 if( anchor.line != head.line){
224 224 return false;
225 225 }
226 226 }
227 227 this.tooltip.request(that);
228 228 event.stop();
229 229 return true;
230 230 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
231 231 // Tab completion.
232 232 this.tooltip.remove_and_cancel_tooltip();
233 233 if (editor.somethingSelected()) {
234 234 return false;
235 235 }
236 236 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
237 237 if (pre_cursor.trim() === "") {
238 238 // Don't autocomplete if the part of the line before the cursor
239 239 // is empty. In this case, let CodeMirror handle indentation.
240 240 return false;
241 241 } else {
242 242 event.stop();
243 243 this.completer.startCompletion();
244 244 return true;
245 245 }
246 246 }
247 247
248 248 // keyboard event wasn't one of those unique to code cells, let's see
249 249 // if it's one of the generic ones (i.e. check edit mode shortcuts)
250 250 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
251 251 };
252 252
253 253 // Kernel related calls.
254 254
255 255 CodeCell.prototype.set_kernel = function (kernel) {
256 256 this.kernel = kernel;
257 257 };
258 258
259 259 /**
260 260 * Execute current code cell to the kernel
261 261 * @method execute
262 262 */
263 263 CodeCell.prototype.execute = function () {
264 264 this.output_area.clear_output();
265 265
266 266 // Clear widget area
267 267 this.widget_subarea.html('');
268 268 this.widget_subarea.height('');
269 269 this.widget_area.height('');
270 270 this.widget_area.hide();
271 271
272 272 this.set_input_prompt('*');
273 273 this.element.addClass("running");
274 274 if (this.last_msg_id) {
275 275 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
276 276 }
277 277 var callbacks = this.get_callbacks();
278 278
279 279 var old_msg_id = this.last_msg_id;
280 280 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
281 281 if (old_msg_id) {
282 282 delete CodeCell.msg_cells[old_msg_id];
283 283 }
284 284 CodeCell.msg_cells[this.last_msg_id] = this;
285 285 };
286 286
287 287 /**
288 288 * Construct the default callbacks for
289 289 * @method get_callbacks
290 290 */
291 291 CodeCell.prototype.get_callbacks = function () {
292 292 return {
293 293 shell : {
294 294 reply : $.proxy(this._handle_execute_reply, this),
295 295 payload : {
296 296 set_next_input : $.proxy(this._handle_set_next_input, this),
297 297 page : $.proxy(this._open_with_pager, this)
298 298 }
299 299 },
300 300 iopub : {
301 301 output : $.proxy(this.output_area.handle_output, this.output_area),
302 302 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
303 303 },
304 304 input : $.proxy(this._handle_input_request, this)
305 305 };
306 306 };
307 307
308 308 CodeCell.prototype._open_with_pager = function (payload) {
309 309 this.events.trigger('open_with_text.Pager', payload);
310 310 };
311 311
312 312 /**
313 313 * @method _handle_execute_reply
314 314 * @private
315 315 */
316 316 CodeCell.prototype._handle_execute_reply = function (msg) {
317 317 this.set_input_prompt(msg.content.execution_count);
318 318 this.element.removeClass("running");
319 319 this.events.trigger('set_dirty.Notebook', {value: true});
320 320 };
321 321
322 322 /**
323 323 * @method _handle_set_next_input
324 324 * @private
325 325 */
326 326 CodeCell.prototype._handle_set_next_input = function (payload) {
327 327 var data = {'cell': this, 'text': payload.text};
328 328 this.events.trigger('set_next_input.Notebook', data);
329 329 };
330 330
331 331 /**
332 332 * @method _handle_input_request
333 333 * @private
334 334 */
335 335 CodeCell.prototype._handle_input_request = function (msg) {
336 336 this.output_area.append_raw_input(msg);
337 337 };
338 338
339 339
340 340 // Basic cell manipulation.
341 341
342 342 CodeCell.prototype.select = function () {
343 343 var cont = Cell.prototype.select.apply(this);
344 344 if (cont) {
345 345 this.code_mirror.refresh();
346 346 this.auto_highlight();
347 347 }
348 348 return cont;
349 349 };
350 350
351 351 CodeCell.prototype.render = function () {
352 352 var cont = Cell.prototype.render.apply(this);
353 353 // Always execute, even if we are already in the rendered state
354 354 return cont;
355 355 };
356 356
357 357 CodeCell.prototype.unrender = function () {
358 358 // CodeCell is always rendered
359 359 return false;
360 360 };
361 361
362 362 CodeCell.prototype.select_all = function () {
363 363 var start = {line: 0, ch: 0};
364 364 var nlines = this.code_mirror.lineCount();
365 365 var last_line = this.code_mirror.getLine(nlines-1);
366 366 var end = {line: nlines-1, ch: last_line.length};
367 367 this.code_mirror.setSelection(start, end);
368 368 };
369 369
370 370
371 371 CodeCell.prototype.collapse_output = function () {
372 372 this.collapsed = true;
373 373 this.output_area.collapse();
374 374 };
375 375
376 376
377 377 CodeCell.prototype.expand_output = function () {
378 378 this.collapsed = false;
379 379 this.output_area.expand();
380 380 this.output_area.unscroll_area();
381 381 };
382 382
383 383 CodeCell.prototype.scroll_output = function () {
384 384 this.output_area.expand();
385 385 this.output_area.scroll_if_long();
386 386 };
387 387
388 388 CodeCell.prototype.toggle_output = function () {
389 389 this.collapsed = Boolean(1 - this.collapsed);
390 390 this.output_area.toggle_output();
391 391 };
392 392
393 393 CodeCell.prototype.toggle_output_scroll = function () {
394 394 this.output_area.toggle_scroll();
395 395 };
396 396
397 397
398 398 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
399 399 var ns;
400 400 if (prompt_value === undefined) {
401 401 ns = "&nbsp;";
402 402 } else {
403 403 ns = encodeURIComponent(prompt_value);
404 404 }
405 405 return 'In&nbsp;[' + ns + ']:';
406 406 };
407 407
408 408 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
409 409 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
410 410 for(var i=1; i < lines_number; i++) {
411 411 html.push(['...:']);
412 412 }
413 413 return html.join('<br/>');
414 414 };
415 415
416 416 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
417 417
418 418
419 419 CodeCell.prototype.set_input_prompt = function (number) {
420 420 var nline = 1;
421 421 if (this.code_mirror !== undefined) {
422 422 nline = this.code_mirror.lineCount();
423 423 }
424 424 this.input_prompt_number = number;
425 425 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
426 426 // This HTML call is okay because the user contents are escaped.
427 427 this.element.find('div.input_prompt').html(prompt_html);
428 428 };
429 429
430 430
431 431 CodeCell.prototype.clear_input = function () {
432 432 this.code_mirror.setValue('');
433 433 };
434 434
435 435
436 436 CodeCell.prototype.get_text = function () {
437 437 return this.code_mirror.getValue();
438 438 };
439 439
440 440
441 441 CodeCell.prototype.set_text = function (code) {
442 442 return this.code_mirror.setValue(code);
443 443 };
444 444
445 445
446 446 CodeCell.prototype.clear_output = function (wait) {
447 447 this.output_area.clear_output(wait);
448 448 this.set_input_prompt();
449 449 };
450 450
451 451
452 452 // JSON serialization
453 453
454 454 CodeCell.prototype.fromJSON = function (data) {
455 455 Cell.prototype.fromJSON.apply(this, arguments);
456 456 if (data.cell_type === 'code') {
457 457 if (data.input !== undefined) {
458 458 this.set_text(data.input);
459 459 // make this value the starting point, so that we can only undo
460 460 // to this state, instead of a blank cell
461 461 this.code_mirror.clearHistory();
462 462 this.auto_highlight();
463 463 }
464 464 if (data.prompt_number !== undefined) {
465 465 this.set_input_prompt(data.prompt_number);
466 466 } else {
467 467 this.set_input_prompt();
468 468 }
469 469 this.output_area.trusted = data.trusted || false;
470 470 this.output_area.fromJSON(data.outputs);
471 471 if (data.collapsed !== undefined) {
472 472 if (data.collapsed) {
473 473 this.collapse_output();
474 474 } else {
475 475 this.expand_output();
476 476 }
477 477 }
478 478 }
479 479 };
480 480
481 481
482 482 CodeCell.prototype.toJSON = function () {
483 483 var data = Cell.prototype.toJSON.apply(this);
484 484 data.input = this.get_text();
485 485 // is finite protect against undefined and '*' value
486 486 if (isFinite(this.input_prompt_number)) {
487 487 data.prompt_number = this.input_prompt_number;
488 488 }
489 489 var outputs = this.output_area.toJSON();
490 490 data.outputs = outputs;
491 491 data.language = 'python';
492 492 data.trusted = this.output_area.trusted;
493 493 data.collapsed = this.collapsed;
494 494 return data;
495 495 };
496 496
497 497 /**
498 498 * handle cell level logic when a cell is unselected
499 499 * @method unselect
500 500 * @return is the action being taken
501 501 */
502 502 CodeCell.prototype.unselect = function () {
503 503 var cont = Cell.prototype.unselect.apply(this);
504 504 if (cont) {
505 505 // When a code cell is usnelected, make sure that the corresponding
506 506 // tooltip and completer to that cell is closed.
507 507 this.tooltip.remove_and_cancel_tooltip(true);
508 508 if (this.completer !== null) {
509 509 this.completer.close();
510 510 }
511 511 }
512 512 return cont;
513 513 };
514 514
515 515 // Backwards compatability.
516 516 IPython.CodeCell = CodeCell;
517 517
518 518 return {'CodeCell': CodeCell};
519 519 });
@@ -1,559 +1,566 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/keyboard',
9 9 ], function(IPython, $, utils, keyboard) {
10 10 "use strict";
11 11
12 12 var browser = utils.browser[0];
13 13 var platform = utils.platform;
14 14
15 15 // Main keyboard manager for the notebook
16 16 var keycodes = keyboard.keycodes;
17 17
18 18 var KeyboardManager = function (options) {
19 // Constructor
20 //
21 // Parameters:
22 // options: dictionary
23 // Dictionary of keyword arguments.
24 // events: $(Events) instance
25 // pager: Pager instance
19 26 this.mode = 'command';
20 27 this.enabled = true;
21 28 this.pager = options.pager;
22 29 this.quick_help = undefined;
23 30 this.notebook = undefined;
24 31 this.bind_events();
25 32 this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
26 33 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
27 34 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
28 35 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
29 36 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
30 37 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
31 38 };
32 39
33 40 KeyboardManager.prototype.get_default_common_shortcuts = function() {
34 41 var that = this;
35 42 var shortcuts = {
36 43 'shift' : {
37 44 help : '',
38 45 help_index : '',
39 46 handler : function (event) {
40 47 // ignore shift keydown
41 48 return true;
42 49 }
43 50 },
44 51 'shift-enter' : {
45 52 help : 'run cell, select below',
46 53 help_index : 'ba',
47 54 handler : function (event) {
48 55 that.notebook.execute_cell_and_select_below();
49 56 return false;
50 57 }
51 58 },
52 59 'ctrl-enter' : {
53 60 help : 'run cell',
54 61 help_index : 'bb',
55 62 handler : function (event) {
56 63 that.notebook.execute_cell();
57 64 return false;
58 65 }
59 66 },
60 67 'alt-enter' : {
61 68 help : 'run cell, insert below',
62 69 help_index : 'bc',
63 70 handler : function (event) {
64 71 that.notebook.execute_cell_and_insert_below();
65 72 return false;
66 73 }
67 74 }
68 75 };
69 76
70 77 if (platform === 'MacOS') {
71 78 shortcuts['cmd-s'] =
72 79 {
73 80 help : 'save notebook',
74 81 help_index : 'fb',
75 82 handler : function (event) {
76 83 that.notebook.save_checkpoint();
77 84 event.preventDefault();
78 85 return false;
79 86 }
80 87 };
81 88 } else {
82 89 shortcuts['ctrl-s'] =
83 90 {
84 91 help : 'save notebook',
85 92 help_index : 'fb',
86 93 handler : function (event) {
87 94 that.notebook.save_checkpoint();
88 95 event.preventDefault();
89 96 return false;
90 97 }
91 98 };
92 99 }
93 100 return shortcuts;
94 101 };
95 102
96 103 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
97 104 var that = this;
98 105 return {
99 106 'esc' : {
100 107 help : 'command mode',
101 108 help_index : 'aa',
102 109 handler : function (event) {
103 110 that.notebook.command_mode();
104 111 return false;
105 112 }
106 113 },
107 114 'ctrl-m' : {
108 115 help : 'command mode',
109 116 help_index : 'ab',
110 117 handler : function (event) {
111 118 that.notebook.command_mode();
112 119 return false;
113 120 }
114 121 },
115 122 'up' : {
116 123 help : '',
117 124 help_index : '',
118 125 handler : function (event) {
119 126 var index = that.notebook.get_selected_index();
120 127 var cell = that.notebook.get_cell(index);
121 128 if (cell && cell.at_top() && index !== 0) {
122 129 event.preventDefault();
123 130 that.notebook.command_mode();
124 131 that.notebook.select_prev();
125 132 that.notebook.edit_mode();
126 133 var cm = that.notebook.get_selected_cell().code_mirror;
127 134 cm.setCursor(cm.lastLine(), 0);
128 135 return false;
129 136 } else if (cell) {
130 137 var cm = cell.code_mirror;
131 138 cm.execCommand('goLineUp');
132 139 return false;
133 140 }
134 141 }
135 142 },
136 143 'down' : {
137 144 help : '',
138 145 help_index : '',
139 146 handler : function (event) {
140 147 var index = that.notebook.get_selected_index();
141 148 var cell = that.notebook.get_cell(index);
142 149 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
143 150 event.preventDefault();
144 151 that.notebook.command_mode();
145 152 that.notebook.select_next();
146 153 that.notebook.edit_mode();
147 154 var cm = that.notebook.get_selected_cell().code_mirror;
148 155 cm.setCursor(0, 0);
149 156 return false;
150 157 } else {
151 158 var cm = cell.code_mirror;
152 159 cm.execCommand('goLineDown');
153 160 return false;
154 161 }
155 162 }
156 163 },
157 164 'ctrl-shift--' : {
158 165 help : 'split cell',
159 166 help_index : 'ea',
160 167 handler : function (event) {
161 168 that.notebook.split_cell();
162 169 return false;
163 170 }
164 171 },
165 172 'ctrl-shift-subtract' : {
166 173 help : '',
167 174 help_index : 'eb',
168 175 handler : function (event) {
169 176 that.notebook.split_cell();
170 177 return false;
171 178 }
172 179 },
173 180 };
174 181 };
175 182
176 183 KeyboardManager.prototype.get_default_command_shortcuts = function() {
177 184 var that = this;
178 185 return {
179 186 'enter' : {
180 187 help : 'edit mode',
181 188 help_index : 'aa',
182 189 handler : function (event) {
183 190 that.notebook.edit_mode();
184 191 return false;
185 192 }
186 193 },
187 194 'up' : {
188 195 help : 'select previous cell',
189 196 help_index : 'da',
190 197 handler : function (event) {
191 198 var index = that.notebook.get_selected_index();
192 199 if (index !== 0 && index !== null) {
193 200 that.notebook.select_prev();
194 201 that.notebook.focus_cell();
195 202 }
196 203 return false;
197 204 }
198 205 },
199 206 'down' : {
200 207 help : 'select next cell',
201 208 help_index : 'db',
202 209 handler : function (event) {
203 210 var index = that.notebook.get_selected_index();
204 211 if (index !== (that.notebook.ncells()-1) && index !== null) {
205 212 that.notebook.select_next();
206 213 that.notebook.focus_cell();
207 214 }
208 215 return false;
209 216 }
210 217 },
211 218 'k' : {
212 219 help : 'select previous cell',
213 220 help_index : 'dc',
214 221 handler : function (event) {
215 222 var index = that.notebook.get_selected_index();
216 223 if (index !== 0 && index !== null) {
217 224 that.notebook.select_prev();
218 225 that.notebook.focus_cell();
219 226 }
220 227 return false;
221 228 }
222 229 },
223 230 'j' : {
224 231 help : 'select next cell',
225 232 help_index : 'dd',
226 233 handler : function (event) {
227 234 var index = that.notebook.get_selected_index();
228 235 if (index !== (that.notebook.ncells()-1) && index !== null) {
229 236 that.notebook.select_next();
230 237 that.notebook.focus_cell();
231 238 }
232 239 return false;
233 240 }
234 241 },
235 242 'x' : {
236 243 help : 'cut cell',
237 244 help_index : 'ee',
238 245 handler : function (event) {
239 246 that.notebook.cut_cell();
240 247 return false;
241 248 }
242 249 },
243 250 'c' : {
244 251 help : 'copy cell',
245 252 help_index : 'ef',
246 253 handler : function (event) {
247 254 that.notebook.copy_cell();
248 255 return false;
249 256 }
250 257 },
251 258 'shift-v' : {
252 259 help : 'paste cell above',
253 260 help_index : 'eg',
254 261 handler : function (event) {
255 262 that.notebook.paste_cell_above();
256 263 return false;
257 264 }
258 265 },
259 266 'v' : {
260 267 help : 'paste cell below',
261 268 help_index : 'eh',
262 269 handler : function (event) {
263 270 that.notebook.paste_cell_below();
264 271 return false;
265 272 }
266 273 },
267 274 'd' : {
268 275 help : 'delete cell (press twice)',
269 276 help_index : 'ej',
270 277 count: 2,
271 278 handler : function (event) {
272 279 that.notebook.delete_cell();
273 280 return false;
274 281 }
275 282 },
276 283 'a' : {
277 284 help : 'insert cell above',
278 285 help_index : 'ec',
279 286 handler : function (event) {
280 287 that.notebook.insert_cell_above();
281 288 that.notebook.select_prev();
282 289 that.notebook.focus_cell();
283 290 return false;
284 291 }
285 292 },
286 293 'b' : {
287 294 help : 'insert cell below',
288 295 help_index : 'ed',
289 296 handler : function (event) {
290 297 that.notebook.insert_cell_below();
291 298 that.notebook.select_next();
292 299 that.notebook.focus_cell();
293 300 return false;
294 301 }
295 302 },
296 303 'y' : {
297 304 help : 'to code',
298 305 help_index : 'ca',
299 306 handler : function (event) {
300 307 that.notebook.to_code();
301 308 return false;
302 309 }
303 310 },
304 311 'm' : {
305 312 help : 'to markdown',
306 313 help_index : 'cb',
307 314 handler : function (event) {
308 315 that.notebook.to_markdown();
309 316 return false;
310 317 }
311 318 },
312 319 'r' : {
313 320 help : 'to raw',
314 321 help_index : 'cc',
315 322 handler : function (event) {
316 323 that.notebook.to_raw();
317 324 return false;
318 325 }
319 326 },
320 327 '1' : {
321 328 help : 'to heading 1',
322 329 help_index : 'cd',
323 330 handler : function (event) {
324 331 that.notebook.to_heading(undefined, 1);
325 332 return false;
326 333 }
327 334 },
328 335 '2' : {
329 336 help : 'to heading 2',
330 337 help_index : 'ce',
331 338 handler : function (event) {
332 339 that.notebook.to_heading(undefined, 2);
333 340 return false;
334 341 }
335 342 },
336 343 '3' : {
337 344 help : 'to heading 3',
338 345 help_index : 'cf',
339 346 handler : function (event) {
340 347 that.notebook.to_heading(undefined, 3);
341 348 return false;
342 349 }
343 350 },
344 351 '4' : {
345 352 help : 'to heading 4',
346 353 help_index : 'cg',
347 354 handler : function (event) {
348 355 that.notebook.to_heading(undefined, 4);
349 356 return false;
350 357 }
351 358 },
352 359 '5' : {
353 360 help : 'to heading 5',
354 361 help_index : 'ch',
355 362 handler : function (event) {
356 363 that.notebook.to_heading(undefined, 5);
357 364 return false;
358 365 }
359 366 },
360 367 '6' : {
361 368 help : 'to heading 6',
362 369 help_index : 'ci',
363 370 handler : function (event) {
364 371 that.notebook.to_heading(undefined, 6);
365 372 return false;
366 373 }
367 374 },
368 375 'o' : {
369 376 help : 'toggle output',
370 377 help_index : 'gb',
371 378 handler : function (event) {
372 379 that.notebook.toggle_output();
373 380 return false;
374 381 }
375 382 },
376 383 'shift-o' : {
377 384 help : 'toggle output scrolling',
378 385 help_index : 'gc',
379 386 handler : function (event) {
380 387 that.notebook.toggle_output_scroll();
381 388 return false;
382 389 }
383 390 },
384 391 's' : {
385 392 help : 'save notebook',
386 393 help_index : 'fa',
387 394 handler : function (event) {
388 395 that.notebook.save_checkpoint();
389 396 return false;
390 397 }
391 398 },
392 399 'ctrl-j' : {
393 400 help : 'move cell down',
394 401 help_index : 'eb',
395 402 handler : function (event) {
396 403 that.notebook.move_cell_down();
397 404 return false;
398 405 }
399 406 },
400 407 'ctrl-k' : {
401 408 help : 'move cell up',
402 409 help_index : 'ea',
403 410 handler : function (event) {
404 411 that.notebook.move_cell_up();
405 412 return false;
406 413 }
407 414 },
408 415 'l' : {
409 416 help : 'toggle line numbers',
410 417 help_index : 'ga',
411 418 handler : function (event) {
412 419 that.notebook.cell_toggle_line_numbers();
413 420 return false;
414 421 }
415 422 },
416 423 'i' : {
417 424 help : 'interrupt kernel (press twice)',
418 425 help_index : 'ha',
419 426 count: 2,
420 427 handler : function (event) {
421 428 that.notebook.kernel.interrupt();
422 429 return false;
423 430 }
424 431 },
425 432 '0' : {
426 433 help : 'restart kernel (press twice)',
427 434 help_index : 'hb',
428 435 count: 2,
429 436 handler : function (event) {
430 437 that.notebook.restart_kernel();
431 438 return false;
432 439 }
433 440 },
434 441 'h' : {
435 442 help : 'keyboard shortcuts',
436 443 help_index : 'ge',
437 444 handler : function (event) {
438 445 that.quick_help.show_keyboard_shortcuts();
439 446 return false;
440 447 }
441 448 },
442 449 'z' : {
443 450 help : 'undo last delete',
444 451 help_index : 'ei',
445 452 handler : function (event) {
446 453 that.notebook.undelete_cell();
447 454 return false;
448 455 }
449 456 },
450 457 'shift-m' : {
451 458 help : 'merge cell below',
452 459 help_index : 'ek',
453 460 handler : function (event) {
454 461 that.notebook.merge_cell_below();
455 462 return false;
456 463 }
457 464 },
458 465 'q' : {
459 466 help : 'close pager',
460 467 help_index : 'gd',
461 468 handler : function (event) {
462 469 that.pager.collapse();
463 470 return false;
464 471 }
465 472 },
466 473 };
467 474 };
468 475
469 476 KeyboardManager.prototype.bind_events = function () {
470 477 var that = this;
471 478 $(document).keydown(function (event) {
472 479 return that.handle_keydown(event);
473 480 });
474 481 };
475 482
476 483 KeyboardManager.prototype.handle_keydown = function (event) {
477 484 var notebook = this.notebook;
478 485
479 486 if (event.which === keycodes.esc) {
480 487 // Intercept escape at highest level to avoid closing
481 488 // websocket connection with firefox
482 489 event.preventDefault();
483 490 }
484 491
485 492 if (!this.enabled) {
486 493 if (event.which === keycodes.esc) {
487 494 // ESC
488 495 notebook.command_mode();
489 496 return false;
490 497 }
491 498 return true;
492 499 }
493 500
494 501 if (this.mode === 'edit') {
495 502 return this.edit_shortcuts.call_handler(event);
496 503 } else if (this.mode === 'command') {
497 504 return this.command_shortcuts.call_handler(event);
498 505 }
499 506 return true;
500 507 };
501 508
502 509 KeyboardManager.prototype.edit_mode = function () {
503 510 this.last_mode = this.mode;
504 511 this.mode = 'edit';
505 512 };
506 513
507 514 KeyboardManager.prototype.command_mode = function () {
508 515 this.last_mode = this.mode;
509 516 this.mode = 'command';
510 517 };
511 518
512 519 KeyboardManager.prototype.enable = function () {
513 520 this.enabled = true;
514 521 };
515 522
516 523 KeyboardManager.prototype.disable = function () {
517 524 this.enabled = false;
518 525 };
519 526
520 527 KeyboardManager.prototype.register_events = function (e) {
521 528 var that = this;
522 529 var handle_focus = function () {
523 530 that.disable();
524 531 };
525 532 var handle_blur = function () {
526 533 that.enable();
527 534 };
528 535 e.on('focusin', handle_focus);
529 536 e.on('focusout', handle_blur);
530 537 // TODO: Very strange. The focusout event does not seem fire for the
531 538 // bootstrap textboxes on FF25&26... This works around that by
532 539 // registering focus and blur events recursively on all inputs within
533 540 // registered element.
534 541 e.find('input').blur(handle_blur);
535 542 e.on('DOMNodeInserted', function (event) {
536 543 var target = $(event.target);
537 544 if (target.is('input')) {
538 545 target.blur(handle_blur);
539 546 } else {
540 547 target.find('input').blur(handle_blur);
541 548 }
542 549 });
543 550 // There are times (raw_input) where we remove the element from the DOM before
544 551 // focusout is called. In this case we bind to the remove event of jQueryUI,
545 552 // which gets triggered upon removal, iff it is focused at the time.
546 553 // is_focused must be used to check for the case where an element within
547 554 // the element being removed is focused.
548 555 e.on('remove', function () {
549 556 if (utils.is_focused(e[0])) {
550 557 that.enable();
551 558 }
552 559 });
553 560 };
554 561
555 562 // For backwards compatability.
556 563 IPython.KeyboardManager = KeyboardManager;
557 564
558 565 return {'KeyboardManager': KeyboardManager};
559 566 });
@@ -1,221 +1,229 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'notebook/js/toolbar',
8 8 'notebook/js/celltoolbar',
9 9 ], function(IPython, $, toolbar, celltoolbar) {
10 10 "use strict";
11 11
12 12 var MainToolBar = function (selector, options) {
13 // Constructor
14 //
15 // Parameters:
16 // selector: string
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // notebook: Notebook instance
13 21 toolbar.ToolBar.apply(this, arguments);
14 22 this.events = options.events;
15 23 this.notebook = options.notebook;
16 24 this.construct();
17 25 this.add_celltype_list();
18 26 this.add_celltoolbar_list();
19 27 this.bind_events();
20 28 };
21 29
22 30 MainToolBar.prototype = new toolbar.ToolBar();
23 31
24 32 MainToolBar.prototype.construct = function () {
25 33 var that = this;
26 34 this.add_buttons_group([
27 35 {
28 36 id : 'save_b',
29 37 label : 'Save and Checkpoint',
30 38 icon : 'icon-save',
31 39 callback : function () {
32 40 that.notebook.save_checkpoint();
33 41 }
34 42 }
35 43 ]);
36 44
37 45 this.add_buttons_group([
38 46 {
39 47 id : 'insert_below_b',
40 48 label : 'Insert Cell Below',
41 49 icon : 'icon-plus-sign',
42 50 callback : function () {
43 51 that.notebook.insert_cell_below('code');
44 52 that.notebook.select_next();
45 53 that.notebook.focus_cell();
46 54 }
47 55 }
48 56 ],'insert_above_below');
49 57
50 58 this.add_buttons_group([
51 59 {
52 60 id : 'cut_b',
53 61 label : 'Cut Cell',
54 62 icon : 'icon-cut',
55 63 callback : function () {
56 64 that.notebook.cut_cell();
57 65 }
58 66 },
59 67 {
60 68 id : 'copy_b',
61 69 label : 'Copy Cell',
62 70 icon : 'icon-copy',
63 71 callback : function () {
64 72 that.notebook.copy_cell();
65 73 }
66 74 },
67 75 {
68 76 id : 'paste_b',
69 77 label : 'Paste Cell Below',
70 78 icon : 'icon-paste',
71 79 callback : function () {
72 80 that.notebook.paste_cell_below();
73 81 }
74 82 }
75 83 ],'cut_copy_paste');
76 84
77 85 this.add_buttons_group([
78 86 {
79 87 id : 'move_up_b',
80 88 label : 'Move Cell Up',
81 89 icon : 'icon-arrow-up',
82 90 callback : function () {
83 91 that.notebook.move_cell_up();
84 92 }
85 93 },
86 94 {
87 95 id : 'move_down_b',
88 96 label : 'Move Cell Down',
89 97 icon : 'icon-arrow-down',
90 98 callback : function () {
91 99 that.notebook.move_cell_down();
92 100 }
93 101 }
94 102 ],'move_up_down');
95 103
96 104
97 105 this.add_buttons_group([
98 106 {
99 107 id : 'run_b',
100 108 label : 'Run Cell',
101 109 icon : 'icon-play',
102 110 callback : function () {
103 111 // emulate default shift-enter behavior
104 112 that.notebook.execute_cell_and_select_below();
105 113 }
106 114 },
107 115 {
108 116 id : 'interrupt_b',
109 117 label : 'Interrupt',
110 118 icon : 'icon-stop',
111 119 callback : function () {
112 120 that.notebook.session.interrupt_kernel();
113 121 }
114 122 },
115 123 {
116 124 id : 'repeat_b',
117 125 label : 'Restart Kernel',
118 126 icon : 'icon-repeat',
119 127 callback : function () {
120 128 that.notebook.restart_kernel();
121 129 }
122 130 }
123 131 ],'run_int');
124 132 };
125 133
126 134 MainToolBar.prototype.add_celltype_list = function () {
127 135 this.element
128 136 .append($('<select/>')
129 137 .attr('id','cell_type')
130 138 .addClass('form-control select-xs')
131 139 // .addClass('ui-widget-content')
132 140 .append($('<option/>').attr('value','code').text('Code'))
133 141 .append($('<option/>').attr('value','markdown').text('Markdown'))
134 142 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
135 143 .append($('<option/>').attr('value','heading1').text('Heading 1'))
136 144 .append($('<option/>').attr('value','heading2').text('Heading 2'))
137 145 .append($('<option/>').attr('value','heading3').text('Heading 3'))
138 146 .append($('<option/>').attr('value','heading4').text('Heading 4'))
139 147 .append($('<option/>').attr('value','heading5').text('Heading 5'))
140 148 .append($('<option/>').attr('value','heading6').text('Heading 6'))
141 149 );
142 150 };
143 151
144 152
145 153 MainToolBar.prototype.add_celltoolbar_list = function () {
146 154 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
147 155 var select = $('<select/>')
148 156 // .addClass('ui-widget-content')
149 157 .attr('id', 'ctb_select')
150 158 .addClass('form-control select-xs')
151 159 .append($('<option/>').attr('value', '').text('None'));
152 160 this.element.append(label).append(select);
153 161 select.change(function() {
154 162 var val = $(this).val();
155 163 if (val ==='') {
156 164 celltoolbar.CellToolbar.global_hide();
157 165 delete that.notebook.metadata.celltoolbar;
158 166 } else {
159 167 celltoolbar.CellToolbar.global_show();
160 168 celltoolbar.CellToolbar.activate_preset(val);
161 169 that.notebook.metadata.celltoolbar = val;
162 170 }
163 171 });
164 172 // Setup the currently registered presets.
165 173 var presets = celltoolbar.CellToolbar.list_presets();
166 174 for (var i=0; i<presets.length; i++) {
167 175 var name = presets[i];
168 176 select.append($('<option/>').attr('value', name).text(name));
169 177 }
170 178 // Setup future preset registrations.
171 179 this.events.on('preset_added.CellToolbar', function (event, data) {
172 180 var name = data.name;
173 181 select.append($('<option/>').attr('value', name).text(name));
174 182 });
175 183 // Update select value when a preset is activated.
176 184 this.events.on('preset_activated.CellToolbar', function (event, data) {
177 185 if (select.val() !== data.name)
178 186 select.val(data.name);
179 187 });
180 188 };
181 189
182 190
183 191 MainToolBar.prototype.bind_events = function () {
184 192 var that = this;
185 193
186 194 this.element.find('#cell_type').change(function () {
187 195 var cell_type = $(this).val();
188 196 if (cell_type === 'code') {
189 197 that.notebook.to_code();
190 198 } else if (cell_type === 'markdown') {
191 199 that.notebook.to_markdown();
192 200 } else if (cell_type === 'raw') {
193 201 that.notebook.to_raw();
194 202 } else if (cell_type === 'heading1') {
195 203 that.notebook.to_heading(undefined, 1);
196 204 } else if (cell_type === 'heading2') {
197 205 that.notebook.to_heading(undefined, 2);
198 206 } else if (cell_type === 'heading3') {
199 207 that.notebook.to_heading(undefined, 3);
200 208 } else if (cell_type === 'heading4') {
201 209 that.notebook.to_heading(undefined, 4);
202 210 } else if (cell_type === 'heading5') {
203 211 that.notebook.to_heading(undefined, 5);
204 212 } else if (cell_type === 'heading6') {
205 213 that.notebook.to_heading(undefined, 6);
206 214 }
207 215 });
208 216 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
209 217 if (data.cell_type === 'heading') {
210 218 that.element.find('#cell_type').val(data.cell_type+data.level);
211 219 } else {
212 220 that.element.find('#cell_type').val(data.cell_type);
213 221 }
214 222 });
215 223 };
216 224
217 225 // Backwards compatability.
218 226 IPython.MainToolBar = MainToolBar;
219 227
220 228 return {'MainToolBar': MainToolBar};
221 229 });
@@ -1,243 +1,248 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 'base/js/namespace',
5 6 'jquery',
6 7 'base/js/utils',
7 8 'base/js/dialog',
8 ], function($, utils, dialog) {
9 ], function(IPython, $, utils, dialog) {
9 10 "use strict";
10 11
11 12 var init = function () {
12 13 if (window.MathJax) {
13 14 // MathJax loaded
14 15 MathJax.Hub.Config({
15 16 tex2jax: {
16 17 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
17 18 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
18 19 processEscapes: true,
19 20 processEnvironments: true
20 21 },
21 22 // Center justify equations in code and markdown cells. Elsewhere
22 23 // we use CSS to left justify single line equations in code cells.
23 24 displayAlign: 'center',
24 25 "HTML-CSS": {
25 26 styles: {'.MathJax_Display': {"margin": 0}},
26 27 linebreaks: { automatic: true }
27 28 }
28 29 });
29 30 MathJax.Hub.Configured();
30 31 } else if (window.mathjax_url !== "") {
31 32 // Don't have MathJax, but should. Show dialog.
32 33 var message = $('<div/>')
33 34 .append(
34 35 $("<p/></p>").addClass('dialog').text(
35 36 "Math/LaTeX rendering will be disabled."
36 37 )
37 38 ).append(
38 39 $("<p></p>").addClass('dialog').text(
39 40 "If you have administrative access to the notebook server and" +
40 41 " a working internet connection, you can install a local copy" +
41 42 " of MathJax for offline use with the following command on the server" +
42 43 " at a Python or IPython prompt:"
43 44 )
44 45 ).append(
45 46 $("<pre></pre>").addClass('dialog').text(
46 47 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
47 48 )
48 49 ).append(
49 50 $("<p></p>").addClass('dialog').text(
50 51 "This will try to install MathJax into the IPython source directory."
51 52 )
52 53 ).append(
53 54 $("<p></p>").addClass('dialog').text(
54 55 "If IPython is installed to a location that requires" +
55 56 " administrative privileges to write, you will need to make this call as" +
56 57 " an administrator, via 'sudo'."
57 58 )
58 59 ).append(
59 60 $("<p></p>").addClass('dialog').text(
60 61 "When you start the notebook server, you can instruct it to disable MathJax support altogether:"
61 62 )
62 63 ).append(
63 64 $("<pre></pre>").addClass('dialog').text(
64 65 "$ ipython notebook --no-mathjax"
65 66 )
66 67 ).append(
67 68 $("<p></p>").addClass('dialog').text(
68 69 "which will prevent this dialog from appearing."
69 70 )
70 71 );
71 72 dialog.modal({
72 73 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
73 74 body : message,
74 75 buttons : {
75 76 OK : {class: "btn-danger"}
76 77 }
77 78 });
78 79 }
79 80 };
80 81
81 82 // Some magic for deferring mathematical expressions to MathJax
82 83 // by hiding them from the Markdown parser.
83 84 // Some of the code here is adapted with permission from Davide Cervone
84 85 // under the terms of the Apache2 license governing the MathJax project.
85 86 // Other minor modifications are also due to StackExchange and are used with
86 87 // permission.
87 88
88 89 var inline = "$"; // the inline math delimiter
89 90
90 91 // MATHSPLIT contains the pattern for math delimiters and special symbols
91 92 // needed for searching for math in the text input.
92 93 var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
93 94
94 95 // The math is in blocks i through j, so
95 96 // collect it into one block and clear the others.
96 97 // Replace &, <, and > by named entities.
97 98 // For IE, put <br> at the ends of comments since IE removes \n.
98 99 // Clear the current math positions and store the index of the
99 100 // math, then push the math string onto the storage array.
100 101 // The preProcess function is called on all blocks if it has been passed in
101 102 var process_math = function (i, j, pre_process, math, blocks) {
102 103 var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&amp;") // use HTML entity for &
103 104 .replace(/</g, "&lt;") // use HTML entity for <
104 105 .replace(/>/g, "&gt;") // use HTML entity for >
105 106 ;
106 107 if (utils.browser === 'msie') {
107 108 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
108 109 }
109 110 while (j > i) {
110 111 blocks[j] = "";
111 112 j--;
112 113 }
113 114 blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
114 115 if (pre_process){
115 116 block = pre_process(block);
116 117 }
117 118 math.push(block);
118 119 return blocks;
119 120 };
120 121
121 122 // Break up the text into its component parts and search
122 123 // through them for math delimiters, braces, linebreaks, etc.
123 124 // Math delimiters must match and braces must balance.
124 125 // Don't allow math to pass through a double linebreak
125 126 // (which will be a paragraph).
126 127 //
127 128 var remove_math = function (text) {
128 129 var math = []; // stores math strings for later
129 130 var start;
130 131 var end;
131 132 var last;
132 133 var braces;
133 134
134 135 // Except for extreme edge cases, this should catch precisely those pieces of the markdown
135 136 // source that will later be turned into code spans. While MathJax will not TeXify code spans,
136 137 // we still have to consider them at this point; the following issue has happened several times:
137 138 //
138 139 // `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
139 140
140 141 var hasCodeSpans = /`/.test(text),
141 142 de_tilde;
142 143 if (hasCodeSpans) {
143 144 text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {
144 145 return wholematch.replace(/\$/g, "~D");
145 146 });
146 147 de_tilde = function (text) {
147 148 return text.replace(/~([TD])/g, function (wholematch, character) {
148 149 return { T: "~", D: "$" }[character];
149 150 });
150 151 };
151 152 } else {
152 153 de_tilde = function (text) { return text; };
153 154 }
154 155
155 156 var blocks = utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
156 157
157 158 for (var i = 1, m = blocks.length; i < m; i += 2) {
158 159 var block = blocks[i];
159 160 if (block.charAt(0) === "@") {
160 161 //
161 162 // Things that look like our math markers will get
162 163 // stored and then retrieved along with the math.
163 164 //
164 165 blocks[i] = "@@" + math.length + "@@";
165 166 math.push(block);
166 167 }
167 168 else if (start) {
168 169 //
169 170 // If we are in math, look for the end delimiter,
170 171 // but don't go past double line breaks, and
171 172 // and balance braces within the math.
172 173 //
173 174 if (block === end) {
174 175 if (braces) {
175 176 last = i;
176 177 }
177 178 else {
178 179 blocks = process_math(start, i, de_tilde, math, blocks);
179 180 start = null;
180 181 end = null;
181 182 last = null;
182 183 }
183 184 }
184 185 else if (block.match(/\n.*\n/)) {
185 186 if (last) {
186 187 i = last;
187 188 blocks = process_math(start, i, de_tilde, math, blocks);
188 189 }
189 190 start = null;
190 191 end = null;
191 192 last = null;
192 193 braces = 0;
193 194 }
194 195 else if (block === "{") {
195 196 braces++;
196 197 }
197 198 else if (block === "}" && braces) {
198 199 braces--;
199 200 }
200 201 }
201 202 else {
202 203 //
203 204 // Look for math start delimiters and when
204 205 // found, set up the end delimiter.
205 206 //
206 207 if (block === inline || block === "$$") {
207 208 start = i;
208 209 end = block;
209 210 braces = 0;
210 211 }
211 212 else if (block.substr(1, 5) === "begin") {
212 213 start = i;
213 214 end = "\\end" + block.substr(6);
214 215 braces = 0;
215 216 }
216 217 }
217 218 }
218 219 if (last) {
219 220 blocks = process_math(start, last, de_tilde, math, blocks);
220 221 start = null;
221 222 end = null;
222 223 last = null;
223 224 }
224 225 return [de_tilde(blocks.join("")), math];
225 226 };
226 227
227 228 //
228 229 // Put back the math strings that were saved,
229 230 // and clear the math array (no need to keep it around).
230 231 //
231 232 var replace_math = function (text, math) {
232 233 text = text.replace(/@@(\d+)@@/g, function (match, n) {
233 234 return math[n];
234 235 });
235 236 return text;
236 237 };
237 238
238 return {
239 var mathjaxutils = {
239 240 init : init,
240 241 remove_math : remove_math,
241 242 replace_math : replace_math
242 243 };
244
245 IPython.mathjaxutils = mathjaxutils;
246
247 return mathjaxutils;
243 248 });
@@ -1,347 +1,348 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'notebook/js/tour',
9 'components/bootstrap-tour/build/js/bootstrap-tour.min',
10 9 ], function(IPython, $, utils, tour) {
11 10 "use strict";
12 11
13 /**
14 * A MenuBar Class to generate the menubar of IPython notebook
15 * @Class MenuBar
16 *
17 * @constructor
18 *
19 *
20 * @param selector {string} selector for the menubar element in DOM
21 * @param {object} [options]
22 * @param [options.base_url] {String} String to use for the
23 * base project url. Default is to inspect
24 * $('body').data('baseUrl');
25 * does not support change for now is set through this option
26 */
27 12 var MenuBar = function (selector, options) {
13 // Constructor
14 //
15 // A MenuBar Class to generate the menubar of IPython notebook
16 //
17 // Parameters:
18 // selector: string
19 // options: dictionary
20 // Dictionary of keyword arguments.
21 // notebook: Notebook instance
22 // layout_manager: LayoutManager instance
23 // events: $(Events) instance
24 // save_widget: SaveWidget instance
25 // quick_help: QuickHelp instance
26 // base_url : string
27 // notebook_path : string
28 // notebook_name : string
28 29 options = options || {};
29 30 this.base_url = options.base_url || utils.get_body_data("baseUrl");
30 31 this.selector = selector;
31 32 this.notebook = options.notebook;
32 33 this.layout_manager = options.layout_manager;
33 34 this.events = options.events;
34 35 this.save_widget = options.save_widget;
35 36 this.quick_help = options.quick_help;
36 37
37 38 try {
38 39 this.tour = new tour.Tour(this.notebook, this.events);
39 40 } catch (e) {
40 41 this.tour = undefined;
41 42 console.log("Failed to instantiate Notebook Tour", e);
42 43 }
43 44
44 45 if (this.selector !== undefined) {
45 46 this.element = $(selector);
46 47 this.style();
47 48 this.bind_events();
48 49 }
49 50 };
50 51
51 52 MenuBar.prototype.style = function () {
52 53 var that = this;
53 54 this.element.addClass('border-box-sizing');
54 55 this.element.find("li").click(function (event, ui) {
55 56 // The selected cell loses focus when the menu is entered, so we
56 57 // re-select it upon selection.
57 58 var i = that.notebook.get_selected_index();
58 59 that.notebook.select(i);
59 60 }
60 61 );
61 62 };
62 63
63 64 MenuBar.prototype._nbconvert = function (format, download) {
64 65 download = download || false;
65 66 var notebook_path = this.notebook.notebook_path;
66 67 var notebook_name = this.notebook.notebook_name;
67 68 if (this.notebook.dirty) {
68 69 this.notebook.save_notebook({async : false});
69 70 }
70 71 var url = utils.url_join_encode(
71 72 this.base_url,
72 73 'nbconvert',
73 74 format,
74 75 notebook_path,
75 76 notebook_name
76 77 ) + "?download=" + download.toString();
77 78
78 79 window.open(url);
79 80 };
80 81
81 82 MenuBar.prototype.bind_events = function () {
82 83 // File
83 84 var that = this;
84 85 this.element.find('#new_notebook').click(function () {
85 86 that.notebook.new_notebook();
86 87 });
87 88 this.element.find('#open_notebook').click(function () {
88 89 window.open(utils.url_join_encode(
89 90 that.notebook.base_url,
90 91 'tree',
91 92 that.notebook.notebook_path
92 93 ));
93 94 });
94 95 this.element.find('#copy_notebook').click(function () {
95 96 that.notebook.copy_notebook();
96 97 return false;
97 98 });
98 99 this.element.find('#download_ipynb').click(function () {
99 100 var base_url = that.notebook.base_url;
100 101 var notebook_path = that.notebook.notebook_path;
101 102 var notebook_name = that.notebook.notebook_name;
102 103 if (that.notebook.dirty) {
103 104 that.notebook.save_notebook({async : false});
104 105 }
105 106
106 107 var url = utils.url_join_encode(
107 108 base_url,
108 109 'files',
109 110 notebook_path,
110 111 notebook_name
111 112 );
112 113 window.location.assign(url);
113 114 });
114 115
115 116 this.element.find('#print_preview').click(function () {
116 117 that._nbconvert('html', false);
117 118 });
118 119
119 120 this.element.find('#download_py').click(function () {
120 121 that._nbconvert('python', true);
121 122 });
122 123
123 124 this.element.find('#download_html').click(function () {
124 125 that._nbconvert('html', true);
125 126 });
126 127
127 128 this.element.find('#download_rst').click(function () {
128 129 that._nbconvert('rst', true);
129 130 });
130 131
131 132 this.element.find('#download_pdf').click(function () {
132 133 that._nbconvert('pdf', true);
133 134 });
134 135
135 136 this.element.find('#rename_notebook').click(function () {
136 137 that.save_widget.rename_notebook();
137 138 });
138 139 this.element.find('#save_checkpoint').click(function () {
139 140 that.notebook.save_checkpoint();
140 141 });
141 142 this.element.find('#restore_checkpoint').click(function () {
142 143 });
143 144 this.element.find('#trust_notebook').click(function () {
144 145 that.notebook.trust_notebook();
145 146 });
146 147 this.events.on('trust_changed.Notebook', function (event, trusted) {
147 148 if (trusted) {
148 149 that.element.find('#trust_notebook')
149 150 .addClass("disabled")
150 151 .find("a").text("Trusted Notebook");
151 152 } else {
152 153 that.element.find('#trust_notebook')
153 154 .removeClass("disabled")
154 155 .find("a").text("Trust Notebook");
155 156 }
156 157 });
157 158 this.element.find('#kill_and_exit').click(function () {
158 159 that.notebook.session.delete();
159 160 setTimeout(function(){
160 161 // allow closing of new tabs in Chromium, impossible in FF
161 162 window.open('', '_self', '');
162 163 window.close();
163 164 }, 500);
164 165 });
165 166 // Edit
166 167 this.element.find('#cut_cell').click(function () {
167 168 that.notebook.cut_cell();
168 169 });
169 170 this.element.find('#copy_cell').click(function () {
170 171 that.notebook.copy_cell();
171 172 });
172 173 this.element.find('#delete_cell').click(function () {
173 174 that.notebook.delete_cell();
174 175 });
175 176 this.element.find('#undelete_cell').click(function () {
176 177 that.notebook.undelete_cell();
177 178 });
178 179 this.element.find('#split_cell').click(function () {
179 180 that.notebook.split_cell();
180 181 });
181 182 this.element.find('#merge_cell_above').click(function () {
182 183 that.notebook.merge_cell_above();
183 184 });
184 185 this.element.find('#merge_cell_below').click(function () {
185 186 that.notebook.merge_cell_below();
186 187 });
187 188 this.element.find('#move_cell_up').click(function () {
188 189 that.notebook.move_cell_up();
189 190 });
190 191 this.element.find('#move_cell_down').click(function () {
191 192 that.notebook.move_cell_down();
192 193 });
193 194 this.element.find('#edit_nb_metadata').click(function () {
194 195 that.notebook.edit_metadata();
195 196 });
196 197
197 198 // View
198 199 this.element.find('#toggle_header').click(function () {
199 200 $('div#header').toggle();
200 201 that.layout_manager.do_resize();
201 202 });
202 203 this.element.find('#toggle_toolbar').click(function () {
203 204 $('div#maintoolbar').toggle();
204 205 that.layout_manager.do_resize();
205 206 });
206 207 // Insert
207 208 this.element.find('#insert_cell_above').click(function () {
208 209 that.notebook.insert_cell_above('code');
209 210 that.notebook.select_prev();
210 211 });
211 212 this.element.find('#insert_cell_below').click(function () {
212 213 that.notebook.insert_cell_below('code');
213 214 that.notebook.select_next();
214 215 });
215 216 // Cell
216 217 this.element.find('#run_cell').click(function () {
217 218 that.notebook.execute_cell();
218 219 });
219 220 this.element.find('#run_cell_select_below').click(function () {
220 221 that.notebook.execute_cell_and_select_below();
221 222 });
222 223 this.element.find('#run_cell_insert_below').click(function () {
223 224 that.notebook.execute_cell_and_insert_below();
224 225 });
225 226 this.element.find('#run_all_cells').click(function () {
226 227 that.notebook.execute_all_cells();
227 228 });
228 229 this.element.find('#run_all_cells_above').click(function () {
229 230 that.notebook.execute_cells_above();
230 231 });
231 232 this.element.find('#run_all_cells_below').click(function () {
232 233 that.notebook.execute_cells_below();
233 234 });
234 235 this.element.find('#to_code').click(function () {
235 236 that.notebook.to_code();
236 237 });
237 238 this.element.find('#to_markdown').click(function () {
238 239 that.notebook.to_markdown();
239 240 });
240 241 this.element.find('#to_raw').click(function () {
241 242 that.notebook.to_raw();
242 243 });
243 244 this.element.find('#to_heading1').click(function () {
244 245 that.notebook.to_heading(undefined, 1);
245 246 });
246 247 this.element.find('#to_heading2').click(function () {
247 248 that.notebook.to_heading(undefined, 2);
248 249 });
249 250 this.element.find('#to_heading3').click(function () {
250 251 that.notebook.to_heading(undefined, 3);
251 252 });
252 253 this.element.find('#to_heading4').click(function () {
253 254 that.notebook.to_heading(undefined, 4);
254 255 });
255 256 this.element.find('#to_heading5').click(function () {
256 257 that.notebook.to_heading(undefined, 5);
257 258 });
258 259 this.element.find('#to_heading6').click(function () {
259 260 that.notebook.to_heading(undefined, 6);
260 261 });
261 262
262 263 this.element.find('#toggle_current_output').click(function () {
263 264 that.notebook.toggle_output();
264 265 });
265 266 this.element.find('#toggle_current_output_scroll').click(function () {
266 267 that.notebook.toggle_output_scroll();
267 268 });
268 269 this.element.find('#clear_current_output').click(function () {
269 270 that.notebook.clear_output();
270 271 });
271 272
272 273 this.element.find('#toggle_all_output').click(function () {
273 274 that.notebook.toggle_all_output();
274 275 });
275 276 this.element.find('#toggle_all_output_scroll').click(function () {
276 277 that.notebook.toggle_all_output_scroll();
277 278 });
278 279 this.element.find('#clear_all_output').click(function () {
279 280 that.notebook.clear_all_output();
280 281 });
281 282
282 283 // Kernel
283 284 this.element.find('#int_kernel').click(function () {
284 285 that.notebook.session.interrupt_kernel();
285 286 });
286 287 this.element.find('#restart_kernel').click(function () {
287 288 that.notebook.restart_kernel();
288 289 });
289 290 // Help
290 291 if (this.tour) {
291 292 this.element.find('#notebook_tour').click(function () {
292 293 that.tour.start();
293 294 });
294 295 } else {
295 296 this.element.find('#notebook_tour').addClass("disabled");
296 297 }
297 298 this.element.find('#keyboard_shortcuts').click(function () {
298 299 that.quick_help.show_keyboard_shortcuts();
299 300 });
300 301
301 302 this.update_restore_checkpoint(null);
302 303
303 304 this.events.on('checkpoints_listed.Notebook', function (event, data) {
304 305 that.update_restore_checkpoint(that.notebook.checkpoints);
305 306 });
306 307
307 308 this.events.on('checkpoint_created.Notebook', function (event, data) {
308 309 that.update_restore_checkpoint(that.notebook.checkpoints);
309 310 });
310 311 };
311 312
312 313 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
313 314 var ul = this.element.find("#restore_checkpoint").find("ul");
314 315 ul.empty();
315 316 if (!checkpoints || checkpoints.length === 0) {
316 317 ul.append(
317 318 $("<li/>")
318 319 .addClass("disabled")
319 320 .append(
320 321 $("<a/>")
321 322 .text("No checkpoints")
322 323 )
323 324 );
324 325 return;
325 326 }
326 327
327 328 var that = this;
328 329 checkpoints.map(function (checkpoint) {
329 330 var d = new Date(checkpoint.last_modified);
330 331 ul.append(
331 332 $("<li/>").append(
332 333 $("<a/>")
333 334 .attr("href", "#")
334 335 .text(d.format("mmm dd HH:MM:ss"))
335 336 .click(function () {
336 337 that.notebook.restore_checkpoint_dialog(checkpoint);
337 338 })
338 339 )
339 340 );
340 341 });
341 342 };
342 343
343 344 // Backwards compatability.
344 345 IPython.MenuBar = MenuBar;
345 346
346 347 return {'MenuBar': MenuBar};
347 348 });
@@ -1,2493 +1,2496 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'notebook/js/textcell',
10 10 'notebook/js/codecell',
11 11 'services/sessions/js/session',
12 12 'notebook/js/celltoolbar',
13 13 'components/marked/lib/marked',
14 14 'notebook/js/mathjaxutils',
15 15 'base/js/keyboard',
16 16 'components/jquery-ui/ui/minified/jquery-ui.min',
17 17 'components/bootstrap/js/bootstrap.min',
18 18 ], function (
19 19 IPython,
20 20 $,
21 21 utils,
22 22 dialog,
23 cells,
23 textcell,
24 24 codecell,
25 25 session,
26 26 celltoolbar,
27 27 marked,
28 28 mathjaxutils,
29 29 keyboard
30 30 ) {
31 31
32 /**
33 * A notebook contains and manages cells.
34 *
35 * @class Notebook
36 * @constructor
37 * @param {String} selector A jQuery selector for the notebook's DOM element
38 * @param {Object} [options] A config object
39 * @param {Object} [events] An events object
40 */
41 32 var Notebook = function (selector, options) {
33 // Constructor
34 //
35 // A notebook contains and manages cells.
36 //
37 // Parameters:
38 // selector: string
39 // options: dictionary
40 // Dictionary of keyword arguments.
41 // events: $(Events) instance
42 // keyboard_manager: KeyboardManager instance
43 // save_widget: SaveWidget instance
44 // config: dictionary
45 // base_url : string
46 // notebook_path : string
47 // notebook_name : string
42 48 this.config = options.config || {};
43 49 this.base_url = options.base_url;
44 50 this.notebook_path = options.notebook_path;
45 51 this.notebook_name = options.notebook_name;
46 52 this.events = options.events;
47 53 this.keyboard_manager = options.keyboard_manager;
48 54 this.save_widget = options.save_widget;
49 55 // TODO: This code smells (and the other `= this` line a couple lines down)
50 56 // We need a better way to deal with circular instance references.
51 57 this.keyboard_manager.notebook = this;
52 58 this.save_widget.notebook = this;
53 59
54 60 mathjaxutils.init();
55 61
56 62 if (marked) {
57 63 marked.setOptions({
58 64 gfm : true,
59 65 tables: true,
60 66 langPrefix: "language-",
61 67 highlight: function(code, lang) {
62 68 if (!lang) {
63 69 // no language, no highlight
64 70 return code;
65 71 }
66 72 var highlighted;
67 73 try {
68 74 highlighted = hljs.highlight(lang, code, false);
69 75 } catch(err) {
70 76 highlighted = hljs.highlightAuto(code);
71 77 }
72 78 return highlighted.value;
73 79 }
74 80 });
75 81 }
76 82
77 83 // Backwards compatability.
78 84 IPython.keyboard_manager = this.keyboard_manager;
79 85 IPython.save_widget = this.save_widget;
80 86 IPython.keyboard = this.keyboard;
81 87
82 88 this.element = $(selector);
83 89 this.element.scroll();
84 90 this.element.data("notebook", this);
85 91 this.next_prompt_number = 1;
86 92 this.session = null;
87 93 this.kernel = null;
88 94 this.clipboard = null;
89 95 this.undelete_backup = null;
90 96 this.undelete_index = null;
91 97 this.undelete_below = false;
92 98 this.paste_enabled = false;
93 99 // It is important to start out in command mode to match the intial mode
94 100 // of the KeyboardManager.
95 101 this.mode = 'command';
96 102 this.set_dirty(false);
97 103 this.metadata = {};
98 104 this._checkpoint_after_save = false;
99 105 this.last_checkpoint = null;
100 106 this.checkpoints = [];
101 107 this.autosave_interval = 0;
102 108 this.autosave_timer = null;
103 109 // autosave *at most* every two minutes
104 110 this.minimum_autosave_interval = 120000;
105 111 // single worksheet for now
106 112 this.worksheet_metadata = {};
107 113 this.notebook_name_blacklist_re = /[\/\\:]/;
108 114 this.nbformat = 3; // Increment this when changing the nbformat
109 115 this.nbformat_minor = 0; // Increment this when changing the nbformat
110 116 this.style();
111 117 this.create_elements();
112 118 this.bind_events();
113 119 this.save_notebook = function() { // don't allow save until notebook_loaded
114 120 this.save_notebook_error(null, null, "Load failed, save is disabled");
115 121 };
116 122 };
117 123
118 124 /**
119 125 * Tweak the notebook's CSS style.
120 126 *
121 127 * @method style
122 128 */
123 129 Notebook.prototype.style = function () {
124 130 $('div#notebook').addClass('border-box-sizing');
125 131 };
126 132
127 133 /**
128 134 * Create an HTML and CSS representation of the notebook.
129 135 *
130 136 * @method create_elements
131 137 */
132 138 Notebook.prototype.create_elements = function () {
133 139 var that = this;
134 140 this.element.attr('tabindex','-1');
135 141 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
136 142 // We add this end_space div to the end of the notebook div to:
137 143 // i) provide a margin between the last cell and the end of the notebook
138 144 // ii) to prevent the div from scrolling up when the last cell is being
139 145 // edited, but is too low on the page, which browsers will do automatically.
140 146 var end_space = $('<div/>').addClass('end_space');
141 147 end_space.dblclick(function (e) {
142 148 var ncells = that.ncells();
143 149 that.insert_cell_below('code',ncells-1);
144 150 });
145 151 this.element.append(this.container);
146 152 this.container.append(end_space);
147 153 };
148 154
149 155 /**
150 156 * Bind JavaScript events: key presses and custom IPython events.
151 157 *
152 158 * @method bind_events
153 159 */
154 160 Notebook.prototype.bind_events = function () {
155 161 var that = this;
156 162
157 163 this.events.on('set_next_input.Notebook', function (event, data) {
158 164 var index = that.find_cell_index(data.cell);
159 165 var new_cell = that.insert_cell_below('code',index);
160 166 new_cell.set_text(data.text);
161 167 that.dirty = true;
162 168 });
163 169
164 170 this.events.on('set_dirty.Notebook', function (event, data) {
165 171 that.dirty = data.value;
166 172 });
167 173
168 174 this.events.on('trust_changed.Notebook', function (event, data) {
169 175 that.trusted = data.value;
170 176 });
171 177
172 178 this.events.on('select.Cell', function (event, data) {
173 179 var index = that.find_cell_index(data.cell);
174 180 that.select(index);
175 181 });
176 182
177 183 this.events.on('edit_mode.Cell', function (event, data) {
178 184 that.handle_edit_mode(data.cell);
179 185 });
180 186
181 187 this.events.on('command_mode.Cell', function (event, data) {
182 188 that.handle_command_mode(data.cell);
183 189 });
184 190
185 191 this.events.on('status_autorestarting.Kernel', function () {
186 192 dialog.modal({
187 193 title: "Kernel Restarting",
188 194 body: "The kernel appears to have died. It will restart automatically.",
189 195 buttons: {
190 196 OK : {
191 197 class : "btn-primary"
192 198 }
193 199 }
194 200 });
195 201 });
196 202
197 203 var collapse_time = function (time) {
198 204 var app_height = $('#ipython-main-app').height(); // content height
199 205 var splitter_height = $('div#pager_splitter').outerHeight(true);
200 206 var new_height = app_height - splitter_height;
201 207 that.element.animate({height : new_height + 'px'}, time);
202 208 };
203 209
204 210 this.element.bind('collapse_pager', function (event, extrap) {
205 211 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
206 212 collapse_time(time);
207 213 });
208 214
209 215 var expand_time = function (time) {
210 216 var app_height = $('#ipython-main-app').height(); // content height
211 217 var splitter_height = $('div#pager_splitter').outerHeight(true);
212 218 var pager_height = $('div#pager').outerHeight(true);
213 219 var new_height = app_height - pager_height - splitter_height;
214 220 that.element.animate({height : new_height + 'px'}, time);
215 221 };
216 222
217 223 this.element.bind('expand_pager', function (event, extrap) {
218 224 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
219 225 expand_time(time);
220 226 });
221 227
222 228 // Firefox 22 broke $(window).on("beforeunload")
223 229 // I'm not sure why or how.
224 230 window.onbeforeunload = function (e) {
225 231 // TODO: Make killing the kernel configurable.
226 232 var kill_kernel = false;
227 233 if (kill_kernel) {
228 234 that.session.kill_kernel();
229 235 }
230 236 // if we are autosaving, trigger an autosave on nav-away.
231 237 // still warn, because if we don't the autosave may fail.
232 238 if (that.dirty) {
233 239 if ( that.autosave_interval ) {
234 240 // schedule autosave in a timeout
235 241 // this gives you a chance to forcefully discard changes
236 242 // by reloading the page if you *really* want to.
237 243 // the timer doesn't start until you *dismiss* the dialog.
238 244 setTimeout(function () {
239 245 if (that.dirty) {
240 246 that.save_notebook();
241 247 }
242 248 }, 1000);
243 249 return "Autosave in progress, latest changes may be lost.";
244 250 } else {
245 251 return "Unsaved changes will be lost.";
246 252 }
247 253 }
248 254 // Null is the *only* return value that will make the browser not
249 255 // pop up the "don't leave" dialog.
250 256 return null;
251 257 };
252 258 };
253 259
254 260 /**
255 261 * Set the dirty flag, and trigger the set_dirty.Notebook event
256 262 *
257 263 * @method set_dirty
258 264 */
259 265 Notebook.prototype.set_dirty = function (value) {
260 266 if (value === undefined) {
261 267 value = true;
262 268 }
263 269 if (this.dirty == value) {
264 270 return;
265 271 }
266 272 this.events.trigger('set_dirty.Notebook', {value: value});
267 273 };
268 274
269 275 /**
270 276 * Scroll the top of the page to a given cell.
271 277 *
272 278 * @method scroll_to_cell
273 279 * @param {Number} cell_number An index of the cell to view
274 280 * @param {Number} time Animation time in milliseconds
275 281 * @return {Number} Pixel offset from the top of the container
276 282 */
277 283 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
278 284 var cells = this.get_cells();
279 285 time = time || 0;
280 286 cell_number = Math.min(cells.length-1,cell_number);
281 287 cell_number = Math.max(0 ,cell_number);
282 288 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
283 289 this.element.animate({scrollTop:scroll_value}, time);
284 290 return scroll_value;
285 291 };
286 292
287 293 /**
288 294 * Scroll to the bottom of the page.
289 295 *
290 296 * @method scroll_to_bottom
291 297 */
292 298 Notebook.prototype.scroll_to_bottom = function () {
293 299 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
294 300 };
295 301
296 302 /**
297 303 * Scroll to the top of the page.
298 304 *
299 305 * @method scroll_to_top
300 306 */
301 307 Notebook.prototype.scroll_to_top = function () {
302 308 this.element.animate({scrollTop:0}, 0);
303 309 };
304 310
305 311 // Edit Notebook metadata
306 312
307 313 Notebook.prototype.edit_metadata = function () {
308 314 var that = this;
309 315 dialog.edit_metadata(this.metadata, function (md) {
310 316 that.metadata = md;
311 317 }, 'Notebook');
312 318 };
313 319
314 320 // Cell indexing, retrieval, etc.
315 321
316 322 /**
317 323 * Get all cell elements in the notebook.
318 324 *
319 325 * @method get_cell_elements
320 326 * @return {jQuery} A selector of all cell elements
321 327 */
322 328 Notebook.prototype.get_cell_elements = function () {
323 329 return this.container.children("div.cell");
324 330 };
325 331
326 332 /**
327 333 * Get a particular cell element.
328 334 *
329 335 * @method get_cell_element
330 336 * @param {Number} index An index of a cell to select
331 337 * @return {jQuery} A selector of the given cell.
332 338 */
333 339 Notebook.prototype.get_cell_element = function (index) {
334 340 var result = null;
335 341 var e = this.get_cell_elements().eq(index);
336 342 if (e.length !== 0) {
337 343 result = e;
338 344 }
339 345 return result;
340 346 };
341 347
342 348 /**
343 349 * Try to get a particular cell by msg_id.
344 350 *
345 351 * @method get_msg_cell
346 352 * @param {String} msg_id A message UUID
347 353 * @return {Cell} Cell or null if no cell was found.
348 354 */
349 355 Notebook.prototype.get_msg_cell = function (msg_id) {
350 356 return codecell.CodeCell.msg_cells[msg_id] || null;
351 357 };
352 358
353 359 /**
354 360 * Count the cells in this notebook.
355 361 *
356 362 * @method ncells
357 363 * @return {Number} The number of cells in this notebook
358 364 */
359 365 Notebook.prototype.ncells = function () {
360 366 return this.get_cell_elements().length;
361 367 };
362 368
363 369 /**
364 370 * Get all Cell objects in this notebook.
365 371 *
366 372 * @method get_cells
367 373 * @return {Array} This notebook's Cell objects
368 374 */
369 375 // TODO: we are often calling cells as cells()[i], which we should optimize
370 376 // to cells(i) or a new method.
371 377 Notebook.prototype.get_cells = function () {
372 378 return this.get_cell_elements().toArray().map(function (e) {
373 379 return $(e).data("cell");
374 380 });
375 381 };
376 382
377 383 /**
378 384 * Get a Cell object from this notebook.
379 385 *
380 386 * @method get_cell
381 387 * @param {Number} index An index of a cell to retrieve
382 388 * @return {Cell} A particular cell
383 389 */
384 390 Notebook.prototype.get_cell = function (index) {
385 391 var result = null;
386 392 var ce = this.get_cell_element(index);
387 393 if (ce !== null) {
388 394 result = ce.data('cell');
389 395 }
390 396 return result;
391 397 };
392 398
393 399 /**
394 400 * Get the cell below a given cell.
395 401 *
396 402 * @method get_next_cell
397 403 * @param {Cell} cell The provided cell
398 404 * @return {Cell} The next cell
399 405 */
400 406 Notebook.prototype.get_next_cell = function (cell) {
401 407 var result = null;
402 408 var index = this.find_cell_index(cell);
403 409 if (this.is_valid_cell_index(index+1)) {
404 410 result = this.get_cell(index+1);
405 411 }
406 412 return result;
407 413 };
408 414
409 415 /**
410 416 * Get the cell above a given cell.
411 417 *
412 418 * @method get_prev_cell
413 419 * @param {Cell} cell The provided cell
414 420 * @return {Cell} The previous cell
415 421 */
416 422 Notebook.prototype.get_prev_cell = function (cell) {
417 423 // TODO: off-by-one
418 424 // nb.get_prev_cell(nb.get_cell(1)) is null
419 425 var result = null;
420 426 var index = this.find_cell_index(cell);
421 427 if (index !== null && index > 1) {
422 428 result = this.get_cell(index-1);
423 429 }
424 430 return result;
425 431 };
426 432
427 433 /**
428 434 * Get the numeric index of a given cell.
429 435 *
430 436 * @method find_cell_index
431 437 * @param {Cell} cell The provided cell
432 438 * @return {Number} The cell's numeric index
433 439 */
434 440 Notebook.prototype.find_cell_index = function (cell) {
435 441 var result = null;
436 442 this.get_cell_elements().filter(function (index) {
437 443 if ($(this).data("cell") === cell) {
438 444 result = index;
439 445 }
440 446 });
441 447 return result;
442 448 };
443 449
444 450 /**
445 451 * Get a given index , or the selected index if none is provided.
446 452 *
447 453 * @method index_or_selected
448 454 * @param {Number} index A cell's index
449 455 * @return {Number} The given index, or selected index if none is provided.
450 456 */
451 457 Notebook.prototype.index_or_selected = function (index) {
452 458 var i;
453 459 if (index === undefined || index === null) {
454 460 i = this.get_selected_index();
455 461 if (i === null) {
456 462 i = 0;
457 463 }
458 464 } else {
459 465 i = index;
460 466 }
461 467 return i;
462 468 };
463 469
464 470 /**
465 471 * Get the currently selected cell.
466 472 * @method get_selected_cell
467 473 * @return {Cell} The selected cell
468 474 */
469 475 Notebook.prototype.get_selected_cell = function () {
470 476 var index = this.get_selected_index();
471 477 return this.get_cell(index);
472 478 };
473 479
474 480 /**
475 481 * Check whether a cell index is valid.
476 482 *
477 483 * @method is_valid_cell_index
478 484 * @param {Number} index A cell index
479 485 * @return True if the index is valid, false otherwise
480 486 */
481 487 Notebook.prototype.is_valid_cell_index = function (index) {
482 488 if (index !== null && index >= 0 && index < this.ncells()) {
483 489 return true;
484 490 } else {
485 491 return false;
486 492 }
487 493 };
488 494
489 495 /**
490 496 * Get the index of the currently selected cell.
491 497
492 498 * @method get_selected_index
493 499 * @return {Number} The selected cell's numeric index
494 500 */
495 501 Notebook.prototype.get_selected_index = function () {
496 502 var result = null;
497 503 this.get_cell_elements().filter(function (index) {
498 504 if ($(this).data("cell").selected === true) {
499 505 result = index;
500 506 }
501 507 });
502 508 return result;
503 509 };
504 510
505 511
506 512 // Cell selection.
507 513
508 514 /**
509 515 * Programmatically select a cell.
510 516 *
511 517 * @method select
512 518 * @param {Number} index A cell's index
513 519 * @return {Notebook} This notebook
514 520 */
515 521 Notebook.prototype.select = function (index) {
516 522 if (this.is_valid_cell_index(index)) {
517 523 var sindex = this.get_selected_index();
518 524 if (sindex !== null && index !== sindex) {
519 525 // If we are about to select a different cell, make sure we are
520 526 // first in command mode.
521 527 if (this.mode !== 'command') {
522 528 this.command_mode();
523 529 }
524 530 this.get_cell(sindex).unselect();
525 531 }
526 532 var cell = this.get_cell(index);
527 533 cell.select();
528 534 if (cell.cell_type === 'heading') {
529 535 this.events.trigger('selected_cell_type_changed.Notebook',
530 536 {'cell_type':cell.cell_type,level:cell.level}
531 537 );
532 538 } else {
533 539 this.events.trigger('selected_cell_type_changed.Notebook',
534 540 {'cell_type':cell.cell_type}
535 541 );
536 542 }
537 543 }
538 544 return this;
539 545 };
540 546
541 547 /**
542 548 * Programmatically select the next cell.
543 549 *
544 550 * @method select_next
545 551 * @return {Notebook} This notebook
546 552 */
547 553 Notebook.prototype.select_next = function () {
548 554 var index = this.get_selected_index();
549 555 this.select(index+1);
550 556 return this;
551 557 };
552 558
553 559 /**
554 560 * Programmatically select the previous cell.
555 561 *
556 562 * @method select_prev
557 563 * @return {Notebook} This notebook
558 564 */
559 565 Notebook.prototype.select_prev = function () {
560 566 var index = this.get_selected_index();
561 567 this.select(index-1);
562 568 return this;
563 569 };
564 570
565 571
566 572 // Edit/Command mode
567 573
568 574 /**
569 575 * Gets the index of the cell that is in edit mode.
570 576 *
571 577 * @method get_edit_index
572 578 *
573 579 * @return index {int}
574 580 **/
575 581 Notebook.prototype.get_edit_index = function () {
576 582 var result = null;
577 583 this.get_cell_elements().filter(function (index) {
578 584 if ($(this).data("cell").mode === 'edit') {
579 585 result = index;
580 586 }
581 587 });
582 588 return result;
583 589 };
584 590
585 591 /**
586 592 * Handle when a a cell blurs and the notebook should enter command mode.
587 593 *
588 594 * @method handle_command_mode
589 595 * @param [cell] {Cell} Cell to enter command mode on.
590 596 **/
591 597 Notebook.prototype.handle_command_mode = function (cell) {
592 598 if (this.mode !== 'command') {
593 599 cell.command_mode();
594 600 this.mode = 'command';
595 601 this.events.trigger('command_mode.Notebook');
596 602 this.keyboard_manager.command_mode();
597 603 }
598 604 };
599 605
600 606 /**
601 607 * Make the notebook enter command mode.
602 608 *
603 609 * @method command_mode
604 610 **/
605 611 Notebook.prototype.command_mode = function () {
606 612 var cell = this.get_cell(this.get_edit_index());
607 613 if (cell && this.mode !== 'command') {
608 614 // We don't call cell.command_mode, but rather call cell.focus_cell()
609 615 // which will blur and CM editor and trigger the call to
610 616 // handle_command_mode.
611 617 cell.focus_cell();
612 618 }
613 619 };
614 620
615 621 /**
616 622 * Handle when a cell fires it's edit_mode event.
617 623 *
618 624 * @method handle_edit_mode
619 625 * @param [cell] {Cell} Cell to enter edit mode on.
620 626 **/
621 627 Notebook.prototype.handle_edit_mode = function (cell) {
622 628 if (cell && this.mode !== 'edit') {
623 629 cell.edit_mode();
624 630 this.mode = 'edit';
625 631 this.events.trigger('edit_mode.Notebook');
626 632 this.keyboard_manager.edit_mode();
627 633 }
628 634 };
629 635
630 636 /**
631 637 * Make a cell enter edit mode.
632 638 *
633 639 * @method edit_mode
634 640 **/
635 641 Notebook.prototype.edit_mode = function () {
636 642 var cell = this.get_selected_cell();
637 643 if (cell && this.mode !== 'edit') {
638 644 cell.unrender();
639 645 cell.focus_editor();
640 646 }
641 647 };
642 648
643 649 /**
644 650 * Focus the currently selected cell.
645 651 *
646 652 * @method focus_cell
647 653 **/
648 654 Notebook.prototype.focus_cell = function () {
649 655 var cell = this.get_selected_cell();
650 656 if (cell === null) {return;} // No cell is selected
651 657 cell.focus_cell();
652 658 };
653 659
654 660 // Cell movement
655 661
656 662 /**
657 663 * Move given (or selected) cell up and select it.
658 664 *
659 665 * @method move_cell_up
660 666 * @param [index] {integer} cell index
661 667 * @return {Notebook} This notebook
662 668 **/
663 669 Notebook.prototype.move_cell_up = function (index) {
664 670 var i = this.index_or_selected(index);
665 671 if (this.is_valid_cell_index(i) && i > 0) {
666 672 var pivot = this.get_cell_element(i-1);
667 673 var tomove = this.get_cell_element(i);
668 674 if (pivot !== null && tomove !== null) {
669 675 tomove.detach();
670 676 pivot.before(tomove);
671 677 this.select(i-1);
672 678 var cell = this.get_selected_cell();
673 679 cell.focus_cell();
674 680 }
675 681 this.set_dirty(true);
676 682 }
677 683 return this;
678 684 };
679 685
680 686
681 687 /**
682 688 * Move given (or selected) cell down and select it
683 689 *
684 690 * @method move_cell_down
685 691 * @param [index] {integer} cell index
686 692 * @return {Notebook} This notebook
687 693 **/
688 694 Notebook.prototype.move_cell_down = function (index) {
689 695 var i = this.index_or_selected(index);
690 696 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
691 697 var pivot = this.get_cell_element(i+1);
692 698 var tomove = this.get_cell_element(i);
693 699 if (pivot !== null && tomove !== null) {
694 700 tomove.detach();
695 701 pivot.after(tomove);
696 702 this.select(i+1);
697 703 var cell = this.get_selected_cell();
698 704 cell.focus_cell();
699 705 }
700 706 }
701 707 this.set_dirty();
702 708 return this;
703 709 };
704 710
705 711
706 712 // Insertion, deletion.
707 713
708 714 /**
709 715 * Delete a cell from the notebook.
710 716 *
711 717 * @method delete_cell
712 718 * @param [index] A cell's numeric index
713 719 * @return {Notebook} This notebook
714 720 */
715 721 Notebook.prototype.delete_cell = function (index) {
716 722 var i = this.index_or_selected(index);
717 723 var cell = this.get_selected_cell();
718 724 this.undelete_backup = cell.toJSON();
719 725 $('#undelete_cell').removeClass('disabled');
720 726 if (this.is_valid_cell_index(i)) {
721 727 var old_ncells = this.ncells();
722 728 var ce = this.get_cell_element(i);
723 729 ce.remove();
724 730 if (i === 0) {
725 731 // Always make sure we have at least one cell.
726 732 if (old_ncells === 1) {
727 733 this.insert_cell_below('code');
728 734 }
729 735 this.select(0);
730 736 this.undelete_index = 0;
731 737 this.undelete_below = false;
732 738 } else if (i === old_ncells-1 && i !== 0) {
733 739 this.select(i-1);
734 740 this.undelete_index = i - 1;
735 741 this.undelete_below = true;
736 742 } else {
737 743 this.select(i);
738 744 this.undelete_index = i;
739 745 this.undelete_below = false;
740 746 }
741 747 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
742 748 this.set_dirty(true);
743 749 }
744 750 return this;
745 751 };
746 752
747 753 /**
748 754 * Restore the most recently deleted cell.
749 755 *
750 756 * @method undelete
751 757 */
752 758 Notebook.prototype.undelete_cell = function() {
753 759 if (this.undelete_backup !== null && this.undelete_index !== null) {
754 760 var current_index = this.get_selected_index();
755 761 if (this.undelete_index < current_index) {
756 762 current_index = current_index + 1;
757 763 }
758 764 if (this.undelete_index >= this.ncells()) {
759 765 this.select(this.ncells() - 1);
760 766 }
761 767 else {
762 768 this.select(this.undelete_index);
763 769 }
764 770 var cell_data = this.undelete_backup;
765 771 var new_cell = null;
766 772 if (this.undelete_below) {
767 773 new_cell = this.insert_cell_below(cell_data.cell_type);
768 774 } else {
769 775 new_cell = this.insert_cell_above(cell_data.cell_type);
770 776 }
771 777 new_cell.fromJSON(cell_data);
772 778 if (this.undelete_below) {
773 779 this.select(current_index+1);
774 780 } else {
775 781 this.select(current_index);
776 782 }
777 783 this.undelete_backup = null;
778 784 this.undelete_index = null;
779 785 }
780 786 $('#undelete_cell').addClass('disabled');
781 787 };
782 788
783 789 /**
784 790 * Insert a cell so that after insertion the cell is at given index.
785 791 *
786 792 * If cell type is not provided, it will default to the type of the
787 793 * currently active cell.
788 794 *
789 795 * Similar to insert_above, but index parameter is mandatory
790 796 *
791 797 * Index will be brought back into the accessible range [0,n]
792 798 *
793 799 * @method insert_cell_at_index
794 800 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
795 801 * @param [index] {int} a valid index where to insert cell
796 802 *
797 803 * @return cell {cell|null} created cell or null
798 804 **/
799 805 Notebook.prototype.insert_cell_at_index = function(type, index){
800 806
801 807 var ncells = this.ncells();
802 808 index = Math.min(index,ncells);
803 809 index = Math.max(index,0);
804 810 var cell = null;
805 811 type = type || this.get_selected_cell().cell_type;
806 812
807 813 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
808 814 var cell_options = {
809 base_url: base_url,
810 notebook_path: notebook_path,
811 notebook_name: notebook_name,
812 815 events: this.events,
813 816 config: this.config,
814 817 keyboard_manager: this.keyboard_manager,
815 818 notebook: this
816 819 };
817 820 if (type === 'code') {
818 821 cell = new codecell.CodeCell(this.kernel, cell_options);
819 822 cell.set_input_prompt();
820 823 } else if (type === 'markdown') {
821 cell = new cells.MarkdownCell(cell_options);
824 cell = new textcell.MarkdownCell(cell_options);
822 825 } else if (type === 'raw') {
823 cell = new cells.RawCell(cell_options);
826 cell = new textcell.RawCell(cell_options);
824 827 } else if (type === 'heading') {
825 cell = new cells.HeadingCell(cell_options);
828 cell = new textcell.HeadingCell(cell_options);
826 829 }
827 830
828 831 if(this._insert_element_at_index(cell.element,index)) {
829 832 cell.render();
830 833 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
831 834 cell.refresh();
832 835 // We used to select the cell after we refresh it, but there
833 836 // are now cases were this method is called where select is
834 837 // not appropriate. The selection logic should be handled by the
835 838 // caller of the the top level insert_cell methods.
836 839 this.set_dirty(true);
837 840 }
838 841 }
839 842 return cell;
840 843
841 844 };
842 845
843 846 /**
844 847 * Insert an element at given cell index.
845 848 *
846 849 * @method _insert_element_at_index
847 850 * @param element {dom element} a cell element
848 851 * @param [index] {int} a valid index where to inser cell
849 852 * @private
850 853 *
851 854 * return true if everything whent fine.
852 855 **/
853 856 Notebook.prototype._insert_element_at_index = function(element, index){
854 857 if (element === undefined){
855 858 return false;
856 859 }
857 860
858 861 var ncells = this.ncells();
859 862
860 863 if (ncells === 0) {
861 864 // special case append if empty
862 865 this.element.find('div.end_space').before(element);
863 866 } else if ( ncells === index ) {
864 867 // special case append it the end, but not empty
865 868 this.get_cell_element(index-1).after(element);
866 869 } else if (this.is_valid_cell_index(index)) {
867 870 // otherwise always somewhere to append to
868 871 this.get_cell_element(index).before(element);
869 872 } else {
870 873 return false;
871 874 }
872 875
873 876 if (this.undelete_index !== null && index <= this.undelete_index) {
874 877 this.undelete_index = this.undelete_index + 1;
875 878 this.set_dirty(true);
876 879 }
877 880 return true;
878 881 };
879 882
880 883 /**
881 884 * Insert a cell of given type above given index, or at top
882 885 * of notebook if index smaller than 0.
883 886 *
884 887 * default index value is the one of currently selected cell
885 888 *
886 889 * @method insert_cell_above
887 890 * @param [type] {string} cell type
888 891 * @param [index] {integer}
889 892 *
890 893 * @return handle to created cell or null
891 894 **/
892 895 Notebook.prototype.insert_cell_above = function (type, index) {
893 896 index = this.index_or_selected(index);
894 897 return this.insert_cell_at_index(type, index);
895 898 };
896 899
897 900 /**
898 901 * Insert a cell of given type below given index, or at bottom
899 902 * of notebook if index greater than number of cells
900 903 *
901 904 * default index value is the one of currently selected cell
902 905 *
903 906 * @method insert_cell_below
904 907 * @param [type] {string} cell type
905 908 * @param [index] {integer}
906 909 *
907 910 * @return handle to created cell or null
908 911 *
909 912 **/
910 913 Notebook.prototype.insert_cell_below = function (type, index) {
911 914 index = this.index_or_selected(index);
912 915 return this.insert_cell_at_index(type, index+1);
913 916 };
914 917
915 918
916 919 /**
917 920 * Insert cell at end of notebook
918 921 *
919 922 * @method insert_cell_at_bottom
920 923 * @param {String} type cell type
921 924 *
922 925 * @return the added cell; or null
923 926 **/
924 927 Notebook.prototype.insert_cell_at_bottom = function (type){
925 928 var len = this.ncells();
926 929 return this.insert_cell_below(type,len-1);
927 930 };
928 931
929 932 /**
930 933 * Turn a cell into a code cell.
931 934 *
932 935 * @method to_code
933 936 * @param {Number} [index] A cell's index
934 937 */
935 938 Notebook.prototype.to_code = function (index) {
936 939 var i = this.index_or_selected(index);
937 940 if (this.is_valid_cell_index(i)) {
938 941 var source_element = this.get_cell_element(i);
939 942 var source_cell = source_element.data("cell");
940 943 if (!(source_cell instanceof codecell.CodeCell)) {
941 944 var target_cell = this.insert_cell_below('code',i);
942 945 var text = source_cell.get_text();
943 946 if (text === source_cell.placeholder) {
944 947 text = '';
945 948 }
946 949 target_cell.set_text(text);
947 950 // make this value the starting point, so that we can only undo
948 951 // to this state, instead of a blank cell
949 952 target_cell.code_mirror.clearHistory();
950 953 source_element.remove();
951 954 this.select(i);
952 955 var cursor = source_cell.code_mirror.getCursor();
953 956 target_cell.code_mirror.setCursor(cursor);
954 957 this.set_dirty(true);
955 958 }
956 959 }
957 960 };
958 961
959 962 /**
960 963 * Turn a cell into a Markdown cell.
961 964 *
962 965 * @method to_markdown
963 966 * @param {Number} [index] A cell's index
964 967 */
965 968 Notebook.prototype.to_markdown = function (index) {
966 969 var i = this.index_or_selected(index);
967 970 if (this.is_valid_cell_index(i)) {
968 971 var source_element = this.get_cell_element(i);
969 972 var source_cell = source_element.data("cell");
970 if (!(source_cell instanceof cells.MarkdownCell)) {
973 if (!(source_cell instanceof textcell.MarkdownCell)) {
971 974 var target_cell = this.insert_cell_below('markdown',i);
972 975 var text = source_cell.get_text();
973 976 if (text === source_cell.placeholder) {
974 977 text = '';
975 978 }
976 979 // We must show the editor before setting its contents
977 980 target_cell.unrender();
978 981 target_cell.set_text(text);
979 982 // make this value the starting point, so that we can only undo
980 983 // to this state, instead of a blank cell
981 984 target_cell.code_mirror.clearHistory();
982 985 source_element.remove();
983 986 this.select(i);
984 if ((source_cell instanceof cells.TextCell) && source_cell.rendered) {
987 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
985 988 target_cell.render();
986 989 }
987 990 var cursor = source_cell.code_mirror.getCursor();
988 991 target_cell.code_mirror.setCursor(cursor);
989 992 this.set_dirty(true);
990 993 }
991 994 }
992 995 };
993 996
994 997 /**
995 998 * Turn a cell into a raw text cell.
996 999 *
997 1000 * @method to_raw
998 1001 * @param {Number} [index] A cell's index
999 1002 */
1000 1003 Notebook.prototype.to_raw = function (index) {
1001 1004 var i = this.index_or_selected(index);
1002 1005 if (this.is_valid_cell_index(i)) {
1003 1006 var source_element = this.get_cell_element(i);
1004 1007 var source_cell = source_element.data("cell");
1005 1008 var target_cell = null;
1006 if (!(source_cell instanceof cells.RawCell)) {
1009 if (!(source_cell instanceof textcell.RawCell)) {
1007 1010 target_cell = this.insert_cell_below('raw',i);
1008 1011 var text = source_cell.get_text();
1009 1012 if (text === source_cell.placeholder) {
1010 1013 text = '';
1011 1014 }
1012 1015 // We must show the editor before setting its contents
1013 1016 target_cell.unrender();
1014 1017 target_cell.set_text(text);
1015 1018 // make this value the starting point, so that we can only undo
1016 1019 // to this state, instead of a blank cell
1017 1020 target_cell.code_mirror.clearHistory();
1018 1021 source_element.remove();
1019 1022 this.select(i);
1020 1023 var cursor = source_cell.code_mirror.getCursor();
1021 1024 target_cell.code_mirror.setCursor(cursor);
1022 1025 this.set_dirty(true);
1023 1026 }
1024 1027 }
1025 1028 };
1026 1029
1027 1030 /**
1028 1031 * Turn a cell into a heading cell.
1029 1032 *
1030 1033 * @method to_heading
1031 1034 * @param {Number} [index] A cell's index
1032 1035 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1033 1036 */
1034 1037 Notebook.prototype.to_heading = function (index, level) {
1035 1038 level = level || 1;
1036 1039 var i = this.index_or_selected(index);
1037 1040 if (this.is_valid_cell_index(i)) {
1038 1041 var source_element = this.get_cell_element(i);
1039 1042 var source_cell = source_element.data("cell");
1040 1043 var target_cell = null;
1041 if (source_cell instanceof cells.HeadingCell) {
1044 if (source_cell instanceof textcell.HeadingCell) {
1042 1045 source_cell.set_level(level);
1043 1046 } else {
1044 1047 target_cell = this.insert_cell_below('heading',i);
1045 1048 var text = source_cell.get_text();
1046 1049 if (text === source_cell.placeholder) {
1047 1050 text = '';
1048 1051 }
1049 1052 // We must show the editor before setting its contents
1050 1053 target_cell.set_level(level);
1051 1054 target_cell.unrender();
1052 1055 target_cell.set_text(text);
1053 1056 // make this value the starting point, so that we can only undo
1054 1057 // to this state, instead of a blank cell
1055 1058 target_cell.code_mirror.clearHistory();
1056 1059 source_element.remove();
1057 1060 this.select(i);
1058 1061 var cursor = source_cell.code_mirror.getCursor();
1059 1062 target_cell.code_mirror.setCursor(cursor);
1060 if ((source_cell instanceof cells.TextCell) && source_cell.rendered) {
1063 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1061 1064 target_cell.render();
1062 1065 }
1063 1066 }
1064 1067 this.set_dirty(true);
1065 1068 this.events.trigger('selected_cell_type_changed.Notebook',
1066 1069 {'cell_type':'heading',level:level}
1067 1070 );
1068 1071 }
1069 1072 };
1070 1073
1071 1074
1072 1075 // Cut/Copy/Paste
1073 1076
1074 1077 /**
1075 1078 * Enable UI elements for pasting cells.
1076 1079 *
1077 1080 * @method enable_paste
1078 1081 */
1079 1082 Notebook.prototype.enable_paste = function () {
1080 1083 var that = this;
1081 1084 if (!this.paste_enabled) {
1082 1085 $('#paste_cell_replace').removeClass('disabled')
1083 1086 .on('click', function () {that.paste_cell_replace();});
1084 1087 $('#paste_cell_above').removeClass('disabled')
1085 1088 .on('click', function () {that.paste_cell_above();});
1086 1089 $('#paste_cell_below').removeClass('disabled')
1087 1090 .on('click', function () {that.paste_cell_below();});
1088 1091 this.paste_enabled = true;
1089 1092 }
1090 1093 };
1091 1094
1092 1095 /**
1093 1096 * Disable UI elements for pasting cells.
1094 1097 *
1095 1098 * @method disable_paste
1096 1099 */
1097 1100 Notebook.prototype.disable_paste = function () {
1098 1101 if (this.paste_enabled) {
1099 1102 $('#paste_cell_replace').addClass('disabled').off('click');
1100 1103 $('#paste_cell_above').addClass('disabled').off('click');
1101 1104 $('#paste_cell_below').addClass('disabled').off('click');
1102 1105 this.paste_enabled = false;
1103 1106 }
1104 1107 };
1105 1108
1106 1109 /**
1107 1110 * Cut a cell.
1108 1111 *
1109 1112 * @method cut_cell
1110 1113 */
1111 1114 Notebook.prototype.cut_cell = function () {
1112 1115 this.copy_cell();
1113 1116 this.delete_cell();
1114 1117 };
1115 1118
1116 1119 /**
1117 1120 * Copy a cell.
1118 1121 *
1119 1122 * @method copy_cell
1120 1123 */
1121 1124 Notebook.prototype.copy_cell = function () {
1122 1125 var cell = this.get_selected_cell();
1123 1126 this.clipboard = cell.toJSON();
1124 1127 this.enable_paste();
1125 1128 };
1126 1129
1127 1130 /**
1128 1131 * Replace the selected cell with a cell in the clipboard.
1129 1132 *
1130 1133 * @method paste_cell_replace
1131 1134 */
1132 1135 Notebook.prototype.paste_cell_replace = function () {
1133 1136 if (this.clipboard !== null && this.paste_enabled) {
1134 1137 var cell_data = this.clipboard;
1135 1138 var new_cell = this.insert_cell_above(cell_data.cell_type);
1136 1139 new_cell.fromJSON(cell_data);
1137 1140 var old_cell = this.get_next_cell(new_cell);
1138 1141 this.delete_cell(this.find_cell_index(old_cell));
1139 1142 this.select(this.find_cell_index(new_cell));
1140 1143 }
1141 1144 };
1142 1145
1143 1146 /**
1144 1147 * Paste a cell from the clipboard above the selected cell.
1145 1148 *
1146 1149 * @method paste_cell_above
1147 1150 */
1148 1151 Notebook.prototype.paste_cell_above = function () {
1149 1152 if (this.clipboard !== null && this.paste_enabled) {
1150 1153 var cell_data = this.clipboard;
1151 1154 var new_cell = this.insert_cell_above(cell_data.cell_type);
1152 1155 new_cell.fromJSON(cell_data);
1153 1156 new_cell.focus_cell();
1154 1157 }
1155 1158 };
1156 1159
1157 1160 /**
1158 1161 * Paste a cell from the clipboard below the selected cell.
1159 1162 *
1160 1163 * @method paste_cell_below
1161 1164 */
1162 1165 Notebook.prototype.paste_cell_below = function () {
1163 1166 if (this.clipboard !== null && this.paste_enabled) {
1164 1167 var cell_data = this.clipboard;
1165 1168 var new_cell = this.insert_cell_below(cell_data.cell_type);
1166 1169 new_cell.fromJSON(cell_data);
1167 1170 new_cell.focus_cell();
1168 1171 }
1169 1172 };
1170 1173
1171 1174 // Split/merge
1172 1175
1173 1176 /**
1174 1177 * Split the selected cell into two, at the cursor.
1175 1178 *
1176 1179 * @method split_cell
1177 1180 */
1178 1181 Notebook.prototype.split_cell = function () {
1179 var mdc = cells.MarkdownCell;
1180 var rc = cells.RawCell;
1182 var mdc = textcell.MarkdownCell;
1183 var rc = textcell.RawCell;
1181 1184 var cell = this.get_selected_cell();
1182 1185 if (cell.is_splittable()) {
1183 1186 var texta = cell.get_pre_cursor();
1184 1187 var textb = cell.get_post_cursor();
1185 1188 if (cell instanceof codecell.CodeCell) {
1186 1189 // In this case the operations keep the notebook in its existing mode
1187 1190 // so we don't need to do any post-op mode changes.
1188 1191 cell.set_text(textb);
1189 1192 var new_cell = this.insert_cell_above('code');
1190 1193 new_cell.set_text(texta);
1191 1194 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1192 1195 // We know cell is !rendered so we can use set_text.
1193 1196 cell.set_text(textb);
1194 1197 var new_cell = this.insert_cell_above(cell.cell_type);
1195 1198 // Unrender the new cell so we can call set_text.
1196 1199 new_cell.unrender();
1197 1200 new_cell.set_text(texta);
1198 1201 }
1199 1202 }
1200 1203 };
1201 1204
1202 1205 /**
1203 1206 * Combine the selected cell into the cell above it.
1204 1207 *
1205 1208 * @method merge_cell_above
1206 1209 */
1207 1210 Notebook.prototype.merge_cell_above = function () {
1208 var mdc = cells.MarkdownCell;
1209 var rc = cells.RawCell;
1211 var mdc = textcell.MarkdownCell;
1212 var rc = textcell.RawCell;
1210 1213 var index = this.get_selected_index();
1211 1214 var cell = this.get_cell(index);
1212 1215 var render = cell.rendered;
1213 1216 if (!cell.is_mergeable()) {
1214 1217 return;
1215 1218 }
1216 1219 if (index > 0) {
1217 1220 var upper_cell = this.get_cell(index-1);
1218 1221 if (!upper_cell.is_mergeable()) {
1219 1222 return;
1220 1223 }
1221 1224 var upper_text = upper_cell.get_text();
1222 1225 var text = cell.get_text();
1223 1226 if (cell instanceof codecell.CodeCell) {
1224 1227 cell.set_text(upper_text+'\n'+text);
1225 1228 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1226 1229 cell.unrender(); // Must unrender before we set_text.
1227 1230 cell.set_text(upper_text+'\n\n'+text);
1228 1231 if (render) {
1229 1232 // The rendered state of the final cell should match
1230 1233 // that of the original selected cell;
1231 1234 cell.render();
1232 1235 }
1233 1236 }
1234 1237 this.delete_cell(index-1);
1235 1238 this.select(this.find_cell_index(cell));
1236 1239 }
1237 1240 };
1238 1241
1239 1242 /**
1240 1243 * Combine the selected cell into the cell below it.
1241 1244 *
1242 1245 * @method merge_cell_below
1243 1246 */
1244 1247 Notebook.prototype.merge_cell_below = function () {
1245 var mdc = cells.MarkdownCell;
1246 var rc = cells.RawCell;
1248 var mdc = textcell.MarkdownCell;
1249 var rc = textcell.RawCell;
1247 1250 var index = this.get_selected_index();
1248 1251 var cell = this.get_cell(index);
1249 1252 var render = cell.rendered;
1250 1253 if (!cell.is_mergeable()) {
1251 1254 return;
1252 1255 }
1253 1256 if (index < this.ncells()-1) {
1254 1257 var lower_cell = this.get_cell(index+1);
1255 1258 if (!lower_cell.is_mergeable()) {
1256 1259 return;
1257 1260 }
1258 1261 var lower_text = lower_cell.get_text();
1259 1262 var text = cell.get_text();
1260 1263 if (cell instanceof codecell.CodeCell) {
1261 1264 cell.set_text(text+'\n'+lower_text);
1262 1265 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1263 1266 cell.unrender(); // Must unrender before we set_text.
1264 1267 cell.set_text(text+'\n\n'+lower_text);
1265 1268 if (render) {
1266 1269 // The rendered state of the final cell should match
1267 1270 // that of the original selected cell;
1268 1271 cell.render();
1269 1272 }
1270 1273 }
1271 1274 this.delete_cell(index+1);
1272 1275 this.select(this.find_cell_index(cell));
1273 1276 }
1274 1277 };
1275 1278
1276 1279
1277 1280 // Cell collapsing and output clearing
1278 1281
1279 1282 /**
1280 1283 * Hide a cell's output.
1281 1284 *
1282 1285 * @method collapse_output
1283 1286 * @param {Number} index A cell's numeric index
1284 1287 */
1285 1288 Notebook.prototype.collapse_output = function (index) {
1286 1289 var i = this.index_or_selected(index);
1287 1290 var cell = this.get_cell(i);
1288 1291 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1289 1292 cell.collapse_output();
1290 1293 this.set_dirty(true);
1291 1294 }
1292 1295 };
1293 1296
1294 1297 /**
1295 1298 * Hide each code cell's output area.
1296 1299 *
1297 1300 * @method collapse_all_output
1298 1301 */
1299 1302 Notebook.prototype.collapse_all_output = function () {
1300 1303 $.map(this.get_cells(), function (cell, i) {
1301 1304 if (cell instanceof codecell.CodeCell) {
1302 1305 cell.collapse_output();
1303 1306 }
1304 1307 });
1305 1308 // this should not be set if the `collapse` key is removed from nbformat
1306 1309 this.set_dirty(true);
1307 1310 };
1308 1311
1309 1312 /**
1310 1313 * Show a cell's output.
1311 1314 *
1312 1315 * @method expand_output
1313 1316 * @param {Number} index A cell's numeric index
1314 1317 */
1315 1318 Notebook.prototype.expand_output = function (index) {
1316 1319 var i = this.index_or_selected(index);
1317 1320 var cell = this.get_cell(i);
1318 1321 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1319 1322 cell.expand_output();
1320 1323 this.set_dirty(true);
1321 1324 }
1322 1325 };
1323 1326
1324 1327 /**
1325 1328 * Expand each code cell's output area, and remove scrollbars.
1326 1329 *
1327 1330 * @method expand_all_output
1328 1331 */
1329 1332 Notebook.prototype.expand_all_output = function () {
1330 1333 $.map(this.get_cells(), function (cell, i) {
1331 1334 if (cell instanceof codecell.CodeCell) {
1332 1335 cell.expand_output();
1333 1336 }
1334 1337 });
1335 1338 // this should not be set if the `collapse` key is removed from nbformat
1336 1339 this.set_dirty(true);
1337 1340 };
1338 1341
1339 1342 /**
1340 1343 * Clear the selected CodeCell's output area.
1341 1344 *
1342 1345 * @method clear_output
1343 1346 * @param {Number} index A cell's numeric index
1344 1347 */
1345 1348 Notebook.prototype.clear_output = function (index) {
1346 1349 var i = this.index_or_selected(index);
1347 1350 var cell = this.get_cell(i);
1348 1351 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1349 1352 cell.clear_output();
1350 1353 this.set_dirty(true);
1351 1354 }
1352 1355 };
1353 1356
1354 1357 /**
1355 1358 * Clear each code cell's output area.
1356 1359 *
1357 1360 * @method clear_all_output
1358 1361 */
1359 1362 Notebook.prototype.clear_all_output = function () {
1360 1363 $.map(this.get_cells(), function (cell, i) {
1361 1364 if (cell instanceof codecell.CodeCell) {
1362 1365 cell.clear_output();
1363 1366 }
1364 1367 });
1365 1368 this.set_dirty(true);
1366 1369 };
1367 1370
1368 1371 /**
1369 1372 * Scroll the selected CodeCell's output area.
1370 1373 *
1371 1374 * @method scroll_output
1372 1375 * @param {Number} index A cell's numeric index
1373 1376 */
1374 1377 Notebook.prototype.scroll_output = function (index) {
1375 1378 var i = this.index_or_selected(index);
1376 1379 var cell = this.get_cell(i);
1377 1380 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1378 1381 cell.scroll_output();
1379 1382 this.set_dirty(true);
1380 1383 }
1381 1384 };
1382 1385
1383 1386 /**
1384 1387 * Expand each code cell's output area, and add a scrollbar for long output.
1385 1388 *
1386 1389 * @method scroll_all_output
1387 1390 */
1388 1391 Notebook.prototype.scroll_all_output = function () {
1389 1392 $.map(this.get_cells(), function (cell, i) {
1390 1393 if (cell instanceof codecell.CodeCell) {
1391 1394 cell.scroll_output();
1392 1395 }
1393 1396 });
1394 1397 // this should not be set if the `collapse` key is removed from nbformat
1395 1398 this.set_dirty(true);
1396 1399 };
1397 1400
1398 1401 /** Toggle whether a cell's output is collapsed or expanded.
1399 1402 *
1400 1403 * @method toggle_output
1401 1404 * @param {Number} index A cell's numeric index
1402 1405 */
1403 1406 Notebook.prototype.toggle_output = function (index) {
1404 1407 var i = this.index_or_selected(index);
1405 1408 var cell = this.get_cell(i);
1406 1409 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1407 1410 cell.toggle_output();
1408 1411 this.set_dirty(true);
1409 1412 }
1410 1413 };
1411 1414
1412 1415 /**
1413 1416 * Hide/show the output of all cells.
1414 1417 *
1415 1418 * @method toggle_all_output
1416 1419 */
1417 1420 Notebook.prototype.toggle_all_output = function () {
1418 1421 $.map(this.get_cells(), function (cell, i) {
1419 1422 if (cell instanceof codecell.CodeCell) {
1420 1423 cell.toggle_output();
1421 1424 }
1422 1425 });
1423 1426 // this should not be set if the `collapse` key is removed from nbformat
1424 1427 this.set_dirty(true);
1425 1428 };
1426 1429
1427 1430 /**
1428 1431 * Toggle a scrollbar for long cell outputs.
1429 1432 *
1430 1433 * @method toggle_output_scroll
1431 1434 * @param {Number} index A cell's numeric index
1432 1435 */
1433 1436 Notebook.prototype.toggle_output_scroll = function (index) {
1434 1437 var i = this.index_or_selected(index);
1435 1438 var cell = this.get_cell(i);
1436 1439 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1437 1440 cell.toggle_output_scroll();
1438 1441 this.set_dirty(true);
1439 1442 }
1440 1443 };
1441 1444
1442 1445 /**
1443 1446 * Toggle the scrolling of long output on all cells.
1444 1447 *
1445 1448 * @method toggle_all_output_scrolling
1446 1449 */
1447 1450 Notebook.prototype.toggle_all_output_scroll = function () {
1448 1451 $.map(this.get_cells(), function (cell, i) {
1449 1452 if (cell instanceof codecell.CodeCell) {
1450 1453 cell.toggle_output_scroll();
1451 1454 }
1452 1455 });
1453 1456 // this should not be set if the `collapse` key is removed from nbformat
1454 1457 this.set_dirty(true);
1455 1458 };
1456 1459
1457 1460 // Other cell functions: line numbers, ...
1458 1461
1459 1462 /**
1460 1463 * Toggle line numbers in the selected cell's input area.
1461 1464 *
1462 1465 * @method cell_toggle_line_numbers
1463 1466 */
1464 1467 Notebook.prototype.cell_toggle_line_numbers = function() {
1465 1468 this.get_selected_cell().toggle_line_numbers();
1466 1469 };
1467 1470
1468 1471 // Session related things
1469 1472
1470 1473 /**
1471 1474 * Start a new session and set it on each code cell.
1472 1475 *
1473 1476 * @method start_session
1474 1477 */
1475 1478 Notebook.prototype.start_session = function () {
1476 1479 this.session = new session.Session(this, {
1477 1480 base_url: base_url,
1478 1481 notebook_path: notebook_path,
1479 1482 notebook_name: notebook_name,
1480 1483 notebook: this});
1481 1484 this.session.start($.proxy(this._session_started, this));
1482 1485 };
1483 1486
1484 1487
1485 1488 /**
1486 1489 * Once a session is started, link the code cells to the kernel and pass the
1487 1490 * comm manager to the widget manager
1488 1491 *
1489 1492 */
1490 1493 Notebook.prototype._session_started = function(){
1491 1494 this.kernel = this.session.kernel;
1492 1495 var ncells = this.ncells();
1493 1496 for (var i=0; i<ncells; i++) {
1494 1497 var cell = this.get_cell(i);
1495 1498 if (cell instanceof codecell.CodeCell) {
1496 1499 cell.set_kernel(this.session.kernel);
1497 1500 }
1498 1501 }
1499 1502 };
1500 1503
1501 1504 /**
1502 1505 * Prompt the user to restart the IPython kernel.
1503 1506 *
1504 1507 * @method restart_kernel
1505 1508 */
1506 1509 Notebook.prototype.restart_kernel = function () {
1507 1510 var that = this;
1508 1511 dialog.modal({
1509 1512 title : "Restart kernel or continue running?",
1510 1513 body : $("<p/>").text(
1511 1514 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1512 1515 ),
1513 1516 buttons : {
1514 1517 "Continue running" : {},
1515 1518 "Restart" : {
1516 1519 "class" : "btn-danger",
1517 1520 "click" : function() {
1518 1521 that.session.restart_kernel();
1519 1522 }
1520 1523 }
1521 1524 }
1522 1525 });
1523 1526 };
1524 1527
1525 1528 /**
1526 1529 * Execute or render cell outputs and go into command mode.
1527 1530 *
1528 1531 * @method execute_cell
1529 1532 */
1530 1533 Notebook.prototype.execute_cell = function () {
1531 1534 // mode = shift, ctrl, alt
1532 1535 var cell = this.get_selected_cell();
1533 1536 var cell_index = this.find_cell_index(cell);
1534 1537
1535 1538 cell.execute();
1536 1539 this.command_mode();
1537 1540 this.set_dirty(true);
1538 1541 };
1539 1542
1540 1543 /**
1541 1544 * Execute or render cell outputs and insert a new cell below.
1542 1545 *
1543 1546 * @method execute_cell_and_insert_below
1544 1547 */
1545 1548 Notebook.prototype.execute_cell_and_insert_below = function () {
1546 1549 var cell = this.get_selected_cell();
1547 1550 var cell_index = this.find_cell_index(cell);
1548 1551
1549 1552 cell.execute();
1550 1553
1551 1554 // If we are at the end always insert a new cell and return
1552 1555 if (cell_index === (this.ncells()-1)) {
1553 1556 this.command_mode();
1554 1557 this.insert_cell_below();
1555 1558 this.select(cell_index+1);
1556 1559 this.edit_mode();
1557 1560 this.scroll_to_bottom();
1558 1561 this.set_dirty(true);
1559 1562 return;
1560 1563 }
1561 1564
1562 1565 this.command_mode();
1563 1566 this.insert_cell_below();
1564 1567 this.select(cell_index+1);
1565 1568 this.edit_mode();
1566 1569 this.set_dirty(true);
1567 1570 };
1568 1571
1569 1572 /**
1570 1573 * Execute or render cell outputs and select the next cell.
1571 1574 *
1572 1575 * @method execute_cell_and_select_below
1573 1576 */
1574 1577 Notebook.prototype.execute_cell_and_select_below = function () {
1575 1578
1576 1579 var cell = this.get_selected_cell();
1577 1580 var cell_index = this.find_cell_index(cell);
1578 1581
1579 1582 cell.execute();
1580 1583
1581 1584 // If we are at the end always insert a new cell and return
1582 1585 if (cell_index === (this.ncells()-1)) {
1583 1586 this.command_mode();
1584 1587 this.insert_cell_below();
1585 1588 this.select(cell_index+1);
1586 1589 this.edit_mode();
1587 1590 this.scroll_to_bottom();
1588 1591 this.set_dirty(true);
1589 1592 return;
1590 1593 }
1591 1594
1592 1595 this.command_mode();
1593 1596 this.select(cell_index+1);
1594 1597 this.focus_cell();
1595 1598 this.set_dirty(true);
1596 1599 };
1597 1600
1598 1601 /**
1599 1602 * Execute all cells below the selected cell.
1600 1603 *
1601 1604 * @method execute_cells_below
1602 1605 */
1603 1606 Notebook.prototype.execute_cells_below = function () {
1604 1607 this.execute_cell_range(this.get_selected_index(), this.ncells());
1605 1608 this.scroll_to_bottom();
1606 1609 };
1607 1610
1608 1611 /**
1609 1612 * Execute all cells above the selected cell.
1610 1613 *
1611 1614 * @method execute_cells_above
1612 1615 */
1613 1616 Notebook.prototype.execute_cells_above = function () {
1614 1617 this.execute_cell_range(0, this.get_selected_index());
1615 1618 };
1616 1619
1617 1620 /**
1618 1621 * Execute all cells.
1619 1622 *
1620 1623 * @method execute_all_cells
1621 1624 */
1622 1625 Notebook.prototype.execute_all_cells = function () {
1623 1626 this.execute_cell_range(0, this.ncells());
1624 1627 this.scroll_to_bottom();
1625 1628 };
1626 1629
1627 1630 /**
1628 1631 * Execute a contiguous range of cells.
1629 1632 *
1630 1633 * @method execute_cell_range
1631 1634 * @param {Number} start Index of the first cell to execute (inclusive)
1632 1635 * @param {Number} end Index of the last cell to execute (exclusive)
1633 1636 */
1634 1637 Notebook.prototype.execute_cell_range = function (start, end) {
1635 1638 this.command_mode();
1636 1639 for (var i=start; i<end; i++) {
1637 1640 this.select(i);
1638 1641 this.execute_cell();
1639 1642 }
1640 1643 };
1641 1644
1642 1645 // Persistance and loading
1643 1646
1644 1647 /**
1645 1648 * Getter method for this notebook's name.
1646 1649 *
1647 1650 * @method get_notebook_name
1648 1651 * @return {String} This notebook's name (excluding file extension)
1649 1652 */
1650 1653 Notebook.prototype.get_notebook_name = function () {
1651 1654 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1652 1655 return nbname;
1653 1656 };
1654 1657
1655 1658 /**
1656 1659 * Setter method for this notebook's name.
1657 1660 *
1658 1661 * @method set_notebook_name
1659 1662 * @param {String} name A new name for this notebook
1660 1663 */
1661 1664 Notebook.prototype.set_notebook_name = function (name) {
1662 1665 this.notebook_name = name;
1663 1666 };
1664 1667
1665 1668 /**
1666 1669 * Check that a notebook's name is valid.
1667 1670 *
1668 1671 * @method test_notebook_name
1669 1672 * @param {String} nbname A name for this notebook
1670 1673 * @return {Boolean} True if the name is valid, false if invalid
1671 1674 */
1672 1675 Notebook.prototype.test_notebook_name = function (nbname) {
1673 1676 nbname = nbname || '';
1674 1677 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1675 1678 return true;
1676 1679 } else {
1677 1680 return false;
1678 1681 }
1679 1682 };
1680 1683
1681 1684 /**
1682 1685 * Load a notebook from JSON (.ipynb).
1683 1686 *
1684 1687 * This currently handles one worksheet: others are deleted.
1685 1688 *
1686 1689 * @method fromJSON
1687 1690 * @param {Object} data JSON representation of a notebook
1688 1691 */
1689 1692 Notebook.prototype.fromJSON = function (data) {
1690 1693 var content = data.content;
1691 1694 var ncells = this.ncells();
1692 1695 var i;
1693 1696 for (i=0; i<ncells; i++) {
1694 1697 // Always delete cell 0 as they get renumbered as they are deleted.
1695 1698 this.delete_cell(0);
1696 1699 }
1697 1700 // Save the metadata and name.
1698 1701 this.metadata = content.metadata;
1699 1702 this.notebook_name = data.name;
1700 1703 var trusted = true;
1701 1704 // Only handle 1 worksheet for now.
1702 1705 var worksheet = content.worksheets[0];
1703 1706 if (worksheet !== undefined) {
1704 1707 if (worksheet.metadata) {
1705 1708 this.worksheet_metadata = worksheet.metadata;
1706 1709 }
1707 1710 var new_cells = worksheet.cells;
1708 1711 ncells = new_cells.length;
1709 1712 var cell_data = null;
1710 1713 var new_cell = null;
1711 1714 for (i=0; i<ncells; i++) {
1712 1715 cell_data = new_cells[i];
1713 1716 // VERSIONHACK: plaintext -> raw
1714 1717 // handle never-released plaintext name for raw cells
1715 1718 if (cell_data.cell_type === 'plaintext'){
1716 1719 cell_data.cell_type = 'raw';
1717 1720 }
1718 1721
1719 1722 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1720 1723 new_cell.fromJSON(cell_data);
1721 1724 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1722 1725 trusted = false;
1723 1726 }
1724 1727 }
1725 1728 }
1726 1729 if (trusted != this.trusted) {
1727 1730 this.trusted = trusted;
1728 1731 this.events.trigger("trust_changed.Notebook", trusted);
1729 1732 }
1730 1733 if (content.worksheets.length > 1) {
1731 1734 dialog.modal({
1732 1735 title : "Multiple worksheets",
1733 1736 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1734 1737 "but this version of IPython can only handle the first. " +
1735 1738 "If you save this notebook, worksheets after the first will be lost.",
1736 1739 buttons : {
1737 1740 OK : {
1738 1741 class : "btn-danger"
1739 1742 }
1740 1743 }
1741 1744 });
1742 1745 }
1743 1746 };
1744 1747
1745 1748 /**
1746 1749 * Dump this notebook into a JSON-friendly object.
1747 1750 *
1748 1751 * @method toJSON
1749 1752 * @return {Object} A JSON-friendly representation of this notebook.
1750 1753 */
1751 1754 Notebook.prototype.toJSON = function () {
1752 1755 var cells = this.get_cells();
1753 1756 var ncells = cells.length;
1754 1757 var cell_array = new Array(ncells);
1755 1758 var trusted = true;
1756 1759 for (var i=0; i<ncells; i++) {
1757 1760 var cell = cells[i];
1758 1761 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1759 1762 trusted = false;
1760 1763 }
1761 1764 cell_array[i] = cell.toJSON();
1762 1765 }
1763 1766 var data = {
1764 1767 // Only handle 1 worksheet for now.
1765 1768 worksheets : [{
1766 1769 cells: cell_array,
1767 1770 metadata: this.worksheet_metadata
1768 1771 }],
1769 1772 metadata : this.metadata
1770 1773 };
1771 1774 if (trusted != this.trusted) {
1772 1775 this.trusted = trusted;
1773 1776 this.events.trigger("trust_changed.Notebook", trusted);
1774 1777 }
1775 1778 return data;
1776 1779 };
1777 1780
1778 1781 /**
1779 1782 * Start an autosave timer, for periodically saving the notebook.
1780 1783 *
1781 1784 * @method set_autosave_interval
1782 1785 * @param {Integer} interval the autosave interval in milliseconds
1783 1786 */
1784 1787 Notebook.prototype.set_autosave_interval = function (interval) {
1785 1788 var that = this;
1786 1789 // clear previous interval, so we don't get simultaneous timers
1787 1790 if (this.autosave_timer) {
1788 1791 clearInterval(this.autosave_timer);
1789 1792 }
1790 1793
1791 1794 this.autosave_interval = this.minimum_autosave_interval = interval;
1792 1795 if (interval) {
1793 1796 this.autosave_timer = setInterval(function() {
1794 1797 if (that.dirty) {
1795 1798 that.save_notebook();
1796 1799 }
1797 1800 }, interval);
1798 1801 this.events.trigger("autosave_enabled.Notebook", interval);
1799 1802 } else {
1800 1803 this.autosave_timer = null;
1801 1804 this.events.trigger("autosave_disabled.Notebook");
1802 1805 }
1803 1806 };
1804 1807
1805 1808 /**
1806 1809 * Save this notebook on the server. This becomes a notebook instance's
1807 1810 * .save_notebook method *after* the entire notebook has been loaded.
1808 1811 *
1809 1812 * @method save_notebook
1810 1813 */
1811 1814 Notebook.prototype.save_notebook = function (extra_settings) {
1812 1815 // Create a JSON model to be sent to the server.
1813 1816 var model = {};
1814 1817 model.name = this.notebook_name;
1815 1818 model.path = this.notebook_path;
1816 1819 model.content = this.toJSON();
1817 1820 model.content.nbformat = this.nbformat;
1818 1821 model.content.nbformat_minor = this.nbformat_minor;
1819 1822 // time the ajax call for autosave tuning purposes.
1820 1823 var start = new Date().getTime();
1821 1824 // We do the call with settings so we can set cache to false.
1822 1825 var settings = {
1823 1826 processData : false,
1824 1827 cache : false,
1825 1828 type : "PUT",
1826 1829 data : JSON.stringify(model),
1827 1830 headers : {'Content-Type': 'application/json'},
1828 1831 success : $.proxy(this.save_notebook_success, this, start),
1829 1832 error : $.proxy(this.save_notebook_error, this)
1830 1833 };
1831 1834 if (extra_settings) {
1832 1835 for (var key in extra_settings) {
1833 1836 settings[key] = extra_settings[key];
1834 1837 }
1835 1838 }
1836 1839 this.events.trigger('notebook_saving.Notebook');
1837 1840 var url = utils.url_join_encode(
1838 1841 this.base_url,
1839 1842 'api/notebooks',
1840 1843 this.notebook_path,
1841 1844 this.notebook_name
1842 1845 );
1843 1846 $.ajax(url, settings);
1844 1847 };
1845 1848
1846 1849 /**
1847 1850 * Success callback for saving a notebook.
1848 1851 *
1849 1852 * @method save_notebook_success
1850 1853 * @param {Integer} start the time when the save request started
1851 1854 * @param {Object} data JSON representation of a notebook
1852 1855 * @param {String} status Description of response status
1853 1856 * @param {jqXHR} xhr jQuery Ajax object
1854 1857 */
1855 1858 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1856 1859 this.set_dirty(false);
1857 1860 this.events.trigger('notebook_saved.Notebook');
1858 1861 this._update_autosave_interval(start);
1859 1862 if (this._checkpoint_after_save) {
1860 1863 this.create_checkpoint();
1861 1864 this._checkpoint_after_save = false;
1862 1865 }
1863 1866 };
1864 1867
1865 1868 /**
1866 1869 * update the autosave interval based on how long the last save took
1867 1870 *
1868 1871 * @method _update_autosave_interval
1869 1872 * @param {Integer} timestamp when the save request started
1870 1873 */
1871 1874 Notebook.prototype._update_autosave_interval = function (start) {
1872 1875 var duration = (new Date().getTime() - start);
1873 1876 if (this.autosave_interval) {
1874 1877 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1875 1878 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1876 1879 // round to 10 seconds, otherwise we will be setting a new interval too often
1877 1880 interval = 10000 * Math.round(interval / 10000);
1878 1881 // set new interval, if it's changed
1879 1882 if (interval != this.autosave_interval) {
1880 1883 this.set_autosave_interval(interval);
1881 1884 }
1882 1885 }
1883 1886 };
1884 1887
1885 1888 /**
1886 1889 * Failure callback for saving a notebook.
1887 1890 *
1888 1891 * @method save_notebook_error
1889 1892 * @param {jqXHR} xhr jQuery Ajax object
1890 1893 * @param {String} status Description of response status
1891 1894 * @param {String} error HTTP error message
1892 1895 */
1893 1896 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1894 1897 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1895 1898 };
1896 1899
1897 1900 /**
1898 1901 * Explicitly trust the output of this notebook.
1899 1902 *
1900 1903 * @method trust_notebook
1901 1904 */
1902 1905 Notebook.prototype.trust_notebook = function (extra_settings) {
1903 1906 var body = $("<div>").append($("<p>")
1904 1907 .text("A trusted IPython notebook may execute hidden malicious code ")
1905 1908 .append($("<strong>")
1906 1909 .append(
1907 1910 $("<em>").text("when you open it")
1908 1911 )
1909 1912 ).append(".").append(
1910 1913 " Selecting trust will immediately reload this notebook in a trusted state."
1911 1914 ).append(
1912 1915 " For more information, see the "
1913 1916 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1914 1917 .text("IPython security documentation")
1915 1918 ).append(".")
1916 1919 );
1917 1920
1918 1921 var nb = this;
1919 1922 dialog.modal({
1920 1923 title: "Trust this notebook?",
1921 1924 body: body,
1922 1925
1923 1926 buttons: {
1924 1927 Cancel : {},
1925 1928 Trust : {
1926 1929 class : "btn-danger",
1927 1930 click : function () {
1928 1931 var cells = nb.get_cells();
1929 1932 for (var i = 0; i < cells.length; i++) {
1930 1933 var cell = cells[i];
1931 1934 if (cell.cell_type == 'code') {
1932 1935 cell.output_area.trusted = true;
1933 1936 }
1934 1937 }
1935 1938 this.events.on('notebook_saved.Notebook', function () {
1936 1939 window.location.reload();
1937 1940 });
1938 1941 nb.save_notebook();
1939 1942 }
1940 1943 }
1941 1944 }
1942 1945 });
1943 1946 };
1944 1947
1945 1948 Notebook.prototype.new_notebook = function(){
1946 1949 var path = this.notebook_path;
1947 1950 var base_url = this.base_url;
1948 1951 var settings = {
1949 1952 processData : false,
1950 1953 cache : false,
1951 1954 type : "POST",
1952 1955 dataType : "json",
1953 1956 async : false,
1954 1957 success : function (data, status, xhr){
1955 1958 var notebook_name = data.name;
1956 1959 window.open(
1957 1960 utils.url_join_encode(
1958 1961 base_url,
1959 1962 'notebooks',
1960 1963 path,
1961 1964 notebook_name
1962 1965 ),
1963 1966 '_blank'
1964 1967 );
1965 1968 },
1966 1969 error : utils.log_ajax_error,
1967 1970 };
1968 1971 var url = utils.url_join_encode(
1969 1972 base_url,
1970 1973 'api/notebooks',
1971 1974 path
1972 1975 );
1973 1976 $.ajax(url,settings);
1974 1977 };
1975 1978
1976 1979
1977 1980 Notebook.prototype.copy_notebook = function(){
1978 1981 var path = this.notebook_path;
1979 1982 var base_url = this.base_url;
1980 1983 var settings = {
1981 1984 processData : false,
1982 1985 cache : false,
1983 1986 type : "POST",
1984 1987 dataType : "json",
1985 1988 data : JSON.stringify({copy_from : this.notebook_name}),
1986 1989 async : false,
1987 1990 success : function (data, status, xhr) {
1988 1991 window.open(utils.url_join_encode(
1989 1992 base_url,
1990 1993 'notebooks',
1991 1994 data.path,
1992 1995 data.name
1993 1996 ), '_blank');
1994 1997 },
1995 1998 error : utils.log_ajax_error,
1996 1999 };
1997 2000 var url = utils.url_join_encode(
1998 2001 base_url,
1999 2002 'api/notebooks',
2000 2003 path
2001 2004 );
2002 2005 $.ajax(url,settings);
2003 2006 };
2004 2007
2005 2008 Notebook.prototype.rename = function (nbname) {
2006 2009 var that = this;
2007 2010 if (!nbname.match(/\.ipynb$/)) {
2008 2011 nbname = nbname + ".ipynb";
2009 2012 }
2010 2013 var data = {name: nbname};
2011 2014 var settings = {
2012 2015 processData : false,
2013 2016 cache : false,
2014 2017 type : "PATCH",
2015 2018 data : JSON.stringify(data),
2016 2019 dataType: "json",
2017 2020 headers : {'Content-Type': 'application/json'},
2018 2021 success : $.proxy(that.rename_success, this),
2019 2022 error : $.proxy(that.rename_error, this)
2020 2023 };
2021 2024 this.events.trigger('rename_notebook.Notebook', data);
2022 2025 var url = utils.url_join_encode(
2023 2026 this.base_url,
2024 2027 'api/notebooks',
2025 2028 this.notebook_path,
2026 2029 this.notebook_name
2027 2030 );
2028 2031 $.ajax(url, settings);
2029 2032 };
2030 2033
2031 2034 Notebook.prototype.delete = function () {
2032 2035 var that = this;
2033 2036 var settings = {
2034 2037 processData : false,
2035 2038 cache : false,
2036 2039 type : "DELETE",
2037 2040 dataType: "json",
2038 2041 error : utils.log_ajax_error,
2039 2042 };
2040 2043 var url = utils.url_join_encode(
2041 2044 this.base_url,
2042 2045 'api/notebooks',
2043 2046 this.notebook_path,
2044 2047 this.notebook_name
2045 2048 );
2046 2049 $.ajax(url, settings);
2047 2050 };
2048 2051
2049 2052
2050 2053 Notebook.prototype.rename_success = function (json, status, xhr) {
2051 2054 var name = this.notebook_name = json.name;
2052 2055 var path = json.path;
2053 2056 this.session.rename_notebook(name, path);
2054 2057 this.events.trigger('notebook_renamed.Notebook', json);
2055 2058 };
2056 2059
2057 2060 Notebook.prototype.rename_error = function (xhr, status, error) {
2058 2061 var that = this;
2059 2062 var dialog_body = $('<div/>').append(
2060 2063 $("<p/>").addClass("rename-message")
2061 2064 .text('This notebook name already exists.')
2062 2065 );
2063 2066 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2064 2067 dialog.modal({
2065 2068 title: "Notebook Rename Error!",
2066 2069 body: dialog_body,
2067 2070 buttons : {
2068 2071 "Cancel": {},
2069 2072 "OK": {
2070 2073 class: "btn-primary",
2071 2074 click: function () {
2072 2075 this.save_widget.rename_notebook();
2073 2076 }}
2074 2077 },
2075 2078 open : function (event, ui) {
2076 2079 var that = $(this);
2077 2080 // Upon ENTER, click the OK button.
2078 2081 that.find('input[type="text"]').keydown(function (event, ui) {
2079 2082 if (event.which === this.keyboard.keycodes.enter) {
2080 2083 that.find('.btn-primary').first().click();
2081 2084 }
2082 2085 });
2083 2086 that.find('input[type="text"]').focus();
2084 2087 }
2085 2088 });
2086 2089 };
2087 2090
2088 2091 /**
2089 2092 * Request a notebook's data from the server.
2090 2093 *
2091 2094 * @method load_notebook
2092 2095 * @param {String} notebook_name and path A notebook to load
2093 2096 */
2094 2097 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2095 2098 var that = this;
2096 2099 this.notebook_name = notebook_name;
2097 2100 this.notebook_path = notebook_path;
2098 2101 // We do the call with settings so we can set cache to false.
2099 2102 var settings = {
2100 2103 processData : false,
2101 2104 cache : false,
2102 2105 type : "GET",
2103 2106 dataType : "json",
2104 2107 success : $.proxy(this.load_notebook_success,this),
2105 2108 error : $.proxy(this.load_notebook_error,this),
2106 2109 };
2107 2110 this.events.trigger('notebook_loading.Notebook');
2108 2111 var url = utils.url_join_encode(
2109 2112 this.base_url,
2110 2113 'api/notebooks',
2111 2114 this.notebook_path,
2112 2115 this.notebook_name
2113 2116 );
2114 2117 $.ajax(url, settings);
2115 2118 };
2116 2119
2117 2120 /**
2118 2121 * Success callback for loading a notebook from the server.
2119 2122 *
2120 2123 * Load notebook data from the JSON response.
2121 2124 *
2122 2125 * @method load_notebook_success
2123 2126 * @param {Object} data JSON representation of a notebook
2124 2127 * @param {String} status Description of response status
2125 2128 * @param {jqXHR} xhr jQuery Ajax object
2126 2129 */
2127 2130 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2128 2131 this.fromJSON(data);
2129 2132 if (this.ncells() === 0) {
2130 2133 this.insert_cell_below('code');
2131 2134 this.edit_mode(0);
2132 2135 } else {
2133 2136 this.select(0);
2134 2137 this.handle_command_mode(this.get_cell(0));
2135 2138 }
2136 2139 this.set_dirty(false);
2137 2140 this.scroll_to_top();
2138 2141 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2139 2142 var msg = "This notebook has been converted from an older " +
2140 2143 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2141 2144 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2142 2145 "newer notebook format will be used and older versions of IPython " +
2143 2146 "may not be able to read it. To keep the older version, close the " +
2144 2147 "notebook without saving it.";
2145 2148 dialog.modal({
2146 2149 title : "Notebook converted",
2147 2150 body : msg,
2148 2151 buttons : {
2149 2152 OK : {
2150 2153 class : "btn-primary"
2151 2154 }
2152 2155 }
2153 2156 });
2154 2157 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2155 2158 var that = this;
2156 2159 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2157 2160 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2158 2161 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2159 2162 this_vs + ". You can still work with this notebook, but some features " +
2160 2163 "introduced in later notebook versions may not be available.";
2161 2164
2162 2165 dialog.modal({
2163 2166 title : "Newer Notebook",
2164 2167 body : msg,
2165 2168 buttons : {
2166 2169 OK : {
2167 2170 class : "btn-danger"
2168 2171 }
2169 2172 }
2170 2173 });
2171 2174
2172 2175 }
2173 2176
2174 2177 // Create the session after the notebook is completely loaded to prevent
2175 2178 // code execution upon loading, which is a security risk.
2176 2179 if (this.session === null) {
2177 2180 this.start_session();
2178 2181 }
2179 2182 // load our checkpoint list
2180 2183 this.list_checkpoints();
2181 2184
2182 2185 // load toolbar state
2183 2186 if (this.metadata.celltoolbar) {
2184 2187 celltoolbar.CellToolbar.global_show();
2185 2188 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2186 2189 } else {
2187 2190 celltoolbar.CellToolbar.global_hide();
2188 2191 }
2189 2192
2190 2193 // now that we're fully loaded, it is safe to restore save functionality
2191 2194 delete(this.save_notebook);
2192 2195 this.events.trigger('notebook_loaded.Notebook');
2193 2196 };
2194 2197
2195 2198 /**
2196 2199 * Failure callback for loading a notebook from the server.
2197 2200 *
2198 2201 * @method load_notebook_error
2199 2202 * @param {jqXHR} xhr jQuery Ajax object
2200 2203 * @param {String} status Description of response status
2201 2204 * @param {String} error HTTP error message
2202 2205 */
2203 2206 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2204 2207 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2205 2208 var msg;
2206 2209 if (xhr.status === 400) {
2207 2210 msg = error;
2208 2211 } else if (xhr.status === 500) {
2209 2212 msg = "An unknown error occurred while loading this notebook. " +
2210 2213 "This version can load notebook formats " +
2211 2214 "v" + this.nbformat + " or earlier.";
2212 2215 }
2213 2216 dialog.modal({
2214 2217 title: "Error loading notebook",
2215 2218 body : msg,
2216 2219 buttons : {
2217 2220 "OK": {}
2218 2221 }
2219 2222 });
2220 2223 };
2221 2224
2222 2225 /********************* checkpoint-related *********************/
2223 2226
2224 2227 /**
2225 2228 * Save the notebook then immediately create a checkpoint.
2226 2229 *
2227 2230 * @method save_checkpoint
2228 2231 */
2229 2232 Notebook.prototype.save_checkpoint = function () {
2230 2233 this._checkpoint_after_save = true;
2231 2234 this.save_notebook();
2232 2235 };
2233 2236
2234 2237 /**
2235 2238 * Add a checkpoint for this notebook.
2236 2239 * for use as a callback from checkpoint creation.
2237 2240 *
2238 2241 * @method add_checkpoint
2239 2242 */
2240 2243 Notebook.prototype.add_checkpoint = function (checkpoint) {
2241 2244 var found = false;
2242 2245 for (var i = 0; i < this.checkpoints.length; i++) {
2243 2246 var existing = this.checkpoints[i];
2244 2247 if (existing.id == checkpoint.id) {
2245 2248 found = true;
2246 2249 this.checkpoints[i] = checkpoint;
2247 2250 break;
2248 2251 }
2249 2252 }
2250 2253 if (!found) {
2251 2254 this.checkpoints.push(checkpoint);
2252 2255 }
2253 2256 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2254 2257 };
2255 2258
2256 2259 /**
2257 2260 * List checkpoints for this notebook.
2258 2261 *
2259 2262 * @method list_checkpoints
2260 2263 */
2261 2264 Notebook.prototype.list_checkpoints = function () {
2262 2265 var url = utils.url_join_encode(
2263 2266 this.base_url,
2264 2267 'api/notebooks',
2265 2268 this.notebook_path,
2266 2269 this.notebook_name,
2267 2270 'checkpoints'
2268 2271 );
2269 2272 $.get(url).done(
2270 2273 $.proxy(this.list_checkpoints_success, this)
2271 2274 ).fail(
2272 2275 $.proxy(this.list_checkpoints_error, this)
2273 2276 );
2274 2277 };
2275 2278
2276 2279 /**
2277 2280 * Success callback for listing checkpoints.
2278 2281 *
2279 2282 * @method list_checkpoint_success
2280 2283 * @param {Object} data JSON representation of a checkpoint
2281 2284 * @param {String} status Description of response status
2282 2285 * @param {jqXHR} xhr jQuery Ajax object
2283 2286 */
2284 2287 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2285 2288 data = $.parseJSON(data);
2286 2289 this.checkpoints = data;
2287 2290 if (data.length) {
2288 2291 this.last_checkpoint = data[data.length - 1];
2289 2292 } else {
2290 2293 this.last_checkpoint = null;
2291 2294 }
2292 2295 this.events.trigger('checkpoints_listed.Notebook', [data]);
2293 2296 };
2294 2297
2295 2298 /**
2296 2299 * Failure callback for listing a checkpoint.
2297 2300 *
2298 2301 * @method list_checkpoint_error
2299 2302 * @param {jqXHR} xhr jQuery Ajax object
2300 2303 * @param {String} status Description of response status
2301 2304 * @param {String} error_msg HTTP error message
2302 2305 */
2303 2306 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2304 2307 this.events.trigger('list_checkpoints_failed.Notebook');
2305 2308 };
2306 2309
2307 2310 /**
2308 2311 * Create a checkpoint of this notebook on the server from the most recent save.
2309 2312 *
2310 2313 * @method create_checkpoint
2311 2314 */
2312 2315 Notebook.prototype.create_checkpoint = function () {
2313 2316 var url = utils.url_join_encode(
2314 2317 this.base_url,
2315 2318 'api/notebooks',
2316 2319 this.notebook_path,
2317 2320 this.notebook_name,
2318 2321 'checkpoints'
2319 2322 );
2320 2323 $.post(url).done(
2321 2324 $.proxy(this.create_checkpoint_success, this)
2322 2325 ).fail(
2323 2326 $.proxy(this.create_checkpoint_error, this)
2324 2327 );
2325 2328 };
2326 2329
2327 2330 /**
2328 2331 * Success callback for creating a checkpoint.
2329 2332 *
2330 2333 * @method create_checkpoint_success
2331 2334 * @param {Object} data JSON representation of a checkpoint
2332 2335 * @param {String} status Description of response status
2333 2336 * @param {jqXHR} xhr jQuery Ajax object
2334 2337 */
2335 2338 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2336 2339 data = $.parseJSON(data);
2337 2340 this.add_checkpoint(data);
2338 2341 this.events.trigger('checkpoint_created.Notebook', data);
2339 2342 };
2340 2343
2341 2344 /**
2342 2345 * Failure callback for creating a checkpoint.
2343 2346 *
2344 2347 * @method create_checkpoint_error
2345 2348 * @param {jqXHR} xhr jQuery Ajax object
2346 2349 * @param {String} status Description of response status
2347 2350 * @param {String} error_msg HTTP error message
2348 2351 */
2349 2352 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2350 2353 this.events.trigger('checkpoint_failed.Notebook');
2351 2354 };
2352 2355
2353 2356 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2354 2357 var that = this;
2355 2358 checkpoint = checkpoint || this.last_checkpoint;
2356 2359 if ( ! checkpoint ) {
2357 2360 console.log("restore dialog, but no checkpoint to restore to!");
2358 2361 return;
2359 2362 }
2360 2363 var body = $('<div/>').append(
2361 2364 $('<p/>').addClass("p-space").text(
2362 2365 "Are you sure you want to revert the notebook to " +
2363 2366 "the latest checkpoint?"
2364 2367 ).append(
2365 2368 $("<strong/>").text(
2366 2369 " This cannot be undone."
2367 2370 )
2368 2371 )
2369 2372 ).append(
2370 2373 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2371 2374 ).append(
2372 2375 $('<p/>').addClass("p-space").text(
2373 2376 Date(checkpoint.last_modified)
2374 2377 ).css("text-align", "center")
2375 2378 );
2376 2379
2377 2380 dialog.modal({
2378 2381 title : "Revert notebook to checkpoint",
2379 2382 body : body,
2380 2383 buttons : {
2381 2384 Revert : {
2382 2385 class : "btn-danger",
2383 2386 click : function () {
2384 2387 that.restore_checkpoint(checkpoint.id);
2385 2388 }
2386 2389 },
2387 2390 Cancel : {}
2388 2391 }
2389 2392 });
2390 2393 };
2391 2394
2392 2395 /**
2393 2396 * Restore the notebook to a checkpoint state.
2394 2397 *
2395 2398 * @method restore_checkpoint
2396 2399 * @param {String} checkpoint ID
2397 2400 */
2398 2401 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2399 2402 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2400 2403 var url = utils.url_join_encode(
2401 2404 this.base_url,
2402 2405 'api/notebooks',
2403 2406 this.notebook_path,
2404 2407 this.notebook_name,
2405 2408 'checkpoints',
2406 2409 checkpoint
2407 2410 );
2408 2411 $.post(url).done(
2409 2412 $.proxy(this.restore_checkpoint_success, this)
2410 2413 ).fail(
2411 2414 $.proxy(this.restore_checkpoint_error, this)
2412 2415 );
2413 2416 };
2414 2417
2415 2418 /**
2416 2419 * Success callback for restoring a notebook to a checkpoint.
2417 2420 *
2418 2421 * @method restore_checkpoint_success
2419 2422 * @param {Object} data (ignored, should be empty)
2420 2423 * @param {String} status Description of response status
2421 2424 * @param {jqXHR} xhr jQuery Ajax object
2422 2425 */
2423 2426 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2424 2427 this.events.trigger('checkpoint_restored.Notebook');
2425 2428 this.load_notebook(this.notebook_name, this.notebook_path);
2426 2429 };
2427 2430
2428 2431 /**
2429 2432 * Failure callback for restoring a notebook to a checkpoint.
2430 2433 *
2431 2434 * @method restore_checkpoint_error
2432 2435 * @param {jqXHR} xhr jQuery Ajax object
2433 2436 * @param {String} status Description of response status
2434 2437 * @param {String} error_msg HTTP error message
2435 2438 */
2436 2439 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2437 2440 this.events.trigger('checkpoint_restore_failed.Notebook');
2438 2441 };
2439 2442
2440 2443 /**
2441 2444 * Delete a notebook checkpoint.
2442 2445 *
2443 2446 * @method delete_checkpoint
2444 2447 * @param {String} checkpoint ID
2445 2448 */
2446 2449 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2447 2450 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2448 2451 var url = utils.url_join_encode(
2449 2452 this.base_url,
2450 2453 'api/notebooks',
2451 2454 this.notebook_path,
2452 2455 this.notebook_name,
2453 2456 'checkpoints',
2454 2457 checkpoint
2455 2458 );
2456 2459 $.ajax(url, {
2457 2460 type: 'DELETE',
2458 2461 success: $.proxy(this.delete_checkpoint_success, this),
2459 2462 error: $.proxy(this.delete_checkpoint_error, this)
2460 2463 });
2461 2464 };
2462 2465
2463 2466 /**
2464 2467 * Success callback for deleting a notebook checkpoint
2465 2468 *
2466 2469 * @method delete_checkpoint_success
2467 2470 * @param {Object} data (ignored, should be empty)
2468 2471 * @param {String} status Description of response status
2469 2472 * @param {jqXHR} xhr jQuery Ajax object
2470 2473 */
2471 2474 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2472 2475 this.events.trigger('checkpoint_deleted.Notebook', data);
2473 2476 this.load_notebook(this.notebook_name, this.notebook_path);
2474 2477 };
2475 2478
2476 2479 /**
2477 2480 * Failure callback for deleting a notebook checkpoint.
2478 2481 *
2479 2482 * @method delete_checkpoint_error
2480 2483 * @param {jqXHR} xhr jQuery Ajax object
2481 2484 * @param {String} status Description of response status
2482 2485 * @param {String} error_msg HTTP error message
2483 2486 */
2484 2487 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2485 2488 this.events.trigger('checkpoint_delete_failed.Notebook');
2486 2489 };
2487 2490
2488 2491
2489 2492 // For backwards compatability.
2490 2493 IPython.Notebook = Notebook;
2491 2494
2492 2495 return {'Notebook': Notebook};
2493 2496 });
@@ -1,233 +1,242 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'notebook/js/notificationwidget',
10 10 ], function(IPython, $, utils, dialog, notificationwidget) {
11 11 "use strict";
12 12
13 13 var NotificationArea = function (selector, options) {
14 // Constructor
15 //
16 // Parameters:
17 // selector: string
18 // options: dictionary
19 // Dictionary of keyword arguments.
20 // notebook: Notebook instance
21 // events: $(Events) instance
22 // save_widget: SaveWidget instance
14 23 this.selector = selector;
15 24 this.events = options.events;
16 25 this.save_widget = options.save_widget;
17 26 this.notebook = options.notebook;
18 27 if (this.selector !== undefined) {
19 28 this.element = $(selector);
20 29 }
21 30 this.widget_dict = {};
22 31 };
23 32
24 33 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
25 34 var uuid = utils.uuid();
26 35 if( css_class == 'danger') {css_class = 'ui-state-error';}
27 36 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
28 37 var tdiv = $('<div>')
29 38 .attr('id',uuid)
30 39 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
31 40 .addClass('border-box-sizing')
32 41 .addClass(css_class)
33 42 .hide()
34 43 .text(msg);
35 44
36 45 $(this.selector).append(tdiv);
37 46 var tmout = Math.max(1500,(timeout||1500));
38 47 tdiv.fadeIn(100);
39 48
40 49 setTimeout(function () {
41 50 tdiv.fadeOut(100, function () {tdiv.remove();});
42 51 }, tmout);
43 52 };
44 53
45 54 NotificationArea.prototype.widget = function(name) {
46 55 if(this.widget_dict[name] === undefined) {
47 56 return this.new_notification_widget(name);
48 57 }
49 58 return this.get_widget(name);
50 59 };
51 60
52 61 NotificationArea.prototype.get_widget = function(name) {
53 62 if(this.widget_dict[name] === undefined) {
54 63 throw('no widgets with this name');
55 64 }
56 65 return this.widget_dict[name];
57 66 };
58 67
59 68 NotificationArea.prototype.new_notification_widget = function(name) {
60 69 if(this.widget_dict[name] !== undefined) {
61 70 throw('widget with that name already exists ! ');
62 71 }
63 72 var div = $('<div/>').attr('id','notification_'+name);
64 73 $(this.selector).append(div);
65 74 this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
66 75 return this.widget_dict[name];
67 76 };
68 77
69 78 NotificationArea.prototype.init_notification_widgets = function() {
70 79 var that = this;
71 80 var knw = this.new_notification_widget('kernel');
72 81 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 82 var $modal_ind_icon = $("#modal_indicator_icon");
74 83
75 84 // Command/Edit mode
76 85 this.events.on('edit_mode.Notebook',function () {
77 86 that.save_widget.update_document_title();
78 87 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
79 88 });
80 89
81 90 this.events.on('command_mode.Notebook',function () {
82 91 that.save_widget.update_document_title();
83 92 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
84 93 });
85 94
86 95 // Implicitly start off in Command mode, switching to Edit mode will trigger event
87 96 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
88 97
89 98 // Kernel events
90 99 this.events.on('status_idle.Kernel',function () {
91 100 that.save_widget.update_document_title();
92 101 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
93 102 });
94 103
95 104 this.events.on('status_busy.Kernel',function () {
96 105 window.document.title='(Busy) '+window.document.title;
97 106 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
98 107 });
99 108
100 109 this.events.on('status_restarting.Kernel',function () {
101 110 that.save_widget.update_document_title();
102 111 knw.set_message("Restarting kernel", 2000);
103 112 });
104 113
105 114 this.events.on('status_interrupting.Kernel',function () {
106 115 knw.set_message("Interrupting kernel", 2000);
107 116 });
108 117
109 118 // Start the kernel indicator in the busy state, and send a kernel_info request.
110 119 // When the kernel_info reply arrives, the kernel is idle.
111 120 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
112 121
113 122 this.events.on('status_started.Kernel', function (evt, data) {
114 123 data.kernel.kernel_info(function () {
115 124 that.events.trigger('status_idle.Kernel');
116 125 });
117 126 });
118 127
119 128 this.events.on('status_dead.Kernel',function () {
120 129 var msg = 'The kernel has died, and the automatic restart has failed.' +
121 130 ' It is possible the kernel cannot be restarted.' +
122 131 ' If you are not able to restart the kernel, you will still be able to save' +
123 132 ' the notebook, but running code will no longer work until the notebook' +
124 133 ' is reopened.';
125 134
126 135 dialog.modal({
127 136 title: "Dead kernel",
128 137 body : msg,
129 138 buttons : {
130 139 "Manual Restart": {
131 140 class: "btn-danger",
132 141 click: function () {
133 142 that.events.trigger('status_restarting.Kernel');
134 143 that.notebook.start_kernel();
135 144 }
136 145 },
137 146 "Don't restart": {}
138 147 }
139 148 });
140 149 });
141 150
142 151 this.events.on('websocket_closed.Kernel', function (event, data) {
143 152 var kernel = data.kernel;
144 153 var ws_url = data.ws_url;
145 154 var early = data.early;
146 155 var msg;
147 156 if (!early) {
148 157 knw.set_message('Reconnecting WebSockets', 1000);
149 158 setTimeout(function () {
150 159 kernel.start_channels();
151 160 }, 5000);
152 161 return;
153 162 }
154 163 console.log('WebSocket connection failed: ', ws_url);
155 164 msg = "A WebSocket connection could not be established." +
156 165 " You will NOT be able to run code. Check your" +
157 166 " network connection or notebook server configuration.";
158 167 dialog.modal({
159 168 title: "WebSocket connection failed",
160 169 body: msg,
161 170 buttons : {
162 171 "OK": {},
163 172 "Reconnect": {
164 173 click: function () {
165 174 knw.set_message('Reconnecting WebSockets', 1000);
166 175 setTimeout(function () {
167 176 kernel.start_channels();
168 177 }, 5000);
169 178 }
170 179 }
171 180 }
172 181 });
173 182 });
174 183
175 184
176 185 var nnw = this.new_notification_widget('notebook');
177 186
178 187 // Notebook events
179 188 this.events.on('notebook_loading.Notebook', function () {
180 189 nnw.set_message("Loading notebook",500);
181 190 });
182 191 this.events.on('notebook_loaded.Notebook', function () {
183 192 nnw.set_message("Notebook loaded",500);
184 193 });
185 194 this.events.on('notebook_saving.Notebook', function () {
186 195 nnw.set_message("Saving notebook",500);
187 196 });
188 197 this.events.on('notebook_saved.Notebook', function () {
189 198 nnw.set_message("Notebook saved",2000);
190 199 });
191 200 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 201 nnw.set_message(data || "Notebook save failed");
193 202 });
194 203
195 204 // Checkpoint events
196 205 this.events.on('checkpoint_created.Notebook', function (evt, data) {
197 206 var msg = "Checkpoint created";
198 207 if (data.last_modified) {
199 208 var d = new Date(data.last_modified);
200 209 msg = msg + ": " + d.format("HH:MM:ss");
201 210 }
202 211 nnw.set_message(msg, 2000);
203 212 });
204 213 this.events.on('checkpoint_failed.Notebook', function () {
205 214 nnw.set_message("Checkpoint failed");
206 215 });
207 216 this.events.on('checkpoint_deleted.Notebook', function () {
208 217 nnw.set_message("Checkpoint deleted", 500);
209 218 });
210 219 this.events.on('checkpoint_delete_failed.Notebook', function () {
211 220 nnw.set_message("Checkpoint delete failed");
212 221 });
213 222 this.events.on('checkpoint_restoring.Notebook', function () {
214 223 nnw.set_message("Restoring to checkpoint...", 500);
215 224 });
216 225 this.events.on('checkpoint_restore_failed.Notebook', function () {
217 226 nnw.set_message("Checkpoint restore failed");
218 227 });
219 228
220 229 // Autosave events
221 230 this.events.on('autosave_disabled.Notebook', function () {
222 231 nnw.set_message("Autosave disabled", 2000);
223 232 });
224 233 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
225 234 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
226 235 });
227 236
228 237 };
229 238
230 239 IPython.NotificationArea = NotificationArea;
231 240
232 241 return {'NotificationArea': NotificationArea};
233 242 });
@@ -1,178 +1,187 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11 var Pager = function (pager_selector, pager_splitter_selector, options) {
12 // Constructor
13 //
14 // Parameters:
15 // pager_selector: string
16 // pager_splitter_selector: string
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // layout_manager: LayoutManager instance
12 21 this.events = options.events;
13 22 this.pager_element = $(pager_selector);
14 23 this.pager_button_area = $('#pager_button_area');
15 24 var that = this;
16 25 this.percentage_height = 0.40;
17 26 options.layout_manager.pager = this;
18 27 this.pager_splitter_element = $(pager_splitter_selector)
19 28 .draggable({
20 29 containment: 'window',
21 30 axis:'y',
22 31 helper: null ,
23 32 drag: function(event, ui) {
24 33 // recalculate the amount of space the pager should take
25 34 var pheight = ($(document.body).height()-event.clientY-4);
26 35 var downprct = pheight/options.layout_manager.app_height();
27 36 downprct = Math.min(0.9, downprct);
28 37 if (downprct < 0.1) {
29 38 that.percentage_height = 0.1;
30 39 that.collapse({'duration':0});
31 40 } else if (downprct > 0.2) {
32 41 that.percentage_height = downprct;
33 42 that.expand({'duration':0});
34 43 }
35 44 options.layout_manager.do_resize();
36 45 }
37 46 });
38 47 this.expanded = false;
39 48 this.style();
40 49 this.create_button_area();
41 50 this.bind_events();
42 51 };
43 52
44 53 Pager.prototype.create_button_area = function(){
45 54 var that = this;
46 55 this.pager_button_area.append(
47 56 $('<a>').attr('role', "button")
48 57 .attr('title',"Open the pager in an external window")
49 58 .addClass('ui-button')
50 59 .click(function(){that.detach();})
51 60 .attr('style','position: absolute; right: 20px;')
52 61 .append(
53 62 $('<span>').addClass("ui-icon ui-icon-extlink")
54 63 )
55 64 );
56 65 this.pager_button_area.append(
57 66 $('<a>').attr('role', "button")
58 67 .attr('title',"Close the pager")
59 68 .addClass('ui-button')
60 69 .click(function(){that.collapse();})
61 70 .attr('style','position: absolute; right: 5px;')
62 71 .append(
63 72 $('<span>').addClass("ui-icon ui-icon-close")
64 73 )
65 74 );
66 75 };
67 76
68 77 Pager.prototype.style = function () {
69 78 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
70 79 this.pager_element.addClass('border-box-sizing');
71 80 this.pager_element.find(".container").addClass('border-box-sizing');
72 81 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
73 82 };
74 83
75 84
76 85 Pager.prototype.bind_events = function () {
77 86 var that = this;
78 87
79 88 this.pager_element.bind('collapse_pager', function (event, extrap) {
80 89 var time = 'fast';
81 90 if (extrap && extrap.duration) {
82 91 time = extrap.duration;
83 92 }
84 93 that.pager_element.hide(time);
85 94 });
86 95
87 96 this.pager_element.bind('expand_pager', function (event, extrap) {
88 97 var time = 'fast';
89 98 if (extrap && extrap.duration) {
90 99 time = extrap.duration;
91 100 }
92 101 that.pager_element.show(time);
93 102 });
94 103
95 104 this.pager_splitter_element.hover(
96 105 function () {
97 106 that.pager_splitter_element.addClass('ui-state-hover');
98 107 },
99 108 function () {
100 109 that.pager_splitter_element.removeClass('ui-state-hover');
101 110 }
102 111 );
103 112
104 113 this.pager_splitter_element.click(function () {
105 114 that.toggle();
106 115 });
107 116
108 117 this.events.on('open_with_text.Pager', function (event, payload) {
109 118 // FIXME: support other mime types
110 119 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
111 120 that.clear();
112 121 that.expand();
113 122 that.append_text(payload.data['text/plain']);
114 123 }
115 124 });
116 125 };
117 126
118 127
119 128 Pager.prototype.collapse = function (extrap) {
120 129 if (this.expanded === true) {
121 130 this.expanded = false;
122 131 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
123 132 }
124 133 };
125 134
126 135
127 136 Pager.prototype.expand = function (extrap) {
128 137 if (this.expanded !== true) {
129 138 this.expanded = true;
130 139 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
131 140 }
132 141 };
133 142
134 143
135 144 Pager.prototype.toggle = function () {
136 145 if (this.expanded === true) {
137 146 this.collapse();
138 147 } else {
139 148 this.expand();
140 149 }
141 150 };
142 151
143 152
144 153 Pager.prototype.clear = function (text) {
145 154 this.pager_element.find(".container").empty();
146 155 };
147 156
148 157 Pager.prototype.detach = function(){
149 158 var w = window.open("","_blank");
150 159 $(w.document.head)
151 160 .append(
152 161 $('<link>')
153 162 .attr('rel',"stylesheet")
154 163 .attr('href',"/static/css/notebook.css")
155 164 .attr('type',"text/css")
156 165 )
157 166 .append(
158 167 $('<title>').text("IPython Pager")
159 168 );
160 169 var pager_body = $(w.document.body);
161 170 pager_body.css('overflow','scroll');
162 171
163 172 pager_body.append(this.pager_element.clone().children());
164 173 w.document.close();
165 174 this.collapse();
166 175 };
167 176
168 177 Pager.prototype.append_text = function (text) {
169 178 // The only user content injected with this HTML call is escaped by
170 179 // the fixConsole() method.
171 180 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
172 181 };
173 182
174 183 // Backwards compatability.
175 184 IPython.Pager = Pager;
176 185
177 186 return {'Pager': Pager};
178 187 });
@@ -1,173 +1,180 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 ], function(IPython, $, utils, dialog) {
10 10 "use strict";
11 11 var platform = utils.platform;
12 12
13 13 var QuickHelp = function (options) {
14 // Constructor
15 //
16 // Parameters:
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // keyboard_manager: KeyboardManager instance
14 21 this.keyboard_manager = options.keyboard_manager;
15 22 this.keyboard_manager.quick_help = this;
16 23 this.events = options.events;
17 24 };
18 25
19 26 var cmd_ctrl = 'Ctrl-';
20 27 var platform_specific;
21 28
22 29 if (platform === 'MacOS') {
23 30 // Mac OS X specific
24 31 cmd_ctrl = 'Cmd-';
25 32 platform_specific = [
26 33 { shortcut: "Cmd-Up", help:"go to cell start" },
27 34 { shortcut: "Cmd-Down", help:"go to cell end" },
28 35 { shortcut: "Opt-Left", help:"go one word left" },
29 36 { shortcut: "Opt-Right", help:"go one word right" },
30 37 { shortcut: "Opt-Backspace", help:"del word before" },
31 38 { shortcut: "Opt-Delete", help:"del word after" },
32 39 ];
33 40 } else {
34 41 // PC specific
35 42 platform_specific = [
36 43 { shortcut: "Ctrl-Home", help:"go to cell start" },
37 44 { shortcut: "Ctrl-Up", help:"go to cell start" },
38 45 { shortcut: "Ctrl-End", help:"go to cell end" },
39 46 { shortcut: "Ctrl-Down", help:"go to cell end" },
40 47 { shortcut: "Ctrl-Left", help:"go one word left" },
41 48 { shortcut: "Ctrl-Right", help:"go one word right" },
42 49 { shortcut: "Ctrl-Backspace", help:"del word before" },
43 50 { shortcut: "Ctrl-Delete", help:"del word after" },
44 51 ];
45 52 }
46 53
47 54 var cm_shortcuts = [
48 55 { shortcut:"Tab", help:"code completion or indent" },
49 56 { shortcut:"Shift-Tab", help:"tooltip" },
50 57 { shortcut: cmd_ctrl + "]", help:"indent" },
51 58 { shortcut: cmd_ctrl + "[", help:"dedent" },
52 59 { shortcut: cmd_ctrl + "a", help:"select all" },
53 60 { shortcut: cmd_ctrl + "z", help:"undo" },
54 61 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
55 62 { shortcut: cmd_ctrl + "y", help:"redo" },
56 63 ].concat( platform_specific );
57 64
58 65
59 66
60 67
61 68
62 69
63 70 QuickHelp.prototype.show_keyboard_shortcuts = function () {
64 71 // toggles display of keyboard shortcut dialog
65 72 var that = this;
66 73 if ( this.force_rebuild ) {
67 74 this.shortcut_dialog.remove();
68 75 delete(this.shortcut_dialog);
69 76 this.force_rebuild = false;
70 77 }
71 78 if ( this.shortcut_dialog ){
72 79 // if dialog is already shown, close it
73 80 $(this.shortcut_dialog).modal("toggle");
74 81 return;
75 82 }
76 83 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
77 84 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
78 85 var help, shortcut;
79 86 var i, half, n;
80 87 var element = $('<div/>');
81 88
82 89 // The documentation
83 90 var doc = $('<div/>').addClass('alert alert-warning');
84 91 doc.append(
85 92 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
86 93 ).append(
87 94 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
88 95 'allows you to type code/text into a cell and is indicated by a green cell '+
89 96 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
90 97 'and is indicated by a grey cell border.'
91 98 );
92 99 element.append(doc);
93 100
94 101 // Command mode
95 102 var cmd_div = this.build_command_help();
96 103 element.append(cmd_div);
97 104
98 105 // Edit mode
99 106 var edit_div = this.build_edit_help(cm_shortcuts);
100 107 element.append(edit_div);
101 108
102 109 this.shortcut_dialog = dialog.modal({
103 110 title : "Keyboard shortcuts",
104 111 body : element,
105 112 destroy : false,
106 113 buttons : {
107 114 Close : {}
108 115 }
109 116 });
110 117 this.shortcut_dialog.addClass("modal_stretch");
111 118
112 119 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
113 120 };
114 121
115 122 QuickHelp.prototype.build_command_help = function () {
116 123 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
117 124 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
118 125 };
119 126
120 127 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
121 128 var prettify = function (s) {
122 129 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
123 130 var keys = s.split('-');
124 131 var k, i;
125 132 for (i=0; i < keys.length; i++) {
126 133 k = keys[i];
127 134 if ( k.length == 1 ) {
128 135 keys[i] = "<code><strong>" + k + "</strong></code>";
129 136 continue; // leave individual keys lower-cased
130 137 }
131 138 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
132 139 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
133 140 }
134 141 return keys.join('-');
135 142
136 143
137 144 };
138 145
139 146 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
140 147 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
141 148 jQuery.merge(cm_shortcuts, edit_shortcuts);
142 149 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
143 150 };
144 151
145 152 var build_one = function (s) {
146 153 var help = s.help;
147 154 var shortcut = prettify(s.shortcut);
148 155 return $('<div>').addClass('quickhelp').
149 156 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
150 157 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
151 158
152 159 };
153 160
154 161 var build_div = function (title, shortcuts) {
155 162 var i, half, n;
156 163 var div = $('<div/>').append($(title));
157 164 var sub_div = $('<div/>').addClass('hbox');
158 165 var col1 = $('<div/>').addClass('box-flex1');
159 166 var col2 = $('<div/>').addClass('box-flex1');
160 167 n = shortcuts.length;
161 168 half = ~~(n/2); // Truncate :)
162 169 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
163 170 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
164 171 sub_div.append(col1).append(col2);
165 172 div.append(sub_div);
166 173 return div;
167 174 };
168 175
169 176 // Backwards compatability.
170 177 IPython.QuickHelp = QuickHelp;
171 178
172 179 return {'QuickHelp': QuickHelp};
173 180 });
@@ -1,172 +1,173 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'base/js/keyboard',
10 10 'dateformat/date.format',
11 11 ], function(IPython, $, utils, dialog, keyboard) {
12 12 "use strict";
13 13
14 14 var SaveWidget = function (selector, events) {
15 // TODO: Remove circulat ref.
15 16 this.notebook = undefined;
16 17 this.selector = selector;
17 18 this.events = events;
18 19 if (this.selector !== undefined) {
19 20 this.element = $(selector);
20 21 this.style();
21 22 this.bind_events();
22 23 }
23 24 };
24 25
25 26 SaveWidget.prototype.style = function () {
26 27 };
27 28
28 29
29 30 SaveWidget.prototype.bind_events = function () {
30 31 var that = this;
31 32 this.element.find('span#notebook_name').click(function () {
32 33 that.rename_notebook();
33 34 });
34 35 this.element.find('span#notebook_name').hover(function () {
35 36 $(this).addClass("ui-state-hover");
36 37 }, function () {
37 38 $(this).removeClass("ui-state-hover");
38 39 });
39 40 this.events.on('notebook_loaded.Notebook', function () {
40 41 that.update_notebook_name();
41 42 that.update_document_title();
42 43 });
43 44 this.events.on('notebook_saved.Notebook', function () {
44 45 that.update_notebook_name();
45 46 that.update_document_title();
46 47 });
47 48 this.events.on('notebook_renamed.Notebook', function () {
48 49 that.update_notebook_name();
49 50 that.update_document_title();
50 51 that.update_address_bar();
51 52 });
52 53 this.events.on('notebook_save_failed.Notebook', function () {
53 54 that.set_save_status('Autosave Failed!');
54 55 });
55 56 this.events.on('checkpoints_listed.Notebook', function (event, data) {
56 57 that.set_last_checkpoint(data[0]);
57 58 });
58 59
59 60 this.events.on('checkpoint_created.Notebook', function (event, data) {
60 61 that.set_last_checkpoint(data);
61 62 });
62 63 this.events.on('set_dirty.Notebook', function (event, data) {
63 64 that.set_autosaved(data.value);
64 65 });
65 66 };
66 67
67 68
68 69 SaveWidget.prototype.rename_notebook = function () {
69 70 var that = this;
70 71 var dialog_body = $('<div/>').append(
71 72 $("<p/>").addClass("rename-message")
72 73 .text('Enter a new notebook name:')
73 74 ).append(
74 75 $("<br/>")
75 76 ).append(
76 77 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
77 78 .val(that.notebook.get_notebook_name())
78 79 );
79 80 dialog.modal({
80 81 title: "Rename Notebook",
81 82 body: dialog_body,
82 83 buttons : {
83 84 "Cancel": {},
84 85 "OK": {
85 86 class: "btn-primary",
86 87 click: function () {
87 88 var new_name = $(this).find('input').val();
88 89 if (!that.notebook.test_notebook_name(new_name)) {
89 90 $(this).find('.rename-message').text(
90 91 "Invalid notebook name. Notebook names must "+
91 92 "have 1 or more characters and can contain any characters " +
92 93 "except :/\\. Please enter a new notebook name:"
93 94 );
94 95 return false;
95 96 } else {
96 97 that.notebook.rename(new_name);
97 98 }
98 99 }}
99 100 },
100 101 open : function (event, ui) {
101 102 var that = $(this);
102 103 // Upon ENTER, click the OK button.
103 104 that.find('input[type="text"]').keydown(function (event, ui) {
104 105 if (event.which === that.keyboard.keycodes.enter) {
105 106 that.find('.btn-primary').first().click();
106 107 return false;
107 108 }
108 109 });
109 110 that.find('input[type="text"]').focus().select();
110 111 }
111 112 });
112 113 };
113 114
114 115
115 116 SaveWidget.prototype.update_notebook_name = function () {
116 117 var nbname = this.notebook.get_notebook_name();
117 118 this.element.find('span#notebook_name').text(nbname);
118 119 };
119 120
120 121
121 122 SaveWidget.prototype.update_document_title = function () {
122 123 var nbname = this.notebook.get_notebook_name();
123 124 document.title = nbname;
124 125 };
125 126
126 127 SaveWidget.prototype.update_address_bar = function(){
127 128 var base_url = this.notebook.base_url;
128 129 var nbname = this.notebook.notebook_name;
129 130 var path = this.notebook.notebook_path;
130 131 var state = {path : path, name: nbname};
131 132 window.history.replaceState(state, "", utils.url_join_encode(
132 133 base_url,
133 134 "notebooks",
134 135 path,
135 136 nbname)
136 137 );
137 138 };
138 139
139 140
140 141 SaveWidget.prototype.set_save_status = function (msg) {
141 142 this.element.find('span#autosave_status').text(msg);
142 143 };
143 144
144 145 SaveWidget.prototype.set_checkpoint_status = function (msg) {
145 146 this.element.find('span#checkpoint_status').text(msg);
146 147 };
147 148
148 149 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
149 150 if (!checkpoint) {
150 151 this.set_checkpoint_status("");
151 152 return;
152 153 }
153 154 var d = new Date(checkpoint.last_modified);
154 155 this.set_checkpoint_status(
155 156 "Last Checkpoint: " + d.format('mmm dd HH:MM')
156 157 );
157 158 };
158 159
159 160 SaveWidget.prototype.set_autosaved = function (dirty) {
160 161 if (dirty) {
161 162 this.set_save_status("(unsaved changes)");
162 163 } else {
163 164 this.set_save_status("(autosaved)");
164 165 }
165 166 };
166 167
167 168 // Backwards compatability.
168 169 IPython.SaveWidget = SaveWidget;
169 170
170 171 return {'SaveWidget': SaveWidget};
171 172
172 173 });
@@ -1,453 +1,461 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'notebook/js/cell',
8 8 'base/js/security',
9 9 'notebook/js/mathjaxutils',
10 10 'notebook/js/celltoolbar',
11 11 'components/marked/lib/marked',
12 12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 13 "use strict";
14 14 var Cell = cell.Cell;
15 15
16 /**
17 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
18 * cell start as not redered.
19 *
20 * @class TextCell
21 * @constructor TextCell
22 * @extend Cell
23 * @param {object|undefined} [options]
24 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
25 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
26 */
27 16 var TextCell = function (options) {
17 // Constructor
18 //
19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 // and cell type is 'text' cell start as not redered.
21 //
22 // Parameters:
23 // options: dictionary
24 // Dictionary of keyword arguments.
25 // events: $(Events) instance
26 // config: dictionary
27 // keyboard_manager: KeyboardManager instance
28 // notebook: Notebook instance
28 29 options = options || {};
30
29 31 // in all TextCell/Cell subclasses
30 32 // do not assign most of members here, just pass it down
31 33 // in the options dict potentially overwriting what you wish.
32 34 // they will be assigned in the base class.
33 35 this.notebook = options.notebook;
34 36 this.events = options.events;
35 37 this.config = options.config;
36 38
37 39 // we cannot put this as a class key as it has handle to "this".
38 40 var cm_overwrite_options = {
39 41 onKeyEvent: $.proxy(this.handle_keyevent,this)
40 42 };
41 43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
42 44 Cell.apply(this, [{
43 45 config: config,
44 46 keyboard_manager: options.keyboard_manager,
45 47 events: events}]);
46 48
47 49 this.cell_type = this.cell_type || 'text';
48 50 mathjaxutils = mathjaxutils;
49 51 this.rendered = false;
50 52 };
51 53
52 54 TextCell.prototype = new Cell();
53 55
54 56 TextCell.options_default = {
55 57 cm_config : {
56 58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
57 59 mode: 'htmlmixed',
58 60 lineWrapping : true,
59 61 }
60 62 };
61 63
62 64
63 65 /**
64 66 * Create the DOM element of the TextCell
65 67 * @method create_element
66 68 * @private
67 69 */
68 70 TextCell.prototype.create_element = function () {
69 71 Cell.prototype.create_element.apply(this, arguments);
70 72
71 73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
72 74 cell.attr('tabindex','2');
73 75
74 76 var prompt = $('<div/>').addClass('prompt input_prompt');
75 77 cell.append(prompt);
76 78 var inner_cell = $('<div/>').addClass('inner_cell');
77 79 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
78 80 inner_cell.append(this.celltoolbar.element);
79 81 var input_area = $('<div/>').addClass('input_area');
80 82 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
81 83 // The tabindex=-1 makes this div focusable.
82 84 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
83 85 addClass('rendered_html').attr('tabindex','-1');
84 86 inner_cell.append(input_area).append(render_area);
85 87 cell.append(inner_cell);
86 88 this.element = cell;
87 89 };
88 90
89 91
90 92 /**
91 93 * Bind the DOM evet to cell actions
92 94 * Need to be called after TextCell.create_element
93 95 * @private
94 96 * @method bind_event
95 97 */
96 98 TextCell.prototype.bind_events = function () {
97 99 Cell.prototype.bind_events.apply(this);
98 100 var that = this;
99 101
100 102 this.element.dblclick(function () {
101 103 if (that.selected === false) {
102 104 this.events.trigger('select.Cell', {'cell':that});
103 105 }
104 106 var cont = that.unrender();
105 107 if (cont) {
106 108 that.focus_editor();
107 109 }
108 110 });
109 111 };
110 112
111 113 // Cell level actions
112 114
113 115 TextCell.prototype.select = function () {
114 116 var cont = Cell.prototype.select.apply(this);
115 117 if (cont) {
116 118 if (this.mode === 'edit') {
117 119 this.code_mirror.refresh();
118 120 }
119 121 }
120 122 return cont;
121 123 };
122 124
123 125 TextCell.prototype.unrender = function () {
124 126 if (this.read_only) return;
125 127 var cont = Cell.prototype.unrender.apply(this);
126 128 if (cont) {
127 129 var text_cell = this.element;
128 130 var output = text_cell.find("div.text_cell_render");
129 131 output.hide();
130 132 text_cell.find('div.input_area').show();
131 133 if (this.get_text() === this.placeholder) {
132 134 this.set_text('');
133 135 }
134 136 this.refresh();
135 137 }
136 138 if (this.celltoolbar.ui_controls_list.length) {
137 139 this.celltoolbar.show();
138 140 }
139 141 return cont;
140 142 };
141 143
142 144 TextCell.prototype.execute = function () {
143 145 this.render();
144 146 };
145 147
146 148 /**
147 149 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
148 150 * @method get_text
149 151 * @retrun {string} CodeMirror current text value
150 152 */
151 153 TextCell.prototype.get_text = function() {
152 154 return this.code_mirror.getValue();
153 155 };
154 156
155 157 /**
156 158 * @param {string} text - Codemiror text value
157 159 * @see TextCell#get_text
158 160 * @method set_text
159 161 * */
160 162 TextCell.prototype.set_text = function(text) {
161 163 this.code_mirror.setValue(text);
162 164 this.code_mirror.refresh();
163 165 };
164 166
165 167 /**
166 168 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
167 169 * @method get_rendered
168 170 * */
169 171 TextCell.prototype.get_rendered = function() {
170 172 return this.element.find('div.text_cell_render').html();
171 173 };
172 174
173 175 /**
174 176 * @method set_rendered
175 177 */
176 178 TextCell.prototype.set_rendered = function(text) {
177 179 this.element.find('div.text_cell_render').html(text);
178 180 this.celltoolbar.hide();
179 181 };
180 182
181 183
182 184 /**
183 185 * Create Text cell from JSON
184 186 * @param {json} data - JSON serialized text-cell
185 187 * @method fromJSON
186 188 */
187 189 TextCell.prototype.fromJSON = function (data) {
188 190 Cell.prototype.fromJSON.apply(this, arguments);
189 191 if (data.cell_type === this.cell_type) {
190 192 if (data.source !== undefined) {
191 193 this.set_text(data.source);
192 194 // make this value the starting point, so that we can only undo
193 195 // to this state, instead of a blank cell
194 196 this.code_mirror.clearHistory();
195 197 // TODO: This HTML needs to be treated as potentially dangerous
196 198 // user input and should be handled before set_rendered.
197 199 this.set_rendered(data.rendered || '');
198 200 this.rendered = false;
199 201 this.render();
200 202 }
201 203 }
202 204 };
203 205
204 206 /** Generate JSON from cell
205 207 * @return {object} cell data serialised to json
206 208 */
207 209 TextCell.prototype.toJSON = function () {
208 210 var data = Cell.prototype.toJSON.apply(this);
209 211 data.source = this.get_text();
210 212 if (data.source == this.placeholder) {
211 213 data.source = "";
212 214 }
213 215 return data;
214 216 };
215 217
216 218
217 /**
218 * @class MarkdownCell
219 * @constructor MarkdownCell
220 * @extends IPython.HTMLCell
221 */
222 219 var MarkdownCell = function (options) {
220 // Constructor
221 //
222 // Parameters:
223 // options: dictionary
224 // Dictionary of keyword arguments.
225 // events: $(Events) instance
226 // config: dictionary
227 // keyboard_manager: KeyboardManager instance
228 // notebook: Notebook instance
223 229 options = options || {};
224 230 var config = this.mergeopt(MarkdownCell, options.config);
225 231 TextCell.apply(this, [$.extend({}, options, {config: config})]);
226 232
227 233 this.cell_type = 'markdown';
228 234 };
229 235
230 236 MarkdownCell.options_default = {
231 237 cm_config: {
232 238 mode: 'ipythongfm'
233 239 },
234 240 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
235 241 };
236 242
237 243 MarkdownCell.prototype = new TextCell();
238 244
239 245 /**
240 246 * @method render
241 247 */
242 248 MarkdownCell.prototype.render = function () {
243 249 var cont = TextCell.prototype.render.apply(this);
244 250 if (cont) {
245 251 var text = this.get_text();
246 252 var math = null;
247 253 if (text === "") { text = this.placeholder; }
248 254 var text_and_math = mathjaxutils.remove_math(text);
249 255 text = text_and_math[0];
250 256 math = text_and_math[1];
251 257 var html = marked.parser(marked.lexer(text));
252 258 html = mathjaxutils.replace_math(html, math);
253 259 html = security.sanitize_html(html);
254 260 html = $($.parseHTML(html));
255 261 // links in markdown cells should open in new tabs
256 262 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
257 263 this.set_rendered(html);
258 264 this.element.find('div.input_area').hide();
259 265 this.element.find("div.text_cell_render").show();
260 266 this.typeset();
261 267 }
262 268 return cont;
263 269 };
264 270
265 271
266 // RawCell
267
268 /**
269 * @class RawCell
270 * @constructor RawCell
271 * @extends TextCell
272 */
273 272 var RawCell = function (options) {
273 // Constructor
274 //
275 // Parameters:
276 // options: dictionary
277 // Dictionary of keyword arguments.
278 // events: $(Events) instance
279 // config: dictionary
280 // keyboard_manager: KeyboardManager instance
281 // notebook: Notebook instance
274 282 options = options || {};
275 283 var config = this.mergeopt(RawCell, options.config);
276 284 TextCell.apply(this, [$.extend({}, options, {config: config})]);
277 285
278 286 // RawCell should always hide its rendered div
279 287 this.element.find('div.text_cell_render').hide();
280 288 this.cell_type = 'raw';
281 289 };
282 290
283 291 RawCell.options_default = {
284 292 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
285 293 "It will not be rendered in the notebook. " +
286 294 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
287 295 };
288 296
289 297 RawCell.prototype = new TextCell();
290 298
291 299 /** @method bind_events **/
292 300 RawCell.prototype.bind_events = function () {
293 301 TextCell.prototype.bind_events.apply(this);
294 302 var that = this;
295 303 this.element.focusout(function() {
296 304 that.auto_highlight();
297 305 that.render();
298 306 });
299 307
300 308 this.code_mirror.on('focus', function() { that.unrender(); });
301 309 };
302 310
303 311 /**
304 312 * Trigger autodetection of highlight scheme for current cell
305 313 * @method auto_highlight
306 314 */
307 315 RawCell.prototype.auto_highlight = function () {
308 316 this._auto_highlight(this.config.raw_cell_highlight);
309 317 };
310 318
311 319 /** @method render **/
312 320 RawCell.prototype.render = function () {
313 321 var cont = TextCell.prototype.render.apply(this);
314 322 if (cont){
315 323 var text = this.get_text();
316 324 if (text === "") { text = this.placeholder; }
317 325 this.set_text(text);
318 326 this.element.removeClass('rendered');
319 327 }
320 328 return cont;
321 329 };
322 330
323 331
324 /**
325 * @class HeadingCell
326 * @extends TextCell
327 */
328
329 /**
330 * @constructor HeadingCell
331 * @extends TextCell
332 */
333 332 var HeadingCell = function (options) {
333 // Constructor
334 //
335 // Parameters:
336 // options: dictionary
337 // Dictionary of keyword arguments.
338 // events: $(Events) instance
339 // config: dictionary
340 // keyboard_manager: KeyboardManager instance
341 // notebook: Notebook instance
334 342 options = options || {};
335 343 var config = this.mergeopt(HeadingCell, options.config);
336 344 TextCell.apply(this, [$.extend({}, options, {config: config})]);
337 345
338 346 this.level = 1;
339 347 this.cell_type = 'heading';
340 348 };
341 349
342 350 HeadingCell.options_default = {
343 351 placeholder: "Type Heading Here"
344 352 };
345 353
346 354 HeadingCell.prototype = new TextCell();
347 355
348 356 /** @method fromJSON */
349 357 HeadingCell.prototype.fromJSON = function (data) {
350 358 if (data.level !== undefined){
351 359 this.level = data.level;
352 360 }
353 361 TextCell.prototype.fromJSON.apply(this, arguments);
354 362 };
355 363
356 364
357 365 /** @method toJSON */
358 366 HeadingCell.prototype.toJSON = function () {
359 367 var data = TextCell.prototype.toJSON.apply(this);
360 368 data.level = this.get_level();
361 369 return data;
362 370 };
363 371
364 372 /**
365 373 * can the cell be split into two cells
366 374 * @method is_splittable
367 375 **/
368 376 HeadingCell.prototype.is_splittable = function () {
369 377 return false;
370 378 };
371 379
372 380
373 381 /**
374 382 * can the cell be merged with other cells
375 383 * @method is_mergeable
376 384 **/
377 385 HeadingCell.prototype.is_mergeable = function () {
378 386 return false;
379 387 };
380 388
381 389 /**
382 390 * Change heading level of cell, and re-render
383 391 * @method set_level
384 392 */
385 393 HeadingCell.prototype.set_level = function (level) {
386 394 this.level = level;
387 395 if (this.rendered) {
388 396 this.rendered = false;
389 397 this.render();
390 398 }
391 399 };
392 400
393 401 /** The depth of header cell, based on html (h1 to h6)
394 402 * @method get_level
395 403 * @return {integer} level - for 1 to 6
396 404 */
397 405 HeadingCell.prototype.get_level = function () {
398 406 return this.level;
399 407 };
400 408
401 409
402 410 HeadingCell.prototype.get_rendered = function () {
403 411 var r = this.element.find("div.text_cell_render");
404 412 return r.children().first().html();
405 413 };
406 414
407 415 HeadingCell.prototype.render = function () {
408 416 var cont = TextCell.prototype.render.apply(this);
409 417 if (cont) {
410 418 var text = this.get_text();
411 419 var math = null;
412 420 // Markdown headings must be a single line
413 421 text = text.replace(/\n/g, ' ');
414 422 if (text === "") { text = this.placeholder; }
415 423 text = new Array(this.level + 1).join("#") + " " + text;
416 424 var text_and_math = mathjaxutils.remove_math(text);
417 425 text = text_and_math[0];
418 426 math = text_and_math[1];
419 427 var html = marked.parser(marked.lexer(text));
420 428 html = mathjaxutils.replace_math(html, math);
421 429 html = security.sanitize_html(html);
422 430 var h = $($.parseHTML(html));
423 431 // add id and linkback anchor
424 432 var hash = h.text().replace(/ /g, '-');
425 433 h.attr('id', hash);
426 434 h.append(
427 435 $('<a/>')
428 436 .addClass('anchor-link')
429 437 .attr('href', '#' + hash)
430 438 .text('ΒΆ')
431 439 );
432 440 this.set_rendered(h);
433 441 this.element.find('div.input_area').hide();
434 442 this.element.find("div.text_cell_render").show();
435 443 this.typeset();
436 444 }
437 445 return cont;
438 446 };
439 447
440 448 // Backwards compatability.
441 449 IPython.TextCell = TextCell;
442 450 IPython.MarkdownCell = MarkdownCell;
443 451 IPython.RawCell = RawCell;
444 452 IPython.HeadingCell = HeadingCell;
445 453
446 454 var Cells = {
447 455 'TextCell': TextCell,
448 456 'MarkdownCell': MarkdownCell,
449 457 'RawCell': RawCell,
450 458 'HeadingCell': HeadingCell,
451 459 };
452 460 return Cells;
453 461 });
@@ -1,172 +1,173 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 ], function(IPython, $) {
7 'components/bootstrap-tour/build/js/bootstrap-tour.min',
8 ], function(IPython, $, Tour) {
8 9 "use strict";
9 10
10 11 var tour_style = "<div class='popover tour'>\n" +
11 12 "<div class='arrow'></div>\n" +
12 13 "<div style='position:absolute; top:7px; right:7px'>\n" +
13 14 "<button class='btn btn-default btn-sm icon-remove' data-role='end'></button>\n" +
14 15 "</div><h3 class='popover-title'></h3>\n" +
15 16 "<div class='popover-content'></div>\n" +
16 17 "<div class='popover-navigation'>\n" +
17 18 "<button class='btn btn-default icon-step-backward' data-role='prev'></button>\n" +
18 19 "<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\n" +
19 20 "<button id='tour-pause' class='btn btn-sm btn-default icon-pause' data-resume-text='' data-pause-text='' data-role='pause-resume'></button>\n" +
20 21 "</div>\n" +
21 22 "</div>";
22 23
23 24 var NotebookTour = function (notebook, events) {
24 25 var that = this;
25 26 this.notebook = notebook;
26 27 this.step_duration = 0;
27 28 this.events = events;
28 29 this.tour_steps = [
29 30 {
30 31 title: "Welcome to the Notebook Tour",
31 32 placement: 'bottom',
32 33 orphan: true,
33 34 content: "You can use the left and right arrow keys to go backwards and forwards.",
34 35 }, {
35 36 element: "#notebook_name",
36 37 title: "Filename",
37 38 placement: 'bottom',
38 39 content: "Click here to change the filename for this notebook."
39 40 }, {
40 41 element: $("#menus").parent(),
41 42 placement: 'bottom',
42 43 backdrop: true,
43 44 title: "Notebook Menubar",
44 45 content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
45 46 }, {
46 47 element: "#maintoolbar",
47 48 placement: 'bottom',
48 49 backdrop: true,
49 50 title: "Notebook Toolbar",
50 51 content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
51 52 }, {
52 53 element: "#modal_indicator",
53 54 title: "Mode Indicator",
54 55 placement: 'bottom',
55 56 content: "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.",
56 57 onShow: function(tour) { that.command_icon_hack(); }
57 58 }, {
58 59 element: "#modal_indicator",
59 60 title: "Command Mode",
60 61 placement: 'bottom',
61 62 onShow: function(tour) { notebook.command_mode(); that.command_icon_hack(); },
62 63 onNext: function(tour) { that.edit_mode(); },
63 64 content: "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area."
64 65 }, {
65 66 element: "#modal_indicator",
66 67 title: "Edit Mode",
67 68 placement: 'bottom',
68 69 onShow: function(tour) { that.edit_mode(); },
69 70 content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
70 71 }, {
71 72 element: '.selected',
72 73 title: "Edit Mode",
73 74 placement: 'bottom',
74 75 onShow: function(tour) { that.edit_mode(); },
75 76 content: "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell."
76 77 }, {
77 78 element: '.selected',
78 79 title: "Back to Command Mode",
79 80 placement: 'bottom',
80 81 onShow: function(tour) { notebook.command_mode(); },
81 82 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
82 83 content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
83 84 }, {
84 85 element: '#keyboard_shortcuts',
85 86 title: "Keyboard Shortcuts",
86 87 placement: 'bottom',
87 88 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
88 89 content: "You can click here to get a list of all of the keyboard shortcuts."
89 90 }, {
90 91 element: "#kernel_indicator",
91 92 title: "Kernel Indicator",
92 93 placement: 'bottom',
93 94 onShow: function(tour) { events.trigger('status_idle.Kernel');},
94 95 content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
95 96 }, {
96 97 element: "#kernel_indicator",
97 98 title: "Kernel Indicator",
98 99 placement: 'bottom',
99 100 onShow: function(tour) { events.trigger('status_busy.Kernel'); },
100 101 content: "The Kernel indicator looks like this when the Kernel is busy.",
101 102 }, {
102 103 element: ".icon-stop",
103 104 placement: 'bottom',
104 105 title: "Interrupting the Kernel",
105 106 onHide: function(tour) { events.trigger('status_idle.Kernel'); },
106 107 content: "To cancel a computation in progress, you can click here."
107 108 }, {
108 109 element: "#notification_kernel",
109 110 placement: 'bottom',
110 111 onShow: function(tour) { $('.icon-stop').click(); },
111 112 title: "Notification Area",
112 113 content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
113 114 }, {
114 115 title: "Fin.",
115 116 placement: 'bottom',
116 117 orphan: true,
117 118 content: "This concludes the IPython Notebook User Interface tour.Tour. Happy hacking!",
118 119 }
119 120 ];
120 121
121 122 this.tour = new Tour({
122 123 //orphan: true,
123 124 storage: false, // start tour from beginning every time
124 125 //element: $("#ipython_notebook"),
125 126 debug: true,
126 127 reflex: true, // click on element to continue tour
127 128 //backdrop: true, // show dark behind popover
128 129 animation: false,
129 130 duration: this.step_duration,
130 131 onStart: function() { console.log('tour started'); },
131 132 // TODO: remove the onPause/onResume logic once pi's patch has been
132 133 // merged upstream to make this work via data-resume-class and
133 134 // data-resume-text attributes.
134 135 onPause: this.toggle_pause_play,
135 136 onResume: this.toggle_pause_play,
136 137 steps: this.tour_steps,
137 138 template: tour_style,
138 139 orphan: true
139 140 });
140 141
141 142 };
142 143
143 144 NotebookTour.prototype.start = function () {
144 145 console.log("let's start the tour");
145 146 this.tour.init();
146 147 this.tour.start();
147 148 if (this.tour.ended())
148 149 {
149 150 this.tour.restart();
150 151 }
151 152 };
152 153
153 154 NotebookTour.prototype.command_icon_hack = function() {
154 155 $('#modal_indicator').css('min-height', 20);
155 156 };
156 157
157 158 NotebookTour.prototype.toggle_pause_play = function () {
158 159 $('#tour-pause').toggleClass('icon-pause icon-play');
159 160 };
160 161
161 162 NotebookTour.prototype.edit_mode = function() {
162 163 this.notebook.focus_cell();
163 164 this.notebook.edit_mode();
164 165 };
165 166
166 167 // For backwards compatability.
167 168 IPython.NotebookTour = NotebookTour;
168 169
169 170 return {'Tour': NotebookTour};
170 171
171 172 });
172 173
@@ -1,36 +1,45 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'tree/js/notebooklist',
8 8 ], function(IPython, $, notebooklist) {
9 9 "use strict";
10 10
11 11 var KernelList = function (selector, options) {
12 // Constructor
13 //
14 // Parameters:
15 // selector: string
16 // options: dictionary
17 // Dictionary of keyword arguments.
18 // session_list: SessionList instance
19 // base_url: string
20 // notebook_path: string
12 21 notebooklist.NotebookList.call(this, selector, $.extend({
13 22 element_name: 'running'},
14 23 options));
15 24 };
16 25
17 26 KernelList.prototype = Object.create(notebooklist.NotebookList.prototype);
18 27
19 28 KernelList.prototype.sessions_loaded = function (d) {
20 29 this.sessions = d;
21 30 this.clear_list();
22 31 var item;
23 32 for (var path in d) {
24 33 item = this.new_notebook_item(-1);
25 34 this.add_link('', path, item);
26 35 this.add_shutdown_button(item, this.sessions[path]);
27 36 }
28 37
29 38 $('#running_list_header').toggle($.isEmptyObject(d));
30 39 };
31 40
32 41 // Backwards compatability.
33 42 IPython.KernelList = KernelList;
34 43
35 44 return {'KernelList': KernelList};
36 45 });
@@ -1,438 +1,448 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 ], function(IPython, $, utils, dialog) {
10 10 "use strict";
11 11
12 12 var NotebookList = function (selector, options) {
13 // Constructor
14 //
15 // Parameters:
16 // selector: string
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // session_list: SessionList instance
20 // element_name: string
21 // base_url: string
22 // notebook_path: string
13 23 var that = this;
14 24 this.session_list = options.session_list;
15 25 // allow code re-use by just changing element_name in kernellist.js
16 26 this.element_name = options.element_name || 'notebook';
17 27 this.selector = selector;
18 28 if (this.selector !== undefined) {
19 29 this.element = $(selector);
20 30 this.style();
21 31 this.bind_events();
22 32 }
23 33 this.notebooks_list = [];
24 34 this.sessions = {};
25 35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
26 36 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
27 37 if (this.session_list && this.session_list.events) {
28 38 this.session_list.events.on('sessions_loaded.Dashboard',
29 39 function(e, d) { that.sessions_loaded(d); });
30 40 }
31 41 };
32 42
33 43 NotebookList.prototype.style = function () {
34 44 var prefix = '#' + this.element_name;
35 45 $(prefix + '_toolbar').addClass('list_toolbar');
36 46 $(prefix + '_list_info').addClass('toolbar_info');
37 47 $(prefix + '_buttons').addClass('toolbar_buttons');
38 48 $(prefix + '_list_header').addClass('list_header');
39 49 this.element.addClass("list_container");
40 50 };
41 51
42 52
43 53 NotebookList.prototype.bind_events = function () {
44 54 var that = this;
45 55 $('#refresh_' + this.element_name + '_list').click(function () {
46 56 that.load_sessions();
47 57 });
48 58 this.element.bind('dragover', function () {
49 59 return false;
50 60 });
51 61 this.element.bind('drop', function(event){
52 62 that.handleFilesUpload(event,'drop');
53 63 return false;
54 64 });
55 65 };
56 66
57 67 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
58 68 var that = this;
59 69 var files;
60 70 if(dropOrForm =='drop'){
61 71 files = event.originalEvent.dataTransfer.files;
62 72 } else
63 73 {
64 74 files = event.originalEvent.target.files;
65 75 }
66 76 for (var i = 0; i < files.length; i++) {
67 77 var f = files[i];
68 78 var reader = new FileReader();
69 79 reader.readAsText(f);
70 80 var name_and_ext = utils.splitext(f.name);
71 81 var file_ext = name_and_ext[1];
72 82 if (file_ext === '.ipynb') {
73 83 var item = that.new_notebook_item(0);
74 84 item.addClass('new-file');
75 85 that.add_name_input(f.name, item);
76 86 // Store the notebook item in the reader so we can use it later
77 87 // to know which item it belongs to.
78 88 $(reader).data('item', item);
79 89 reader.onload = function (event) {
80 90 var nbitem = $(event.target).data('item');
81 91 that.add_notebook_data(event.target.result, nbitem);
82 92 that.add_upload_button(nbitem);
83 93 };
84 94 } else {
85 95 var dialog_body = 'Uploaded notebooks must be .ipynb files';
86 96 dialog.modal({
87 97 title : 'Invalid file type',
88 98 body : dialog_body,
89 99 buttons : {'OK' : {'class' : 'btn-primary'}}
90 100 });
91 101 }
92 102 }
93 103 // Replace the file input form wth a clone of itself. This is required to
94 104 // reset the form. Otherwise, if you upload a file, delete it and try to
95 105 // upload it again, the changed event won't fire.
96 106 var form = $('input.fileinput');
97 107 form.replaceWith(form.clone(true));
98 108 return false;
99 109 };
100 110
101 111 NotebookList.prototype.clear_list = function (remove_uploads) {
102 112 // Clears the navigation tree.
103 113 //
104 114 // Parameters
105 115 // remove_uploads: bool=False
106 116 // Should upload prompts also be removed from the tree.
107 117 if (remove_uploads) {
108 118 this.element.children('.list_item').remove();
109 119 } else {
110 120 this.element.children('.list_item:not(.new-file)').remove();
111 121 }
112 122 };
113 123
114 124 NotebookList.prototype.load_sessions = function(){
115 125 this.session_list.load_sessions();
116 126 };
117 127
118 128
119 129 NotebookList.prototype.sessions_loaded = function(data){
120 130 this.sessions = data;
121 131 this.load_list();
122 132 };
123 133
124 134 NotebookList.prototype.load_list = function () {
125 135 var that = this;
126 136 var settings = {
127 137 processData : false,
128 138 cache : false,
129 139 type : "GET",
130 140 dataType : "json",
131 141 success : $.proxy(this.list_loaded, this),
132 142 error : $.proxy( function(xhr, status, error){
133 143 utils.log_ajax_error(xhr, status, error);
134 144 that.list_loaded([], null, null, {msg:"Error connecting to server."});
135 145 },this)
136 146 };
137 147
138 148 var url = utils.url_join_encode(
139 149 this.base_url,
140 150 'api',
141 151 'notebooks',
142 152 this.notebook_path
143 153 );
144 154 $.ajax(url, settings);
145 155 };
146 156
147 157
148 158 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
149 159 var message = 'Notebook list empty.';
150 160 if (param !== undefined && param.msg) {
151 161 message = param.msg;
152 162 }
153 163 var item = null;
154 164 var len = data.length;
155 165 this.clear_list();
156 166 if (len === 0) {
157 167 item = this.new_notebook_item(0);
158 168 var span12 = item.children().first();
159 169 span12.empty();
160 170 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
161 171 }
162 172 var path = this.notebook_path;
163 173 var offset = 0;
164 174 if (path !== '') {
165 175 item = this.new_notebook_item(0);
166 176 this.add_dir(path, '..', item);
167 177 offset = 1;
168 178 }
169 179 for (var i=0; i<len; i++) {
170 180 if (data[i].type === 'directory') {
171 181 var name = data[i].name;
172 182 item = this.new_notebook_item(i+offset);
173 183 this.add_dir(path, name, item);
174 184 } else {
175 185 var name = data[i].name;
176 186 item = this.new_notebook_item(i+offset);
177 187 this.add_link(path, name, item);
178 188 name = utils.url_path_join(path, name);
179 189 if(this.sessions[name] === undefined){
180 190 this.add_delete_button(item);
181 191 } else {
182 192 this.add_shutdown_button(item,this.sessions[name]);
183 193 }
184 194 }
185 195 }
186 196 };
187 197
188 198
189 199 NotebookList.prototype.new_notebook_item = function (index) {
190 200 var item = $('<div/>').addClass("list_item").addClass("row");
191 201 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
192 202 // item.css('border-top-style','none');
193 203 item.append($("<div/>").addClass("col-md-12").append(
194 204 $('<i/>').addClass('item_icon')
195 205 ).append(
196 206 $("<a/>").addClass("item_link").append(
197 207 $("<span/>").addClass("item_name")
198 208 )
199 209 ).append(
200 210 $('<div/>').addClass("item_buttons btn-group pull-right")
201 211 ));
202 212
203 213 if (index === -1) {
204 214 this.element.append(item);
205 215 } else {
206 216 this.element.children().eq(index).after(item);
207 217 }
208 218 return item;
209 219 };
210 220
211 221
212 222 NotebookList.prototype.add_dir = function (path, name, item) {
213 223 item.data('name', name);
214 224 item.data('path', path);
215 225 item.find(".item_name").text(name);
216 226 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
217 227 item.find("a.item_link")
218 228 .attr('href',
219 229 utils.url_join_encode(
220 230 this.base_url,
221 231 "tree",
222 232 path,
223 233 name
224 234 )
225 235 );
226 236 };
227 237
228 238
229 239 NotebookList.prototype.add_link = function (path, nbname, item) {
230 240 item.data('nbname', nbname);
231 241 item.data('path', path);
232 242 item.find(".item_name").text(nbname);
233 243 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
234 244 item.find("a.item_link")
235 245 .attr('href',
236 246 utils.url_join_encode(
237 247 this.base_url,
238 248 "notebooks",
239 249 path,
240 250 nbname
241 251 )
242 252 ).attr('target','_blank');
243 253 };
244 254
245 255
246 256 NotebookList.prototype.add_name_input = function (nbname, item) {
247 257 item.data('nbname', nbname);
248 258 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
249 259 item.find(".item_name").empty().append(
250 260 $('<input/>')
251 261 .addClass("nbname_input")
252 262 .attr('value', utils.splitext(nbname)[0])
253 263 .attr('size', '30')
254 264 .attr('type', 'text')
255 265 );
256 266 };
257 267
258 268
259 269 NotebookList.prototype.add_notebook_data = function (data, item) {
260 270 item.data('nbdata', data);
261 271 };
262 272
263 273
264 274 NotebookList.prototype.add_shutdown_button = function (item, session) {
265 275 var that = this;
266 276 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
267 277 click(function (e) {
268 278 var settings = {
269 279 processData : false,
270 280 cache : false,
271 281 type : "DELETE",
272 282 dataType : "json",
273 283 success : function () {
274 284 that.load_sessions();
275 285 },
276 286 error : utils.log_ajax_error,
277 287 };
278 288 var url = utils.url_join_encode(
279 289 that.base_url,
280 290 'api/sessions',
281 291 session
282 292 );
283 293 $.ajax(url, settings);
284 294 return false;
285 295 });
286 296 // var new_buttons = item.find('a'); // shutdown_button;
287 297 item.find(".item_buttons").text("").append(shutdown_button);
288 298 };
289 299
290 300 NotebookList.prototype.add_delete_button = function (item) {
291 301 var new_buttons = $('<span/>').addClass("btn-group pull-right");
292 302 var notebooklist = this;
293 303 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
294 304 click(function (e) {
295 305 // $(this) is the button that was clicked.
296 306 var that = $(this);
297 307 // We use the nbname and notebook_id from the parent notebook_item element's
298 308 // data because the outer scopes values change as we iterate through the loop.
299 309 var parent_item = that.parents('div.list_item');
300 310 var nbname = parent_item.data('nbname');
301 311 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
302 312 dialog.modal({
303 313 title : "Delete notebook",
304 314 body : message,
305 315 buttons : {
306 316 Delete : {
307 317 class: "btn-danger",
308 318 click: function() {
309 319 var settings = {
310 320 processData : false,
311 321 cache : false,
312 322 type : "DELETE",
313 323 dataType : "json",
314 324 success : function (data, status, xhr) {
315 325 parent_item.remove();
316 326 },
317 327 error : utils.log_ajax_error,
318 328 };
319 329 var url = utils.url_join_encode(
320 330 notebooklist.base_url,
321 331 'api/notebooks',
322 332 notebooklist.notebook_path,
323 333 nbname
324 334 );
325 335 $.ajax(url, settings);
326 336 }
327 337 },
328 338 Cancel : {}
329 339 }
330 340 });
331 341 return false;
332 342 });
333 343 item.find(".item_buttons").text("").append(delete_button);
334 344 };
335 345
336 346
337 347 NotebookList.prototype.add_upload_button = function (item) {
338 348 var that = this;
339 349 var upload_button = $('<button/>').text("Upload")
340 350 .addClass('btn btn-primary btn-xs upload_button')
341 351 .click(function (e) {
342 352 var nbname = item.find('.item_name > input').val();
343 353 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
344 354 nbname = nbname + ".ipynb";
345 355 }
346 356 var path = that.notebook_path;
347 357 var nbdata = item.data('nbdata');
348 358 var content_type = 'application/json';
349 359 var model = {
350 360 content : JSON.parse(nbdata),
351 361 };
352 362 var settings = {
353 363 processData : false,
354 364 cache : false,
355 365 type : 'PUT',
356 366 dataType : 'json',
357 367 data : JSON.stringify(model),
358 368 headers : {'Content-Type': content_type},
359 369 success : function (data, status, xhr) {
360 370 that.add_link(path, nbname, item);
361 371 that.add_delete_button(item);
362 372 },
363 373 error : utils.log_ajax_error,
364 374 };
365 375
366 376 var url = utils.url_join_encode(
367 377 that.base_url,
368 378 'api/notebooks',
369 379 that.notebook_path,
370 380 nbname
371 381 );
372 382 $.ajax(url, settings);
373 383 return false;
374 384 });
375 385 var cancel_button = $('<button/>').text("Cancel")
376 386 .addClass("btn btn-default btn-xs")
377 387 .click(function (e) {
378 388 console.log('cancel click');
379 389 item.remove();
380 390 return false;
381 391 });
382 392 item.find(".item_buttons").empty()
383 393 .append(upload_button)
384 394 .append(cancel_button);
385 395 };
386 396
387 397
388 398 NotebookList.prototype.new_notebook = function(){
389 399 var path = this.notebook_path;
390 400 var base_url = this.base_url;
391 401 var settings = {
392 402 processData : false,
393 403 cache : false,
394 404 type : "POST",
395 405 dataType : "json",
396 406 async : false,
397 407 success : function (data, status, xhr) {
398 408 var notebook_name = data.name;
399 409 window.open(
400 410 utils.url_join_encode(
401 411 base_url,
402 412 'notebooks',
403 413 path,
404 414 notebook_name),
405 415 '_blank'
406 416 );
407 417 },
408 418 error : $.proxy(this.new_notebook_failed, this),
409 419 };
410 420 var url = utils.url_join_encode(
411 421 base_url,
412 422 'api/notebooks',
413 423 path
414 424 );
415 425 $.ajax(url, settings);
416 426 };
417 427
418 428
419 429 NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
420 430 utils.log_ajax_error(xhr, status, error);
421 431 var msg;
422 432 if (xhr.responseJSON && xhr.responseJSON.message) {
423 433 msg = xhr.responseJSON.message;
424 434 } else {
425 435 msg = xhr.statusText;
426 436 }
427 437 dialog.modal({
428 438 title : 'Creating Notebook Failed',
429 439 body : "The error was: " + msg,
430 440 buttons : {'OK' : {'class' : 'btn-primary'}}
431 441 });
432 442 };
433 443
434 444 // Backwards compatability.
435 445 IPython.NotebookList = NotebookList;
436 446
437 447 return {'NotebookList': NotebookList};
438 448 });
@@ -1,49 +1,56 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 var SesssionList = function (options, events) {
11 var SesssionList = function (options) {
12 // Constructor
13 //
14 // Parameters:
15 // options: dictionary
16 // Dictionary of keyword arguments.
17 // events: $(Events) instance
18 // base_url : string
12 19 this.events = options.events;
13 20 this.sessions = {};
14 21 this.base_url = options.base_url || utils.get_body_data("baseUrl");
15 22 };
16 23
17 24 SesssionList.prototype.load_sessions = function(){
18 25 var that = this;
19 26 var settings = {
20 27 processData : false,
21 28 cache : false,
22 29 type : "GET",
23 30 dataType : "json",
24 31 success : $.proxy(that.sessions_loaded, this),
25 32 error : utils.log_ajax_error,
26 33 };
27 34 var url = utils.url_join_encode(this.base_url, 'api/sessions');
28 35 $.ajax(url, settings);
29 36 };
30 37
31 38 SesssionList.prototype.sessions_loaded = function(data){
32 39 this.sessions = {};
33 40 var len = data.length;
34 41 var nb_path;
35 42 for (var i=0; i<len; i++) {
36 43 nb_path = utils.url_path_join(
37 44 data[i].notebook.path,
38 45 data[i].notebook.name
39 46 );
40 47 this.sessions[nb_path] = data[i].id;
41 48 }
42 49 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
43 50 };
44 51
45 52 // Backwards compatability.
46 53 IPython.SesssionList = SesssionList;
47 54
48 55 return {'SesssionList': SesssionList};
49 56 });
General Comments 0
You need to be logged in to leave comments. Login now