##// END OF EJS Templates
path with spaces completely fixed
Zachary Sailer -
Show More
@@ -1,104 +1,103 b''
1 """Tornado handlers for the live notebook view.
1 """Tornado handlers for the live notebook view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 from tornado import web
20 from tornado import web
21 HTTPError = web.HTTPError
21 HTTPError = web.HTTPError
22
22
23 from ..base.handlers import IPythonHandler
23 from ..base.handlers import IPythonHandler
24 from ..utils import url_path_join
24 from ..utils import url_path_join
25 from urllib import quote
25 from urllib import quote
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Handlers
28 # Handlers
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class NewPathHandler(IPythonHandler):
32 class NewPathHandler(IPythonHandler):
33
33
34 @web.authenticated
34 @web.authenticated
35 def get(self, notebook_path):
35 def get(self, notebook_path):
36 notebook_name = self.notebook_manager.new_notebook(notebook_path)
36 notebook_name = self.notebook_manager.new_notebook(notebook_path)
37 self.redirect(url_path_join(self.base_project_url,"notebooks", notebook_path, notebook_name))
37 self.redirect(url_path_join(self.base_project_url,"notebooks", notebook_path, notebook_name))
38
38
39
39
40 class NewHandler(IPythonHandler):
40 class NewHandler(IPythonHandler):
41
41
42 @web.authenticated
42 @web.authenticated
43 def get(self):
43 def get(self):
44 notebook_name = self.notebook_manager.new_notebook()
44 notebook_name = self.notebook_manager.new_notebook()
45 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
45 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
46
46
47
47
48 class NamedNotebookHandler(IPythonHandler):
48 class NamedNotebookHandler(IPythonHandler):
49
49
50 @web.authenticated
50 @web.authenticated
51 def get(self, notebook_path):
51 def get(self, notebook_path):
52 nbm = self.notebook_manager
52 nbm = self.notebook_manager
53 name, path = nbm.named_notebook_path(notebook_path)
53 name, path = nbm.named_notebook_path(notebook_path)
54 if name != None:
54 if name != None:
55 name = quote(name)
55 name = nbm.url_encode(name)
56 if path == None:
56 if path == None:
57 project = self.project + '/' + name
57 project = self.project + '/' + name
58 else:
58 else:
59 project = self.project + '/' + path +'/'+ name
59 project = self.project + '/' + path +'/'+ name
60 path = nbm.url_encode(path)
60 if not nbm.notebook_exists(notebook_path):
61 if not nbm.notebook_exists(notebook_path):
61 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
62 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
62 path = nbm.url_encode(path)
63 name = nbm.url_encode(name)
64 self.write(self.render_template('notebook.html',
63 self.write(self.render_template('notebook.html',
65 project=project,
64 project=project,
66 notebook_path=path,
65 notebook_path=path,
67 notebook_name=name,
66 notebook_name=name,
68 kill_kernel=False,
67 kill_kernel=False,
69 mathjax_url=self.mathjax_url,
68 mathjax_url=self.mathjax_url,
70 )
69 )
71 )
70 )
72
71
73 @web.authenticated
72 @web.authenticated
74 def post(self, notebook_path):
73 def post(self, notebook_path):
75 nbm =self.notebook_manager
74 nbm =self.notebook_manager
76 notebook_name = nbm.new_notebook()
75 notebook_name = nbm.new_notebook()
77
76
78
77
79 class NotebookCopyHandler(IPythonHandler):
78 class NotebookCopyHandler(IPythonHandler):
80
79
81 @web.authenticated
80 @web.authenticated
82 def get(self, notebook_path=None):
81 def get(self, notebook_path=None):
83 nbm = self.notebook_manager
82 nbm = self.notebook_manager
84 name, path = nbm.named_notebook_path(notebook_path)
83 name, path = nbm.named_notebook_path(notebook_path)
85 notebook_name = self.notebook_manager.copy_notebook(name, path)
84 notebook_name = self.notebook_manager.copy_notebook(name, path)
86 if path==None:
85 if path==None:
87 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
86 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
88 else:
87 else:
89 self.redirect(url_path_join(self.base_project_url, "notebooks", path, notebook_name))
88 self.redirect(url_path_join(self.base_project_url, "notebooks", path, notebook_name))
90
89
91
90
92 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
93 # URL to handler mappings
92 # URL to handler mappings
94 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
95
94
96
95
97 _notebook_path_regex = r"(?P<notebook_path>.+)"
96 _notebook_path_regex = r"(?P<notebook_path>.+)"
98
97
99 default_handlers = [
98 default_handlers = [
100 (r"/notebooks/%s/new" % _notebook_path_regex, NewPathHandler),
99 (r"/notebooks/%s/new" % _notebook_path_regex, NewPathHandler),
101 (r"/notebooks/new", NewHandler),
100 (r"/notebooks/new", NewHandler),
102 (r"/notebooks/%s/copy" % _notebook_path_regex, NotebookCopyHandler),
101 (r"/notebooks/%s/copy" % _notebook_path_regex, NotebookCopyHandler),
103 (r"/notebooks/%s" % _notebook_path_regex, NamedNotebookHandler)
102 (r"/notebooks/%s" % _notebook_path_regex, NamedNotebookHandler)
104 ]
103 ]
@@ -1,125 +1,126 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11 "use strict";
11 "use strict";
12
12
13 // for the time beeing, we have to pass marked as a parameter here,
13 // for the time beeing, we have to pass marked as a parameter here,
14 // as injecting require.js make marked not to put itself in the globals,
14 // as injecting require.js make marked not to put itself in the globals,
15 // which make both this file fail at setting marked configuration, and textcell.js
15 // which make both this file fail at setting marked configuration, and textcell.js
16 // which search marked into global.
16 // which search marked into global.
17 require(['components/marked/lib/marked'],
17 require(['components/marked/lib/marked'],
18
18
19 function (marked) {
19 function (marked) {
20
20
21 window.marked = marked
21 window.marked = marked
22
22
23 // monkey patch CM to be able to syntax highlight cell magics
23 // monkey patch CM to be able to syntax highlight cell magics
24 // bug reported upstream,
24 // bug reported upstream,
25 // see https://github.com/marijnh/CodeMirror2/issues/670
25 // see https://github.com/marijnh/CodeMirror2/issues/670
26 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
26 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
27 console.log('patching CM for undefined indent');
27 console.log('patching CM for undefined indent');
28 CodeMirror.modes.null = function() {
28 CodeMirror.modes.null = function() {
29 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
29 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
30 }
30 }
31 }
31 }
32
32
33 CodeMirror.patchedGetMode = function(config, mode){
33 CodeMirror.patchedGetMode = function(config, mode){
34 var cmmode = CodeMirror.getMode(config, mode);
34 var cmmode = CodeMirror.getMode(config, mode);
35 if(cmmode.indent == null)
35 if(cmmode.indent == null)
36 {
36 {
37 console.log('patch mode "' , mode, '" on the fly');
37 console.log('patch mode "' , mode, '" on the fly');
38 cmmode.indent = function(){return 0};
38 cmmode.indent = function(){return 0};
39 }
39 }
40 return cmmode;
40 return cmmode;
41 }
41 }
42 // end monkey patching CodeMirror
42 // end monkey patching CodeMirror
43
43
44 IPython.mathjaxutils.init();
44 IPython.mathjaxutils.init();
45
45
46 $('#ipython-main-app').addClass('border-box-sizing');
46 $('#ipython-main-app').addClass('border-box-sizing');
47 $('div#notebook_panel').addClass('border-box-sizing');
47 $('div#notebook_panel').addClass('border-box-sizing');
48
48
49 var baseProjectUrl = $('body').data('baseProjectUrl');
49 var baseProjectUrl = $('body').data('baseProjectUrl');
50 var notebookPath = $('body').data('notebookPath');
50 var notebookPath = $('body').data('notebookPath');
51 var notebookName = $('body').data('notebookName');
51 var notebookName = $('body').data('notebookName');
52 notebookName = decodeURIComponent(notebookName);
52 notebookName = decodeURIComponent(notebookName);
53 notebookPath = decodeURIComponent(notebookPath);
53 console.log(notebookName);
54 console.log(notebookName);
54 if (notebookPath == 'None'){
55 if (notebookPath == 'None'){
55 notebookPath = "";
56 notebookPath = "";
56 }
57 }
57
58
58 IPython.page = new IPython.Page();
59 IPython.page = new IPython.Page();
59 IPython.layout_manager = new IPython.LayoutManager();
60 IPython.layout_manager = new IPython.LayoutManager();
60 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
61 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
61 IPython.quick_help = new IPython.QuickHelp();
62 IPython.quick_help = new IPython.QuickHelp();
62 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
63 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
63 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName});
64 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName});
64 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
65 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
65 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath})
66 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath})
66 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
67 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
67 IPython.tooltip = new IPython.Tooltip()
68 IPython.tooltip = new IPython.Tooltip()
68 IPython.notification_area = new IPython.NotificationArea('#notification_area')
69 IPython.notification_area = new IPython.NotificationArea('#notification_area')
69 IPython.notification_area.init_notification_widgets();
70 IPython.notification_area.init_notification_widgets();
70
71
71 IPython.layout_manager.do_resize();
72 IPython.layout_manager.do_resize();
72
73
73 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
74 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
74 '<span id="test2" style="font-weight: bold;">x</span>'+
75 '<span id="test2" style="font-weight: bold;">x</span>'+
75 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
76 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
76 var nh = $('#test1').innerHeight();
77 var nh = $('#test1').innerHeight();
77 var bh = $('#test2').innerHeight();
78 var bh = $('#test2').innerHeight();
78 var ih = $('#test3').innerHeight();
79 var ih = $('#test3').innerHeight();
79 if(nh != bh || nh != ih) {
80 if(nh != bh || nh != ih) {
80 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
81 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
81 }
82 }
82 $('#fonttest').remove();
83 $('#fonttest').remove();
83
84
84 IPython.page.show();
85 IPython.page.show();
85
86
86 IPython.layout_manager.do_resize();
87 IPython.layout_manager.do_resize();
87 var first_load = function () {
88 var first_load = function () {
88 IPython.layout_manager.do_resize();
89 IPython.layout_manager.do_resize();
89 var hash = document.location.hash;
90 var hash = document.location.hash;
90 if (hash) {
91 if (hash) {
91 document.location.hash = '';
92 document.location.hash = '';
92 document.location.hash = hash;
93 document.location.hash = hash;
93 }
94 }
94 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
95 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
95 // only do this once
96 // only do this once
96 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
97 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
97 };
98 };
98
99
99 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
100 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
100 $([IPython.events]).trigger('app_initialized.NotebookApp');
101 $([IPython.events]).trigger('app_initialized.NotebookApp');
101 IPython.notebook.load_notebook(notebookName, notebookPath);
102 IPython.notebook.load_notebook(notebookName, notebookPath);
102
103
103 if (marked) {
104 if (marked) {
104 marked.setOptions({
105 marked.setOptions({
105 gfm : true,
106 gfm : true,
106 tables: true,
107 tables: true,
107 langPrefix: "language-",
108 langPrefix: "language-",
108 highlight: function(code, lang) {
109 highlight: function(code, lang) {
109 if (!lang) {
110 if (!lang) {
110 // no language, no highlight
111 // no language, no highlight
111 return code;
112 return code;
112 }
113 }
113 var highlighted;
114 var highlighted;
114 try {
115 try {
115 highlighted = hljs.highlight(lang, code, false);
116 highlighted = hljs.highlight(lang, code, false);
116 } catch(err) {
117 } catch(err) {
117 highlighted = hljs.highlightAuto(code);
118 highlighted = hljs.highlightAuto(code);
118 }
119 }
119 return highlighted.value;
120 return highlighted.value;
120 }
121 }
121 })
122 })
122 }
123 }
123 }
124 }
124
125
125 );
126 );
@@ -1,2152 +1,2153 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16 var key = IPython.utils.keycodes;
16 var key = IPython.utils.keycodes;
17
17
18 /**
18 /**
19 * A notebook contains and manages cells.
19 * A notebook contains and manages cells.
20 *
20 *
21 * @class Notebook
21 * @class Notebook
22 * @constructor
22 * @constructor
23 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {String} selector A jQuery selector for the notebook's DOM element
24 * @param {Object} [options] A config object
24 * @param {Object} [options] A config object
25 */
25 */
26 var Notebook = function (selector, options) {
26 var Notebook = function (selector, options) {
27 var options = options || {};
27 var options = options || {};
28 this._baseProjectUrl = options.baseProjectUrl;
28 this._baseProjectUrl = options.baseProjectUrl;
29 this.notebook_path = options.notebookPath;
29 this.notebook_path = options.notebookPath;
30 this.notebook_name = options.notebookName;
30 this.notebook_name = options.notebookName;
31 this.element = $(selector);
31 this.element = $(selector);
32 this.element.scroll();
32 this.element.scroll();
33 this.element.data("notebook", this);
33 this.element.data("notebook", this);
34 this.next_prompt_number = 1;
34 this.next_prompt_number = 1;
35 this.session = null;
35 this.session = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 this.set_dirty(false);
41 this.set_dirty(false);
42 this.metadata = {};
42 this.metadata = {};
43 this._checkpoint_after_save = false;
43 this._checkpoint_after_save = false;
44 this.last_checkpoint = null;
44 this.last_checkpoint = null;
45 this.checkpoints = [];
45 this.checkpoints = [];
46 this.autosave_interval = 0;
46 this.autosave_interval = 0;
47 this.autosave_timer = null;
47 this.autosave_timer = null;
48 // autosave *at most* every two minutes
48 // autosave *at most* every two minutes
49 this.minimum_autosave_interval = 120000;
49 this.minimum_autosave_interval = 120000;
50 // single worksheet for now
50 // single worksheet for now
51 this.worksheet_metadata = {};
51 this.worksheet_metadata = {};
52 this.control_key_active = false;
52 this.control_key_active = false;
53 this.notebook_name = null;
53 this.notebook_name = null;
54 this.notebook_name_blacklist_re = /[\/\\:]/;
54 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.nbformat = 3 // Increment this when changing the nbformat
55 this.nbformat = 3 // Increment this when changing the nbformat
56 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 this.nbformat_minor = 0 // Increment this when changing the nbformat
57 this.style();
57 this.style();
58 this.create_elements();
58 this.create_elements();
59 this.bind_events();
59 this.bind_events();
60 };
60 };
61
61
62 /**
62 /**
63 * Tweak the notebook's CSS style.
63 * Tweak the notebook's CSS style.
64 *
64 *
65 * @method style
65 * @method style
66 */
66 */
67 Notebook.prototype.style = function () {
67 Notebook.prototype.style = function () {
68 $('div#notebook').addClass('border-box-sizing');
68 $('div#notebook').addClass('border-box-sizing');
69 };
69 };
70
70
71 /**
71 /**
72 * Get the root URL of the notebook server.
72 * Get the root URL of the notebook server.
73 *
73 *
74 * @method baseProjectUrl
74 * @method baseProjectUrl
75 * @return {String} The base project URL
75 * @return {String} The base project URL
76 */
76 */
77 Notebook.prototype.baseProjectUrl = function(){
77 Notebook.prototype.baseProjectUrl = function(){
78 return this._baseProjectUrl || $('body').data('baseProjectUrl');
78 return this._baseProjectUrl || $('body').data('baseProjectUrl');
79 };
79 };
80
80
81 Notebook.prototype.notebookName = function() {
81 Notebook.prototype.notebookName = function() {
82 var name = $('body').data('notebookName');
82 var name = $('body').data('notebookName');
83 return name;
83 return name;
84 };
84 };
85
85
86 Notebook.prototype.notebookPath = function() {
86 Notebook.prototype.notebookPath = function() {
87 var path = $('body').data('notebookPath');
87 var path = $('body').data('notebookPath');
88 path = decodeURIComponent(path);
88 if (path != 'None') {
89 if (path != 'None') {
89 if (path[path.length-1] != '/') {
90 if (path[path.length-1] != '/') {
90 path = path.substring(0,path.length);
91 path = path.substring(0,path.length);
91 };
92 };
92 return path;
93 return path;
93 } else {
94 } else {
94 return '';
95 return '';
95 }
96 }
96 };
97 };
97
98
98 /**
99 /**
99 * Create an HTML and CSS representation of the notebook.
100 * Create an HTML and CSS representation of the notebook.
100 *
101 *
101 * @method create_elements
102 * @method create_elements
102 */
103 */
103 Notebook.prototype.create_elements = function () {
104 Notebook.prototype.create_elements = function () {
104 // We add this end_space div to the end of the notebook div to:
105 // We add this end_space div to the end of the notebook div to:
105 // i) provide a margin between the last cell and the end of the notebook
106 // i) provide a margin between the last cell and the end of the notebook
106 // ii) to prevent the div from scrolling up when the last cell is being
107 // ii) to prevent the div from scrolling up when the last cell is being
107 // edited, but is too low on the page, which browsers will do automatically.
108 // edited, but is too low on the page, which browsers will do automatically.
108 var that = this;
109 var that = this;
109 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
110 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
110 var end_space = $('<div/>').addClass('end_space');
111 var end_space = $('<div/>').addClass('end_space');
111 end_space.dblclick(function (e) {
112 end_space.dblclick(function (e) {
112 var ncells = that.ncells();
113 var ncells = that.ncells();
113 that.insert_cell_below('code',ncells-1);
114 that.insert_cell_below('code',ncells-1);
114 });
115 });
115 this.element.append(this.container);
116 this.element.append(this.container);
116 this.container.append(end_space);
117 this.container.append(end_space);
117 $('div#notebook').addClass('border-box-sizing');
118 $('div#notebook').addClass('border-box-sizing');
118 };
119 };
119
120
120 /**
121 /**
121 * Bind JavaScript events: key presses and custom IPython events.
122 * Bind JavaScript events: key presses and custom IPython events.
122 *
123 *
123 * @method bind_events
124 * @method bind_events
124 */
125 */
125 Notebook.prototype.bind_events = function () {
126 Notebook.prototype.bind_events = function () {
126 var that = this;
127 var that = this;
127
128
128 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
129 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
129 var index = that.find_cell_index(data.cell);
130 var index = that.find_cell_index(data.cell);
130 var new_cell = that.insert_cell_below('code',index);
131 var new_cell = that.insert_cell_below('code',index);
131 new_cell.set_text(data.text);
132 new_cell.set_text(data.text);
132 that.dirty = true;
133 that.dirty = true;
133 });
134 });
134
135
135 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
136 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
136 that.dirty = data.value;
137 that.dirty = data.value;
137 });
138 });
138
139
139 $([IPython.events]).on('select.Cell', function (event, data) {
140 $([IPython.events]).on('select.Cell', function (event, data) {
140 var index = that.find_cell_index(data.cell);
141 var index = that.find_cell_index(data.cell);
141 that.select(index);
142 that.select(index);
142 });
143 });
143
144
144 $([IPython.events]).on('status_autorestarting.Kernel', function () {
145 $([IPython.events]).on('status_autorestarting.Kernel', function () {
145 IPython.dialog.modal({
146 IPython.dialog.modal({
146 title: "Kernel Restarting",
147 title: "Kernel Restarting",
147 body: "The kernel appears to have died. It will restart automatically.",
148 body: "The kernel appears to have died. It will restart automatically.",
148 buttons: {
149 buttons: {
149 OK : {
150 OK : {
150 class : "btn-primary"
151 class : "btn-primary"
151 }
152 }
152 }
153 }
153 });
154 });
154 });
155 });
155
156
156
157
157 $(document).keydown(function (event) {
158 $(document).keydown(function (event) {
158
159
159 // Save (CTRL+S) or (AppleKey+S)
160 // Save (CTRL+S) or (AppleKey+S)
160 //metaKey = applekey on mac
161 //metaKey = applekey on mac
161 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
162 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
162 that.save_checkpoint();
163 that.save_checkpoint();
163 event.preventDefault();
164 event.preventDefault();
164 return false;
165 return false;
165 } else if (event.which === key.ESC) {
166 } else if (event.which === key.ESC) {
166 // Intercept escape at highest level to avoid closing
167 // Intercept escape at highest level to avoid closing
167 // websocket connection with firefox
168 // websocket connection with firefox
168 IPython.pager.collapse();
169 IPython.pager.collapse();
169 event.preventDefault();
170 event.preventDefault();
170 } else if (event.which === key.SHIFT) {
171 } else if (event.which === key.SHIFT) {
171 // ignore shift keydown
172 // ignore shift keydown
172 return true;
173 return true;
173 }
174 }
174 if (event.which === key.UPARROW && !event.shiftKey) {
175 if (event.which === key.UPARROW && !event.shiftKey) {
175 var cell = that.get_selected_cell();
176 var cell = that.get_selected_cell();
176 if (cell && cell.at_top()) {
177 if (cell && cell.at_top()) {
177 event.preventDefault();
178 event.preventDefault();
178 that.select_prev();
179 that.select_prev();
179 };
180 };
180 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
181 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
181 var cell = that.get_selected_cell();
182 var cell = that.get_selected_cell();
182 if (cell && cell.at_bottom()) {
183 if (cell && cell.at_bottom()) {
183 event.preventDefault();
184 event.preventDefault();
184 that.select_next();
185 that.select_next();
185 };
186 };
186 } else if (event.which === key.ENTER && event.shiftKey) {
187 } else if (event.which === key.ENTER && event.shiftKey) {
187 that.execute_selected_cell();
188 that.execute_selected_cell();
188 return false;
189 return false;
189 } else if (event.which === key.ENTER && event.altKey) {
190 } else if (event.which === key.ENTER && event.altKey) {
190 // Execute code cell, and insert new in place
191 // Execute code cell, and insert new in place
191 that.execute_selected_cell();
192 that.execute_selected_cell();
192 // Only insert a new cell, if we ended up in an already populated cell
193 // Only insert a new cell, if we ended up in an already populated cell
193 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
194 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
194 that.insert_cell_above('code');
195 that.insert_cell_above('code');
195 }
196 }
196 return false;
197 return false;
197 } else if (event.which === key.ENTER && event.ctrlKey) {
198 } else if (event.which === key.ENTER && event.ctrlKey) {
198 that.execute_selected_cell({terminal:true});
199 that.execute_selected_cell({terminal:true});
199 return false;
200 return false;
200 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
201 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
201 that.control_key_active = true;
202 that.control_key_active = true;
202 return false;
203 return false;
203 } else if (event.which === 88 && that.control_key_active) {
204 } else if (event.which === 88 && that.control_key_active) {
204 // Cut selected cell = x
205 // Cut selected cell = x
205 that.cut_cell();
206 that.cut_cell();
206 that.control_key_active = false;
207 that.control_key_active = false;
207 return false;
208 return false;
208 } else if (event.which === 67 && that.control_key_active) {
209 } else if (event.which === 67 && that.control_key_active) {
209 // Copy selected cell = c
210 // Copy selected cell = c
210 that.copy_cell();
211 that.copy_cell();
211 that.control_key_active = false;
212 that.control_key_active = false;
212 return false;
213 return false;
213 } else if (event.which === 86 && that.control_key_active) {
214 } else if (event.which === 86 && that.control_key_active) {
214 // Paste below selected cell = v
215 // Paste below selected cell = v
215 that.paste_cell_below();
216 that.paste_cell_below();
216 that.control_key_active = false;
217 that.control_key_active = false;
217 return false;
218 return false;
218 } else if (event.which === 68 && that.control_key_active) {
219 } else if (event.which === 68 && that.control_key_active) {
219 // Delete selected cell = d
220 // Delete selected cell = d
220 that.delete_cell();
221 that.delete_cell();
221 that.control_key_active = false;
222 that.control_key_active = false;
222 return false;
223 return false;
223 } else if (event.which === 65 && that.control_key_active) {
224 } else if (event.which === 65 && that.control_key_active) {
224 // Insert code cell above selected = a
225 // Insert code cell above selected = a
225 that.insert_cell_above('code');
226 that.insert_cell_above('code');
226 that.control_key_active = false;
227 that.control_key_active = false;
227 return false;
228 return false;
228 } else if (event.which === 66 && that.control_key_active) {
229 } else if (event.which === 66 && that.control_key_active) {
229 // Insert code cell below selected = b
230 // Insert code cell below selected = b
230 that.insert_cell_below('code');
231 that.insert_cell_below('code');
231 that.control_key_active = false;
232 that.control_key_active = false;
232 return false;
233 return false;
233 } else if (event.which === 89 && that.control_key_active) {
234 } else if (event.which === 89 && that.control_key_active) {
234 // To code = y
235 // To code = y
235 that.to_code();
236 that.to_code();
236 that.control_key_active = false;
237 that.control_key_active = false;
237 return false;
238 return false;
238 } else if (event.which === 77 && that.control_key_active) {
239 } else if (event.which === 77 && that.control_key_active) {
239 // To markdown = m
240 // To markdown = m
240 that.to_markdown();
241 that.to_markdown();
241 that.control_key_active = false;
242 that.control_key_active = false;
242 return false;
243 return false;
243 } else if (event.which === 84 && that.control_key_active) {
244 } else if (event.which === 84 && that.control_key_active) {
244 // To Raw = t
245 // To Raw = t
245 that.to_raw();
246 that.to_raw();
246 that.control_key_active = false;
247 that.control_key_active = false;
247 return false;
248 return false;
248 } else if (event.which === 49 && that.control_key_active) {
249 } else if (event.which === 49 && that.control_key_active) {
249 // To Heading 1 = 1
250 // To Heading 1 = 1
250 that.to_heading(undefined, 1);
251 that.to_heading(undefined, 1);
251 that.control_key_active = false;
252 that.control_key_active = false;
252 return false;
253 return false;
253 } else if (event.which === 50 && that.control_key_active) {
254 } else if (event.which === 50 && that.control_key_active) {
254 // To Heading 2 = 2
255 // To Heading 2 = 2
255 that.to_heading(undefined, 2);
256 that.to_heading(undefined, 2);
256 that.control_key_active = false;
257 that.control_key_active = false;
257 return false;
258 return false;
258 } else if (event.which === 51 && that.control_key_active) {
259 } else if (event.which === 51 && that.control_key_active) {
259 // To Heading 3 = 3
260 // To Heading 3 = 3
260 that.to_heading(undefined, 3);
261 that.to_heading(undefined, 3);
261 that.control_key_active = false;
262 that.control_key_active = false;
262 return false;
263 return false;
263 } else if (event.which === 52 && that.control_key_active) {
264 } else if (event.which === 52 && that.control_key_active) {
264 // To Heading 4 = 4
265 // To Heading 4 = 4
265 that.to_heading(undefined, 4);
266 that.to_heading(undefined, 4);
266 that.control_key_active = false;
267 that.control_key_active = false;
267 return false;
268 return false;
268 } else if (event.which === 53 && that.control_key_active) {
269 } else if (event.which === 53 && that.control_key_active) {
269 // To Heading 5 = 5
270 // To Heading 5 = 5
270 that.to_heading(undefined, 5);
271 that.to_heading(undefined, 5);
271 that.control_key_active = false;
272 that.control_key_active = false;
272 return false;
273 return false;
273 } else if (event.which === 54 && that.control_key_active) {
274 } else if (event.which === 54 && that.control_key_active) {
274 // To Heading 6 = 6
275 // To Heading 6 = 6
275 that.to_heading(undefined, 6);
276 that.to_heading(undefined, 6);
276 that.control_key_active = false;
277 that.control_key_active = false;
277 return false;
278 return false;
278 } else if (event.which === 79 && that.control_key_active) {
279 } else if (event.which === 79 && that.control_key_active) {
279 // Toggle output = o
280 // Toggle output = o
280 if (event.shiftKey){
281 if (event.shiftKey){
281 that.toggle_output_scroll();
282 that.toggle_output_scroll();
282 } else {
283 } else {
283 that.toggle_output();
284 that.toggle_output();
284 }
285 }
285 that.control_key_active = false;
286 that.control_key_active = false;
286 return false;
287 return false;
287 } else if (event.which === 83 && that.control_key_active) {
288 } else if (event.which === 83 && that.control_key_active) {
288 // Save notebook = s
289 // Save notebook = s
289 that.save_checkpoint();
290 that.save_checkpoint();
290 that.control_key_active = false;
291 that.control_key_active = false;
291 return false;
292 return false;
292 } else if (event.which === 74 && that.control_key_active) {
293 } else if (event.which === 74 && that.control_key_active) {
293 // Move cell down = j
294 // Move cell down = j
294 that.move_cell_down();
295 that.move_cell_down();
295 that.control_key_active = false;
296 that.control_key_active = false;
296 return false;
297 return false;
297 } else if (event.which === 75 && that.control_key_active) {
298 } else if (event.which === 75 && that.control_key_active) {
298 // Move cell up = k
299 // Move cell up = k
299 that.move_cell_up();
300 that.move_cell_up();
300 that.control_key_active = false;
301 that.control_key_active = false;
301 return false;
302 return false;
302 } else if (event.which === 80 && that.control_key_active) {
303 } else if (event.which === 80 && that.control_key_active) {
303 // Select previous = p
304 // Select previous = p
304 that.select_prev();
305 that.select_prev();
305 that.control_key_active = false;
306 that.control_key_active = false;
306 return false;
307 return false;
307 } else if (event.which === 78 && that.control_key_active) {
308 } else if (event.which === 78 && that.control_key_active) {
308 // Select next = n
309 // Select next = n
309 that.select_next();
310 that.select_next();
310 that.control_key_active = false;
311 that.control_key_active = false;
311 return false;
312 return false;
312 } else if (event.which === 76 && that.control_key_active) {
313 } else if (event.which === 76 && that.control_key_active) {
313 // Toggle line numbers = l
314 // Toggle line numbers = l
314 that.cell_toggle_line_numbers();
315 that.cell_toggle_line_numbers();
315 that.control_key_active = false;
316 that.control_key_active = false;
316 return false;
317 return false;
317 } else if (event.which === 73 && that.control_key_active) {
318 } else if (event.which === 73 && that.control_key_active) {
318 // Interrupt kernel = i
319 // Interrupt kernel = i
319 that.session.interrupt_kernel();
320 that.session.interrupt_kernel();
320 that.control_key_active = false;
321 that.control_key_active = false;
321 return false;
322 return false;
322 } else if (event.which === 190 && that.control_key_active) {
323 } else if (event.which === 190 && that.control_key_active) {
323 // Restart kernel = . # matches qt console
324 // Restart kernel = . # matches qt console
324 that.restart_kernel();
325 that.restart_kernel();
325 that.control_key_active = false;
326 that.control_key_active = false;
326 return false;
327 return false;
327 } else if (event.which === 72 && that.control_key_active) {
328 } else if (event.which === 72 && that.control_key_active) {
328 // Show keyboard shortcuts = h
329 // Show keyboard shortcuts = h
329 IPython.quick_help.show_keyboard_shortcuts();
330 IPython.quick_help.show_keyboard_shortcuts();
330 that.control_key_active = false;
331 that.control_key_active = false;
331 return false;
332 return false;
332 } else if (event.which === 90 && that.control_key_active) {
333 } else if (event.which === 90 && that.control_key_active) {
333 // Undo last cell delete = z
334 // Undo last cell delete = z
334 that.undelete();
335 that.undelete();
335 that.control_key_active = false;
336 that.control_key_active = false;
336 return false;
337 return false;
337 } else if ((event.which === 189 || event.which === 173) &&
338 } else if ((event.which === 189 || event.which === 173) &&
338 that.control_key_active) {
339 that.control_key_active) {
339 // how fun! '-' is 189 in Chrome, but 173 in FF and Opera
340 // how fun! '-' is 189 in Chrome, but 173 in FF and Opera
340 // Split cell = -
341 // Split cell = -
341 that.split_cell();
342 that.split_cell();
342 that.control_key_active = false;
343 that.control_key_active = false;
343 return false;
344 return false;
344 } else if (that.control_key_active) {
345 } else if (that.control_key_active) {
345 that.control_key_active = false;
346 that.control_key_active = false;
346 return true;
347 return true;
347 }
348 }
348 return true;
349 return true;
349 });
350 });
350
351
351 var collapse_time = function(time){
352 var collapse_time = function(time){
352 var app_height = $('#ipython-main-app').height(); // content height
353 var app_height = $('#ipython-main-app').height(); // content height
353 var splitter_height = $('div#pager_splitter').outerHeight(true);
354 var splitter_height = $('div#pager_splitter').outerHeight(true);
354 var new_height = app_height - splitter_height;
355 var new_height = app_height - splitter_height;
355 that.element.animate({height : new_height + 'px'}, time);
356 that.element.animate({height : new_height + 'px'}, time);
356 }
357 }
357
358
358 this.element.bind('collapse_pager', function (event,extrap) {
359 this.element.bind('collapse_pager', function (event,extrap) {
359 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
360 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
360 collapse_time(time);
361 collapse_time(time);
361 });
362 });
362
363
363 var expand_time = function(time) {
364 var expand_time = function(time) {
364 var app_height = $('#ipython-main-app').height(); // content height
365 var app_height = $('#ipython-main-app').height(); // content height
365 var splitter_height = $('div#pager_splitter').outerHeight(true);
366 var splitter_height = $('div#pager_splitter').outerHeight(true);
366 var pager_height = $('div#pager').outerHeight(true);
367 var pager_height = $('div#pager').outerHeight(true);
367 var new_height = app_height - pager_height - splitter_height;
368 var new_height = app_height - pager_height - splitter_height;
368 that.element.animate({height : new_height + 'px'}, time);
369 that.element.animate({height : new_height + 'px'}, time);
369 }
370 }
370
371
371 this.element.bind('expand_pager', function (event, extrap) {
372 this.element.bind('expand_pager', function (event, extrap) {
372 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
373 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
373 expand_time(time);
374 expand_time(time);
374 });
375 });
375
376
376 // Firefox 22 broke $(window).on("beforeunload")
377 // Firefox 22 broke $(window).on("beforeunload")
377 // I'm not sure why or how.
378 // I'm not sure why or how.
378 window.onbeforeunload = function (e) {
379 window.onbeforeunload = function (e) {
379 // TODO: Make killing the kernel configurable.
380 // TODO: Make killing the kernel configurable.
380 var kill_kernel = false;
381 var kill_kernel = false;
381 if (kill_kernel) {
382 if (kill_kernel) {
382 that.session.kill_kernel();
383 that.session.kill_kernel();
383 }
384 }
384 // if we are autosaving, trigger an autosave on nav-away.
385 // if we are autosaving, trigger an autosave on nav-away.
385 // still warn, because if we don't the autosave may fail.
386 // still warn, because if we don't the autosave may fail.
386 if (that.dirty) {
387 if (that.dirty) {
387 if ( that.autosave_interval ) {
388 if ( that.autosave_interval ) {
388 // schedule autosave in a timeout
389 // schedule autosave in a timeout
389 // this gives you a chance to forcefully discard changes
390 // this gives you a chance to forcefully discard changes
390 // by reloading the page if you *really* want to.
391 // by reloading the page if you *really* want to.
391 // the timer doesn't start until you *dismiss* the dialog.
392 // the timer doesn't start until you *dismiss* the dialog.
392 setTimeout(function () {
393 setTimeout(function () {
393 if (that.dirty) {
394 if (that.dirty) {
394 that.save_notebook();
395 that.save_notebook();
395 }
396 }
396 }, 1000);
397 }, 1000);
397 return "Autosave in progress, latest changes may be lost.";
398 return "Autosave in progress, latest changes may be lost.";
398 } else {
399 } else {
399 return "Unsaved changes will be lost.";
400 return "Unsaved changes will be lost.";
400 }
401 }
401 };
402 };
402 // Null is the *only* return value that will make the browser not
403 // Null is the *only* return value that will make the browser not
403 // pop up the "don't leave" dialog.
404 // pop up the "don't leave" dialog.
404 return null;
405 return null;
405 };
406 };
406 };
407 };
407
408
408 /**
409 /**
409 * Set the dirty flag, and trigger the set_dirty.Notebook event
410 * Set the dirty flag, and trigger the set_dirty.Notebook event
410 *
411 *
411 * @method set_dirty
412 * @method set_dirty
412 */
413 */
413 Notebook.prototype.set_dirty = function (value) {
414 Notebook.prototype.set_dirty = function (value) {
414 if (value === undefined) {
415 if (value === undefined) {
415 value = true;
416 value = true;
416 }
417 }
417 if (this.dirty == value) {
418 if (this.dirty == value) {
418 return;
419 return;
419 }
420 }
420 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
421 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
421 };
422 };
422
423
423 /**
424 /**
424 * Scroll the top of the page to a given cell.
425 * Scroll the top of the page to a given cell.
425 *
426 *
426 * @method scroll_to_cell
427 * @method scroll_to_cell
427 * @param {Number} cell_number An index of the cell to view
428 * @param {Number} cell_number An index of the cell to view
428 * @param {Number} time Animation time in milliseconds
429 * @param {Number} time Animation time in milliseconds
429 * @return {Number} Pixel offset from the top of the container
430 * @return {Number} Pixel offset from the top of the container
430 */
431 */
431 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
432 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
432 var cells = this.get_cells();
433 var cells = this.get_cells();
433 var time = time || 0;
434 var time = time || 0;
434 cell_number = Math.min(cells.length-1,cell_number);
435 cell_number = Math.min(cells.length-1,cell_number);
435 cell_number = Math.max(0 ,cell_number);
436 cell_number = Math.max(0 ,cell_number);
436 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
437 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
437 this.element.animate({scrollTop:scroll_value}, time);
438 this.element.animate({scrollTop:scroll_value}, time);
438 return scroll_value;
439 return scroll_value;
439 };
440 };
440
441
441 /**
442 /**
442 * Scroll to the bottom of the page.
443 * Scroll to the bottom of the page.
443 *
444 *
444 * @method scroll_to_bottom
445 * @method scroll_to_bottom
445 */
446 */
446 Notebook.prototype.scroll_to_bottom = function () {
447 Notebook.prototype.scroll_to_bottom = function () {
447 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
448 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
448 };
449 };
449
450
450 /**
451 /**
451 * Scroll to the top of the page.
452 * Scroll to the top of the page.
452 *
453 *
453 * @method scroll_to_top
454 * @method scroll_to_top
454 */
455 */
455 Notebook.prototype.scroll_to_top = function () {
456 Notebook.prototype.scroll_to_top = function () {
456 this.element.animate({scrollTop:0}, 0);
457 this.element.animate({scrollTop:0}, 0);
457 };
458 };
458
459
459 // Edit Notebook metadata
460 // Edit Notebook metadata
460
461
461 Notebook.prototype.edit_metadata = function () {
462 Notebook.prototype.edit_metadata = function () {
462 var that = this;
463 var that = this;
463 IPython.dialog.edit_metadata(this.metadata, function (md) {
464 IPython.dialog.edit_metadata(this.metadata, function (md) {
464 that.metadata = md;
465 that.metadata = md;
465 }, 'Notebook');
466 }, 'Notebook');
466 };
467 };
467
468
468 // Cell indexing, retrieval, etc.
469 // Cell indexing, retrieval, etc.
469
470
470 /**
471 /**
471 * Get all cell elements in the notebook.
472 * Get all cell elements in the notebook.
472 *
473 *
473 * @method get_cell_elements
474 * @method get_cell_elements
474 * @return {jQuery} A selector of all cell elements
475 * @return {jQuery} A selector of all cell elements
475 */
476 */
476 Notebook.prototype.get_cell_elements = function () {
477 Notebook.prototype.get_cell_elements = function () {
477 return this.container.children("div.cell");
478 return this.container.children("div.cell");
478 };
479 };
479
480
480 /**
481 /**
481 * Get a particular cell element.
482 * Get a particular cell element.
482 *
483 *
483 * @method get_cell_element
484 * @method get_cell_element
484 * @param {Number} index An index of a cell to select
485 * @param {Number} index An index of a cell to select
485 * @return {jQuery} A selector of the given cell.
486 * @return {jQuery} A selector of the given cell.
486 */
487 */
487 Notebook.prototype.get_cell_element = function (index) {
488 Notebook.prototype.get_cell_element = function (index) {
488 var result = null;
489 var result = null;
489 var e = this.get_cell_elements().eq(index);
490 var e = this.get_cell_elements().eq(index);
490 if (e.length !== 0) {
491 if (e.length !== 0) {
491 result = e;
492 result = e;
492 }
493 }
493 return result;
494 return result;
494 };
495 };
495
496
496 /**
497 /**
497 * Count the cells in this notebook.
498 * Count the cells in this notebook.
498 *
499 *
499 * @method ncells
500 * @method ncells
500 * @return {Number} The number of cells in this notebook
501 * @return {Number} The number of cells in this notebook
501 */
502 */
502 Notebook.prototype.ncells = function () {
503 Notebook.prototype.ncells = function () {
503 return this.get_cell_elements().length;
504 return this.get_cell_elements().length;
504 };
505 };
505
506
506 /**
507 /**
507 * Get all Cell objects in this notebook.
508 * Get all Cell objects in this notebook.
508 *
509 *
509 * @method get_cells
510 * @method get_cells
510 * @return {Array} This notebook's Cell objects
511 * @return {Array} This notebook's Cell objects
511 */
512 */
512 // TODO: we are often calling cells as cells()[i], which we should optimize
513 // TODO: we are often calling cells as cells()[i], which we should optimize
513 // to cells(i) or a new method.
514 // to cells(i) or a new method.
514 Notebook.prototype.get_cells = function () {
515 Notebook.prototype.get_cells = function () {
515 return this.get_cell_elements().toArray().map(function (e) {
516 return this.get_cell_elements().toArray().map(function (e) {
516 return $(e).data("cell");
517 return $(e).data("cell");
517 });
518 });
518 };
519 };
519
520
520 /**
521 /**
521 * Get a Cell object from this notebook.
522 * Get a Cell object from this notebook.
522 *
523 *
523 * @method get_cell
524 * @method get_cell
524 * @param {Number} index An index of a cell to retrieve
525 * @param {Number} index An index of a cell to retrieve
525 * @return {Cell} A particular cell
526 * @return {Cell} A particular cell
526 */
527 */
527 Notebook.prototype.get_cell = function (index) {
528 Notebook.prototype.get_cell = function (index) {
528 var result = null;
529 var result = null;
529 var ce = this.get_cell_element(index);
530 var ce = this.get_cell_element(index);
530 if (ce !== null) {
531 if (ce !== null) {
531 result = ce.data('cell');
532 result = ce.data('cell');
532 }
533 }
533 return result;
534 return result;
534 }
535 }
535
536
536 /**
537 /**
537 * Get the cell below a given cell.
538 * Get the cell below a given cell.
538 *
539 *
539 * @method get_next_cell
540 * @method get_next_cell
540 * @param {Cell} cell The provided cell
541 * @param {Cell} cell The provided cell
541 * @return {Cell} The next cell
542 * @return {Cell} The next cell
542 */
543 */
543 Notebook.prototype.get_next_cell = function (cell) {
544 Notebook.prototype.get_next_cell = function (cell) {
544 var result = null;
545 var result = null;
545 var index = this.find_cell_index(cell);
546 var index = this.find_cell_index(cell);
546 if (this.is_valid_cell_index(index+1)) {
547 if (this.is_valid_cell_index(index+1)) {
547 result = this.get_cell(index+1);
548 result = this.get_cell(index+1);
548 }
549 }
549 return result;
550 return result;
550 }
551 }
551
552
552 /**
553 /**
553 * Get the cell above a given cell.
554 * Get the cell above a given cell.
554 *
555 *
555 * @method get_prev_cell
556 * @method get_prev_cell
556 * @param {Cell} cell The provided cell
557 * @param {Cell} cell The provided cell
557 * @return {Cell} The previous cell
558 * @return {Cell} The previous cell
558 */
559 */
559 Notebook.prototype.get_prev_cell = function (cell) {
560 Notebook.prototype.get_prev_cell = function (cell) {
560 // TODO: off-by-one
561 // TODO: off-by-one
561 // nb.get_prev_cell(nb.get_cell(1)) is null
562 // nb.get_prev_cell(nb.get_cell(1)) is null
562 var result = null;
563 var result = null;
563 var index = this.find_cell_index(cell);
564 var index = this.find_cell_index(cell);
564 if (index !== null && index > 1) {
565 if (index !== null && index > 1) {
565 result = this.get_cell(index-1);
566 result = this.get_cell(index-1);
566 }
567 }
567 return result;
568 return result;
568 }
569 }
569
570
570 /**
571 /**
571 * Get the numeric index of a given cell.
572 * Get the numeric index of a given cell.
572 *
573 *
573 * @method find_cell_index
574 * @method find_cell_index
574 * @param {Cell} cell The provided cell
575 * @param {Cell} cell The provided cell
575 * @return {Number} The cell's numeric index
576 * @return {Number} The cell's numeric index
576 */
577 */
577 Notebook.prototype.find_cell_index = function (cell) {
578 Notebook.prototype.find_cell_index = function (cell) {
578 var result = null;
579 var result = null;
579 this.get_cell_elements().filter(function (index) {
580 this.get_cell_elements().filter(function (index) {
580 if ($(this).data("cell") === cell) {
581 if ($(this).data("cell") === cell) {
581 result = index;
582 result = index;
582 };
583 };
583 });
584 });
584 return result;
585 return result;
585 };
586 };
586
587
587 /**
588 /**
588 * Get a given index , or the selected index if none is provided.
589 * Get a given index , or the selected index if none is provided.
589 *
590 *
590 * @method index_or_selected
591 * @method index_or_selected
591 * @param {Number} index A cell's index
592 * @param {Number} index A cell's index
592 * @return {Number} The given index, or selected index if none is provided.
593 * @return {Number} The given index, or selected index if none is provided.
593 */
594 */
594 Notebook.prototype.index_or_selected = function (index) {
595 Notebook.prototype.index_or_selected = function (index) {
595 var i;
596 var i;
596 if (index === undefined || index === null) {
597 if (index === undefined || index === null) {
597 i = this.get_selected_index();
598 i = this.get_selected_index();
598 if (i === null) {
599 if (i === null) {
599 i = 0;
600 i = 0;
600 }
601 }
601 } else {
602 } else {
602 i = index;
603 i = index;
603 }
604 }
604 return i;
605 return i;
605 };
606 };
606
607
607 /**
608 /**
608 * Get the currently selected cell.
609 * Get the currently selected cell.
609 * @method get_selected_cell
610 * @method get_selected_cell
610 * @return {Cell} The selected cell
611 * @return {Cell} The selected cell
611 */
612 */
612 Notebook.prototype.get_selected_cell = function () {
613 Notebook.prototype.get_selected_cell = function () {
613 var index = this.get_selected_index();
614 var index = this.get_selected_index();
614 return this.get_cell(index);
615 return this.get_cell(index);
615 };
616 };
616
617
617 /**
618 /**
618 * Check whether a cell index is valid.
619 * Check whether a cell index is valid.
619 *
620 *
620 * @method is_valid_cell_index
621 * @method is_valid_cell_index
621 * @param {Number} index A cell index
622 * @param {Number} index A cell index
622 * @return True if the index is valid, false otherwise
623 * @return True if the index is valid, false otherwise
623 */
624 */
624 Notebook.prototype.is_valid_cell_index = function (index) {
625 Notebook.prototype.is_valid_cell_index = function (index) {
625 if (index !== null && index >= 0 && index < this.ncells()) {
626 if (index !== null && index >= 0 && index < this.ncells()) {
626 return true;
627 return true;
627 } else {
628 } else {
628 return false;
629 return false;
629 };
630 };
630 }
631 }
631
632
632 /**
633 /**
633 * Get the index of the currently selected cell.
634 * Get the index of the currently selected cell.
634
635
635 * @method get_selected_index
636 * @method get_selected_index
636 * @return {Number} The selected cell's numeric index
637 * @return {Number} The selected cell's numeric index
637 */
638 */
638 Notebook.prototype.get_selected_index = function () {
639 Notebook.prototype.get_selected_index = function () {
639 var result = null;
640 var result = null;
640 this.get_cell_elements().filter(function (index) {
641 this.get_cell_elements().filter(function (index) {
641 if ($(this).data("cell").selected === true) {
642 if ($(this).data("cell").selected === true) {
642 result = index;
643 result = index;
643 };
644 };
644 });
645 });
645 return result;
646 return result;
646 };
647 };
647
648
648
649
649 // Cell selection.
650 // Cell selection.
650
651
651 /**
652 /**
652 * Programmatically select a cell.
653 * Programmatically select a cell.
653 *
654 *
654 * @method select
655 * @method select
655 * @param {Number} index A cell's index
656 * @param {Number} index A cell's index
656 * @return {Notebook} This notebook
657 * @return {Notebook} This notebook
657 */
658 */
658 Notebook.prototype.select = function (index) {
659 Notebook.prototype.select = function (index) {
659 if (this.is_valid_cell_index(index)) {
660 if (this.is_valid_cell_index(index)) {
660 var sindex = this.get_selected_index()
661 var sindex = this.get_selected_index()
661 if (sindex !== null && index !== sindex) {
662 if (sindex !== null && index !== sindex) {
662 this.get_cell(sindex).unselect();
663 this.get_cell(sindex).unselect();
663 };
664 };
664 var cell = this.get_cell(index);
665 var cell = this.get_cell(index);
665 cell.select();
666 cell.select();
666 if (cell.cell_type === 'heading') {
667 if (cell.cell_type === 'heading') {
667 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
668 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
668 {'cell_type':cell.cell_type,level:cell.level}
669 {'cell_type':cell.cell_type,level:cell.level}
669 );
670 );
670 } else {
671 } else {
671 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
672 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
672 {'cell_type':cell.cell_type}
673 {'cell_type':cell.cell_type}
673 );
674 );
674 };
675 };
675 };
676 };
676 return this;
677 return this;
677 };
678 };
678
679
679 /**
680 /**
680 * Programmatically select the next cell.
681 * Programmatically select the next cell.
681 *
682 *
682 * @method select_next
683 * @method select_next
683 * @return {Notebook} This notebook
684 * @return {Notebook} This notebook
684 */
685 */
685 Notebook.prototype.select_next = function () {
686 Notebook.prototype.select_next = function () {
686 var index = this.get_selected_index();
687 var index = this.get_selected_index();
687 this.select(index+1);
688 this.select(index+1);
688 return this;
689 return this;
689 };
690 };
690
691
691 /**
692 /**
692 * Programmatically select the previous cell.
693 * Programmatically select the previous cell.
693 *
694 *
694 * @method select_prev
695 * @method select_prev
695 * @return {Notebook} This notebook
696 * @return {Notebook} This notebook
696 */
697 */
697 Notebook.prototype.select_prev = function () {
698 Notebook.prototype.select_prev = function () {
698 var index = this.get_selected_index();
699 var index = this.get_selected_index();
699 this.select(index-1);
700 this.select(index-1);
700 return this;
701 return this;
701 };
702 };
702
703
703
704
704 // Cell movement
705 // Cell movement
705
706
706 /**
707 /**
707 * Move given (or selected) cell up and select it.
708 * Move given (or selected) cell up and select it.
708 *
709 *
709 * @method move_cell_up
710 * @method move_cell_up
710 * @param [index] {integer} cell index
711 * @param [index] {integer} cell index
711 * @return {Notebook} This notebook
712 * @return {Notebook} This notebook
712 **/
713 **/
713 Notebook.prototype.move_cell_up = function (index) {
714 Notebook.prototype.move_cell_up = function (index) {
714 var i = this.index_or_selected(index);
715 var i = this.index_or_selected(index);
715 if (this.is_valid_cell_index(i) && i > 0) {
716 if (this.is_valid_cell_index(i) && i > 0) {
716 var pivot = this.get_cell_element(i-1);
717 var pivot = this.get_cell_element(i-1);
717 var tomove = this.get_cell_element(i);
718 var tomove = this.get_cell_element(i);
718 if (pivot !== null && tomove !== null) {
719 if (pivot !== null && tomove !== null) {
719 tomove.detach();
720 tomove.detach();
720 pivot.before(tomove);
721 pivot.before(tomove);
721 this.select(i-1);
722 this.select(i-1);
722 };
723 };
723 this.set_dirty(true);
724 this.set_dirty(true);
724 };
725 };
725 return this;
726 return this;
726 };
727 };
727
728
728
729
729 /**
730 /**
730 * Move given (or selected) cell down and select it
731 * Move given (or selected) cell down and select it
731 *
732 *
732 * @method move_cell_down
733 * @method move_cell_down
733 * @param [index] {integer} cell index
734 * @param [index] {integer} cell index
734 * @return {Notebook} This notebook
735 * @return {Notebook} This notebook
735 **/
736 **/
736 Notebook.prototype.move_cell_down = function (index) {
737 Notebook.prototype.move_cell_down = function (index) {
737 var i = this.index_or_selected(index);
738 var i = this.index_or_selected(index);
738 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
739 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
739 var pivot = this.get_cell_element(i+1);
740 var pivot = this.get_cell_element(i+1);
740 var tomove = this.get_cell_element(i);
741 var tomove = this.get_cell_element(i);
741 if (pivot !== null && tomove !== null) {
742 if (pivot !== null && tomove !== null) {
742 tomove.detach();
743 tomove.detach();
743 pivot.after(tomove);
744 pivot.after(tomove);
744 this.select(i+1);
745 this.select(i+1);
745 };
746 };
746 };
747 };
747 this.set_dirty();
748 this.set_dirty();
748 return this;
749 return this;
749 };
750 };
750
751
751
752
752 // Insertion, deletion.
753 // Insertion, deletion.
753
754
754 /**
755 /**
755 * Delete a cell from the notebook.
756 * Delete a cell from the notebook.
756 *
757 *
757 * @method delete_cell
758 * @method delete_cell
758 * @param [index] A cell's numeric index
759 * @param [index] A cell's numeric index
759 * @return {Notebook} This notebook
760 * @return {Notebook} This notebook
760 */
761 */
761 Notebook.prototype.delete_cell = function (index) {
762 Notebook.prototype.delete_cell = function (index) {
762 var i = this.index_or_selected(index);
763 var i = this.index_or_selected(index);
763 var cell = this.get_selected_cell();
764 var cell = this.get_selected_cell();
764 this.undelete_backup = cell.toJSON();
765 this.undelete_backup = cell.toJSON();
765 $('#undelete_cell').removeClass('ui-state-disabled');
766 $('#undelete_cell').removeClass('ui-state-disabled');
766 if (this.is_valid_cell_index(i)) {
767 if (this.is_valid_cell_index(i)) {
767 var ce = this.get_cell_element(i);
768 var ce = this.get_cell_element(i);
768 ce.remove();
769 ce.remove();
769 if (i === (this.ncells())) {
770 if (i === (this.ncells())) {
770 this.select(i-1);
771 this.select(i-1);
771 this.undelete_index = i - 1;
772 this.undelete_index = i - 1;
772 this.undelete_below = true;
773 this.undelete_below = true;
773 } else {
774 } else {
774 this.select(i);
775 this.select(i);
775 this.undelete_index = i;
776 this.undelete_index = i;
776 this.undelete_below = false;
777 this.undelete_below = false;
777 };
778 };
778 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
779 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
779 this.set_dirty(true);
780 this.set_dirty(true);
780 };
781 };
781 return this;
782 return this;
782 };
783 };
783
784
784 /**
785 /**
785 * Insert a cell so that after insertion the cell is at given index.
786 * Insert a cell so that after insertion the cell is at given index.
786 *
787 *
787 * Similar to insert_above, but index parameter is mandatory
788 * Similar to insert_above, but index parameter is mandatory
788 *
789 *
789 * Index will be brought back into the accissible range [0,n]
790 * Index will be brought back into the accissible range [0,n]
790 *
791 *
791 * @method insert_cell_at_index
792 * @method insert_cell_at_index
792 * @param type {string} in ['code','markdown','heading']
793 * @param type {string} in ['code','markdown','heading']
793 * @param [index] {int} a valid index where to inser cell
794 * @param [index] {int} a valid index where to inser cell
794 *
795 *
795 * @return cell {cell|null} created cell or null
796 * @return cell {cell|null} created cell or null
796 **/
797 **/
797 Notebook.prototype.insert_cell_at_index = function(type, index){
798 Notebook.prototype.insert_cell_at_index = function(type, index){
798
799
799 var ncells = this.ncells();
800 var ncells = this.ncells();
800 var index = Math.min(index,ncells);
801 var index = Math.min(index,ncells);
801 index = Math.max(index,0);
802 index = Math.max(index,0);
802 var cell = null;
803 var cell = null;
803
804
804 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
805 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
805 if (type === 'code') {
806 if (type === 'code') {
806 cell = new IPython.CodeCell(this.session);
807 cell = new IPython.CodeCell(this.session);
807 cell.set_input_prompt();
808 cell.set_input_prompt();
808 } else if (type === 'markdown') {
809 } else if (type === 'markdown') {
809 cell = new IPython.MarkdownCell();
810 cell = new IPython.MarkdownCell();
810 } else if (type === 'raw') {
811 } else if (type === 'raw') {
811 cell = new IPython.RawCell();
812 cell = new IPython.RawCell();
812 } else if (type === 'heading') {
813 } else if (type === 'heading') {
813 cell = new IPython.HeadingCell();
814 cell = new IPython.HeadingCell();
814 }
815 }
815
816
816 if(this._insert_element_at_index(cell.element,index)){
817 if(this._insert_element_at_index(cell.element,index)){
817 cell.render();
818 cell.render();
818 this.select(this.find_cell_index(cell));
819 this.select(this.find_cell_index(cell));
819 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
820 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
820 this.set_dirty(true);
821 this.set_dirty(true);
821 }
822 }
822 }
823 }
823 return cell;
824 return cell;
824
825
825 };
826 };
826
827
827 /**
828 /**
828 * Insert an element at given cell index.
829 * Insert an element at given cell index.
829 *
830 *
830 * @method _insert_element_at_index
831 * @method _insert_element_at_index
831 * @param element {dom element} a cell element
832 * @param element {dom element} a cell element
832 * @param [index] {int} a valid index where to inser cell
833 * @param [index] {int} a valid index where to inser cell
833 * @private
834 * @private
834 *
835 *
835 * return true if everything whent fine.
836 * return true if everything whent fine.
836 **/
837 **/
837 Notebook.prototype._insert_element_at_index = function(element, index){
838 Notebook.prototype._insert_element_at_index = function(element, index){
838 if (element === undefined){
839 if (element === undefined){
839 return false;
840 return false;
840 }
841 }
841
842
842 var ncells = this.ncells();
843 var ncells = this.ncells();
843
844
844 if (ncells === 0) {
845 if (ncells === 0) {
845 // special case append if empty
846 // special case append if empty
846 this.element.find('div.end_space').before(element);
847 this.element.find('div.end_space').before(element);
847 } else if ( ncells === index ) {
848 } else if ( ncells === index ) {
848 // special case append it the end, but not empty
849 // special case append it the end, but not empty
849 this.get_cell_element(index-1).after(element);
850 this.get_cell_element(index-1).after(element);
850 } else if (this.is_valid_cell_index(index)) {
851 } else if (this.is_valid_cell_index(index)) {
851 // otherwise always somewhere to append to
852 // otherwise always somewhere to append to
852 this.get_cell_element(index).before(element);
853 this.get_cell_element(index).before(element);
853 } else {
854 } else {
854 return false;
855 return false;
855 }
856 }
856
857
857 if (this.undelete_index !== null && index <= this.undelete_index) {
858 if (this.undelete_index !== null && index <= this.undelete_index) {
858 this.undelete_index = this.undelete_index + 1;
859 this.undelete_index = this.undelete_index + 1;
859 this.set_dirty(true);
860 this.set_dirty(true);
860 }
861 }
861 return true;
862 return true;
862 };
863 };
863
864
864 /**
865 /**
865 * Insert a cell of given type above given index, or at top
866 * Insert a cell of given type above given index, or at top
866 * of notebook if index smaller than 0.
867 * of notebook if index smaller than 0.
867 *
868 *
868 * default index value is the one of currently selected cell
869 * default index value is the one of currently selected cell
869 *
870 *
870 * @method insert_cell_above
871 * @method insert_cell_above
871 * @param type {string} cell type
872 * @param type {string} cell type
872 * @param [index] {integer}
873 * @param [index] {integer}
873 *
874 *
874 * @return handle to created cell or null
875 * @return handle to created cell or null
875 **/
876 **/
876 Notebook.prototype.insert_cell_above = function (type, index) {
877 Notebook.prototype.insert_cell_above = function (type, index) {
877 index = this.index_or_selected(index);
878 index = this.index_or_selected(index);
878 return this.insert_cell_at_index(type, index);
879 return this.insert_cell_at_index(type, index);
879 };
880 };
880
881
881 /**
882 /**
882 * Insert a cell of given type below given index, or at bottom
883 * Insert a cell of given type below given index, or at bottom
883 * of notebook if index greater thatn number of cell
884 * of notebook if index greater thatn number of cell
884 *
885 *
885 * default index value is the one of currently selected cell
886 * default index value is the one of currently selected cell
886 *
887 *
887 * @method insert_cell_below
888 * @method insert_cell_below
888 * @param type {string} cell type
889 * @param type {string} cell type
889 * @param [index] {integer}
890 * @param [index] {integer}
890 *
891 *
891 * @return handle to created cell or null
892 * @return handle to created cell or null
892 *
893 *
893 **/
894 **/
894 Notebook.prototype.insert_cell_below = function (type, index) {
895 Notebook.prototype.insert_cell_below = function (type, index) {
895 index = this.index_or_selected(index);
896 index = this.index_or_selected(index);
896 return this.insert_cell_at_index(type, index+1);
897 return this.insert_cell_at_index(type, index+1);
897 };
898 };
898
899
899
900
900 /**
901 /**
901 * Insert cell at end of notebook
902 * Insert cell at end of notebook
902 *
903 *
903 * @method insert_cell_at_bottom
904 * @method insert_cell_at_bottom
904 * @param {String} type cell type
905 * @param {String} type cell type
905 *
906 *
906 * @return the added cell; or null
907 * @return the added cell; or null
907 **/
908 **/
908 Notebook.prototype.insert_cell_at_bottom = function (type){
909 Notebook.prototype.insert_cell_at_bottom = function (type){
909 var len = this.ncells();
910 var len = this.ncells();
910 return this.insert_cell_below(type,len-1);
911 return this.insert_cell_below(type,len-1);
911 };
912 };
912
913
913 /**
914 /**
914 * Turn a cell into a code cell.
915 * Turn a cell into a code cell.
915 *
916 *
916 * @method to_code
917 * @method to_code
917 * @param {Number} [index] A cell's index
918 * @param {Number} [index] A cell's index
918 */
919 */
919 Notebook.prototype.to_code = function (index) {
920 Notebook.prototype.to_code = function (index) {
920 var i = this.index_or_selected(index);
921 var i = this.index_or_selected(index);
921 if (this.is_valid_cell_index(i)) {
922 if (this.is_valid_cell_index(i)) {
922 var source_element = this.get_cell_element(i);
923 var source_element = this.get_cell_element(i);
923 var source_cell = source_element.data("cell");
924 var source_cell = source_element.data("cell");
924 if (!(source_cell instanceof IPython.CodeCell)) {
925 if (!(source_cell instanceof IPython.CodeCell)) {
925 var target_cell = this.insert_cell_below('code',i);
926 var target_cell = this.insert_cell_below('code',i);
926 var text = source_cell.get_text();
927 var text = source_cell.get_text();
927 if (text === source_cell.placeholder) {
928 if (text === source_cell.placeholder) {
928 text = '';
929 text = '';
929 }
930 }
930 target_cell.set_text(text);
931 target_cell.set_text(text);
931 // make this value the starting point, so that we can only undo
932 // make this value the starting point, so that we can only undo
932 // to this state, instead of a blank cell
933 // to this state, instead of a blank cell
933 target_cell.code_mirror.clearHistory();
934 target_cell.code_mirror.clearHistory();
934 source_element.remove();
935 source_element.remove();
935 this.set_dirty(true);
936 this.set_dirty(true);
936 };
937 };
937 };
938 };
938 };
939 };
939
940
940 /**
941 /**
941 * Turn a cell into a Markdown cell.
942 * Turn a cell into a Markdown cell.
942 *
943 *
943 * @method to_markdown
944 * @method to_markdown
944 * @param {Number} [index] A cell's index
945 * @param {Number} [index] A cell's index
945 */
946 */
946 Notebook.prototype.to_markdown = function (index) {
947 Notebook.prototype.to_markdown = function (index) {
947 var i = this.index_or_selected(index);
948 var i = this.index_or_selected(index);
948 if (this.is_valid_cell_index(i)) {
949 if (this.is_valid_cell_index(i)) {
949 var source_element = this.get_cell_element(i);
950 var source_element = this.get_cell_element(i);
950 var source_cell = source_element.data("cell");
951 var source_cell = source_element.data("cell");
951 if (!(source_cell instanceof IPython.MarkdownCell)) {
952 if (!(source_cell instanceof IPython.MarkdownCell)) {
952 var target_cell = this.insert_cell_below('markdown',i);
953 var target_cell = this.insert_cell_below('markdown',i);
953 var text = source_cell.get_text();
954 var text = source_cell.get_text();
954 if (text === source_cell.placeholder) {
955 if (text === source_cell.placeholder) {
955 text = '';
956 text = '';
956 };
957 };
957 // The edit must come before the set_text.
958 // The edit must come before the set_text.
958 target_cell.edit();
959 target_cell.edit();
959 target_cell.set_text(text);
960 target_cell.set_text(text);
960 // make this value the starting point, so that we can only undo
961 // make this value the starting point, so that we can only undo
961 // to this state, instead of a blank cell
962 // to this state, instead of a blank cell
962 target_cell.code_mirror.clearHistory();
963 target_cell.code_mirror.clearHistory();
963 source_element.remove();
964 source_element.remove();
964 this.set_dirty(true);
965 this.set_dirty(true);
965 };
966 };
966 };
967 };
967 };
968 };
968
969
969 /**
970 /**
970 * Turn a cell into a raw text cell.
971 * Turn a cell into a raw text cell.
971 *
972 *
972 * @method to_raw
973 * @method to_raw
973 * @param {Number} [index] A cell's index
974 * @param {Number} [index] A cell's index
974 */
975 */
975 Notebook.prototype.to_raw = function (index) {
976 Notebook.prototype.to_raw = function (index) {
976 var i = this.index_or_selected(index);
977 var i = this.index_or_selected(index);
977 if (this.is_valid_cell_index(i)) {
978 if (this.is_valid_cell_index(i)) {
978 var source_element = this.get_cell_element(i);
979 var source_element = this.get_cell_element(i);
979 var source_cell = source_element.data("cell");
980 var source_cell = source_element.data("cell");
980 var target_cell = null;
981 var target_cell = null;
981 if (!(source_cell instanceof IPython.RawCell)) {
982 if (!(source_cell instanceof IPython.RawCell)) {
982 target_cell = this.insert_cell_below('raw',i);
983 target_cell = this.insert_cell_below('raw',i);
983 var text = source_cell.get_text();
984 var text = source_cell.get_text();
984 if (text === source_cell.placeholder) {
985 if (text === source_cell.placeholder) {
985 text = '';
986 text = '';
986 };
987 };
987 // The edit must come before the set_text.
988 // The edit must come before the set_text.
988 target_cell.edit();
989 target_cell.edit();
989 target_cell.set_text(text);
990 target_cell.set_text(text);
990 // make this value the starting point, so that we can only undo
991 // make this value the starting point, so that we can only undo
991 // to this state, instead of a blank cell
992 // to this state, instead of a blank cell
992 target_cell.code_mirror.clearHistory();
993 target_cell.code_mirror.clearHistory();
993 source_element.remove();
994 source_element.remove();
994 this.set_dirty(true);
995 this.set_dirty(true);
995 };
996 };
996 };
997 };
997 };
998 };
998
999
999 /**
1000 /**
1000 * Turn a cell into a heading cell.
1001 * Turn a cell into a heading cell.
1001 *
1002 *
1002 * @method to_heading
1003 * @method to_heading
1003 * @param {Number} [index] A cell's index
1004 * @param {Number} [index] A cell's index
1004 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1005 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1005 */
1006 */
1006 Notebook.prototype.to_heading = function (index, level) {
1007 Notebook.prototype.to_heading = function (index, level) {
1007 level = level || 1;
1008 level = level || 1;
1008 var i = this.index_or_selected(index);
1009 var i = this.index_or_selected(index);
1009 if (this.is_valid_cell_index(i)) {
1010 if (this.is_valid_cell_index(i)) {
1010 var source_element = this.get_cell_element(i);
1011 var source_element = this.get_cell_element(i);
1011 var source_cell = source_element.data("cell");
1012 var source_cell = source_element.data("cell");
1012 var target_cell = null;
1013 var target_cell = null;
1013 if (source_cell instanceof IPython.HeadingCell) {
1014 if (source_cell instanceof IPython.HeadingCell) {
1014 source_cell.set_level(level);
1015 source_cell.set_level(level);
1015 } else {
1016 } else {
1016 target_cell = this.insert_cell_below('heading',i);
1017 target_cell = this.insert_cell_below('heading',i);
1017 var text = source_cell.get_text();
1018 var text = source_cell.get_text();
1018 if (text === source_cell.placeholder) {
1019 if (text === source_cell.placeholder) {
1019 text = '';
1020 text = '';
1020 };
1021 };
1021 // The edit must come before the set_text.
1022 // The edit must come before the set_text.
1022 target_cell.set_level(level);
1023 target_cell.set_level(level);
1023 target_cell.edit();
1024 target_cell.edit();
1024 target_cell.set_text(text);
1025 target_cell.set_text(text);
1025 // make this value the starting point, so that we can only undo
1026 // make this value the starting point, so that we can only undo
1026 // to this state, instead of a blank cell
1027 // to this state, instead of a blank cell
1027 target_cell.code_mirror.clearHistory();
1028 target_cell.code_mirror.clearHistory();
1028 source_element.remove();
1029 source_element.remove();
1029 this.set_dirty(true);
1030 this.set_dirty(true);
1030 };
1031 };
1031 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1032 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1032 {'cell_type':'heading',level:level}
1033 {'cell_type':'heading',level:level}
1033 );
1034 );
1034 };
1035 };
1035 };
1036 };
1036
1037
1037
1038
1038 // Cut/Copy/Paste
1039 // Cut/Copy/Paste
1039
1040
1040 /**
1041 /**
1041 * Enable UI elements for pasting cells.
1042 * Enable UI elements for pasting cells.
1042 *
1043 *
1043 * @method enable_paste
1044 * @method enable_paste
1044 */
1045 */
1045 Notebook.prototype.enable_paste = function () {
1046 Notebook.prototype.enable_paste = function () {
1046 var that = this;
1047 var that = this;
1047 if (!this.paste_enabled) {
1048 if (!this.paste_enabled) {
1048 $('#paste_cell_replace').removeClass('ui-state-disabled')
1049 $('#paste_cell_replace').removeClass('ui-state-disabled')
1049 .on('click', function () {that.paste_cell_replace();});
1050 .on('click', function () {that.paste_cell_replace();});
1050 $('#paste_cell_above').removeClass('ui-state-disabled')
1051 $('#paste_cell_above').removeClass('ui-state-disabled')
1051 .on('click', function () {that.paste_cell_above();});
1052 .on('click', function () {that.paste_cell_above();});
1052 $('#paste_cell_below').removeClass('ui-state-disabled')
1053 $('#paste_cell_below').removeClass('ui-state-disabled')
1053 .on('click', function () {that.paste_cell_below();});
1054 .on('click', function () {that.paste_cell_below();});
1054 this.paste_enabled = true;
1055 this.paste_enabled = true;
1055 };
1056 };
1056 };
1057 };
1057
1058
1058 /**
1059 /**
1059 * Disable UI elements for pasting cells.
1060 * Disable UI elements for pasting cells.
1060 *
1061 *
1061 * @method disable_paste
1062 * @method disable_paste
1062 */
1063 */
1063 Notebook.prototype.disable_paste = function () {
1064 Notebook.prototype.disable_paste = function () {
1064 if (this.paste_enabled) {
1065 if (this.paste_enabled) {
1065 $('#paste_cell_replace').addClass('ui-state-disabled').off('click');
1066 $('#paste_cell_replace').addClass('ui-state-disabled').off('click');
1066 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
1067 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
1067 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
1068 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
1068 this.paste_enabled = false;
1069 this.paste_enabled = false;
1069 };
1070 };
1070 };
1071 };
1071
1072
1072 /**
1073 /**
1073 * Cut a cell.
1074 * Cut a cell.
1074 *
1075 *
1075 * @method cut_cell
1076 * @method cut_cell
1076 */
1077 */
1077 Notebook.prototype.cut_cell = function () {
1078 Notebook.prototype.cut_cell = function () {
1078 this.copy_cell();
1079 this.copy_cell();
1079 this.delete_cell();
1080 this.delete_cell();
1080 }
1081 }
1081
1082
1082 /**
1083 /**
1083 * Copy a cell.
1084 * Copy a cell.
1084 *
1085 *
1085 * @method copy_cell
1086 * @method copy_cell
1086 */
1087 */
1087 Notebook.prototype.copy_cell = function () {
1088 Notebook.prototype.copy_cell = function () {
1088 var cell = this.get_selected_cell();
1089 var cell = this.get_selected_cell();
1089 this.clipboard = cell.toJSON();
1090 this.clipboard = cell.toJSON();
1090 this.enable_paste();
1091 this.enable_paste();
1091 };
1092 };
1092
1093
1093 /**
1094 /**
1094 * Replace the selected cell with a cell in the clipboard.
1095 * Replace the selected cell with a cell in the clipboard.
1095 *
1096 *
1096 * @method paste_cell_replace
1097 * @method paste_cell_replace
1097 */
1098 */
1098 Notebook.prototype.paste_cell_replace = function () {
1099 Notebook.prototype.paste_cell_replace = function () {
1099 if (this.clipboard !== null && this.paste_enabled) {
1100 if (this.clipboard !== null && this.paste_enabled) {
1100 var cell_data = this.clipboard;
1101 var cell_data = this.clipboard;
1101 var new_cell = this.insert_cell_above(cell_data.cell_type);
1102 var new_cell = this.insert_cell_above(cell_data.cell_type);
1102 new_cell.fromJSON(cell_data);
1103 new_cell.fromJSON(cell_data);
1103 var old_cell = this.get_next_cell(new_cell);
1104 var old_cell = this.get_next_cell(new_cell);
1104 this.delete_cell(this.find_cell_index(old_cell));
1105 this.delete_cell(this.find_cell_index(old_cell));
1105 this.select(this.find_cell_index(new_cell));
1106 this.select(this.find_cell_index(new_cell));
1106 };
1107 };
1107 };
1108 };
1108
1109
1109 /**
1110 /**
1110 * Paste a cell from the clipboard above the selected cell.
1111 * Paste a cell from the clipboard above the selected cell.
1111 *
1112 *
1112 * @method paste_cell_above
1113 * @method paste_cell_above
1113 */
1114 */
1114 Notebook.prototype.paste_cell_above = function () {
1115 Notebook.prototype.paste_cell_above = function () {
1115 if (this.clipboard !== null && this.paste_enabled) {
1116 if (this.clipboard !== null && this.paste_enabled) {
1116 var cell_data = this.clipboard;
1117 var cell_data = this.clipboard;
1117 var new_cell = this.insert_cell_above(cell_data.cell_type);
1118 var new_cell = this.insert_cell_above(cell_data.cell_type);
1118 new_cell.fromJSON(cell_data);
1119 new_cell.fromJSON(cell_data);
1119 };
1120 };
1120 };
1121 };
1121
1122
1122 /**
1123 /**
1123 * Paste a cell from the clipboard below the selected cell.
1124 * Paste a cell from the clipboard below the selected cell.
1124 *
1125 *
1125 * @method paste_cell_below
1126 * @method paste_cell_below
1126 */
1127 */
1127 Notebook.prototype.paste_cell_below = function () {
1128 Notebook.prototype.paste_cell_below = function () {
1128 if (this.clipboard !== null && this.paste_enabled) {
1129 if (this.clipboard !== null && this.paste_enabled) {
1129 var cell_data = this.clipboard;
1130 var cell_data = this.clipboard;
1130 var new_cell = this.insert_cell_below(cell_data.cell_type);
1131 var new_cell = this.insert_cell_below(cell_data.cell_type);
1131 new_cell.fromJSON(cell_data);
1132 new_cell.fromJSON(cell_data);
1132 };
1133 };
1133 };
1134 };
1134
1135
1135 // Cell undelete
1136 // Cell undelete
1136
1137
1137 /**
1138 /**
1138 * Restore the most recently deleted cell.
1139 * Restore the most recently deleted cell.
1139 *
1140 *
1140 * @method undelete
1141 * @method undelete
1141 */
1142 */
1142 Notebook.prototype.undelete = function() {
1143 Notebook.prototype.undelete = function() {
1143 if (this.undelete_backup !== null && this.undelete_index !== null) {
1144 if (this.undelete_backup !== null && this.undelete_index !== null) {
1144 var current_index = this.get_selected_index();
1145 var current_index = this.get_selected_index();
1145 if (this.undelete_index < current_index) {
1146 if (this.undelete_index < current_index) {
1146 current_index = current_index + 1;
1147 current_index = current_index + 1;
1147 }
1148 }
1148 if (this.undelete_index >= this.ncells()) {
1149 if (this.undelete_index >= this.ncells()) {
1149 this.select(this.ncells() - 1);
1150 this.select(this.ncells() - 1);
1150 }
1151 }
1151 else {
1152 else {
1152 this.select(this.undelete_index);
1153 this.select(this.undelete_index);
1153 }
1154 }
1154 var cell_data = this.undelete_backup;
1155 var cell_data = this.undelete_backup;
1155 var new_cell = null;
1156 var new_cell = null;
1156 if (this.undelete_below) {
1157 if (this.undelete_below) {
1157 new_cell = this.insert_cell_below(cell_data.cell_type);
1158 new_cell = this.insert_cell_below(cell_data.cell_type);
1158 } else {
1159 } else {
1159 new_cell = this.insert_cell_above(cell_data.cell_type);
1160 new_cell = this.insert_cell_above(cell_data.cell_type);
1160 }
1161 }
1161 new_cell.fromJSON(cell_data);
1162 new_cell.fromJSON(cell_data);
1162 this.select(current_index);
1163 this.select(current_index);
1163 this.undelete_backup = null;
1164 this.undelete_backup = null;
1164 this.undelete_index = null;
1165 this.undelete_index = null;
1165 }
1166 }
1166 $('#undelete_cell').addClass('ui-state-disabled');
1167 $('#undelete_cell').addClass('ui-state-disabled');
1167 }
1168 }
1168
1169
1169 // Split/merge
1170 // Split/merge
1170
1171
1171 /**
1172 /**
1172 * Split the selected cell into two, at the cursor.
1173 * Split the selected cell into two, at the cursor.
1173 *
1174 *
1174 * @method split_cell
1175 * @method split_cell
1175 */
1176 */
1176 Notebook.prototype.split_cell = function () {
1177 Notebook.prototype.split_cell = function () {
1177 // Todo: implement spliting for other cell types.
1178 // Todo: implement spliting for other cell types.
1178 var cell = this.get_selected_cell();
1179 var cell = this.get_selected_cell();
1179 if (cell.is_splittable()) {
1180 if (cell.is_splittable()) {
1180 var texta = cell.get_pre_cursor();
1181 var texta = cell.get_pre_cursor();
1181 var textb = cell.get_post_cursor();
1182 var textb = cell.get_post_cursor();
1182 if (cell instanceof IPython.CodeCell) {
1183 if (cell instanceof IPython.CodeCell) {
1183 cell.set_text(texta);
1184 cell.set_text(texta);
1184 var new_cell = this.insert_cell_below('code');
1185 var new_cell = this.insert_cell_below('code');
1185 new_cell.set_text(textb);
1186 new_cell.set_text(textb);
1186 } else if (cell instanceof IPython.MarkdownCell) {
1187 } else if (cell instanceof IPython.MarkdownCell) {
1187 cell.set_text(texta);
1188 cell.set_text(texta);
1188 cell.render();
1189 cell.render();
1189 var new_cell = this.insert_cell_below('markdown');
1190 var new_cell = this.insert_cell_below('markdown');
1190 new_cell.edit(); // editor must be visible to call set_text
1191 new_cell.edit(); // editor must be visible to call set_text
1191 new_cell.set_text(textb);
1192 new_cell.set_text(textb);
1192 new_cell.render();
1193 new_cell.render();
1193 }
1194 }
1194 };
1195 };
1195 };
1196 };
1196
1197
1197 /**
1198 /**
1198 * Combine the selected cell into the cell above it.
1199 * Combine the selected cell into the cell above it.
1199 *
1200 *
1200 * @method merge_cell_above
1201 * @method merge_cell_above
1201 */
1202 */
1202 Notebook.prototype.merge_cell_above = function () {
1203 Notebook.prototype.merge_cell_above = function () {
1203 var index = this.get_selected_index();
1204 var index = this.get_selected_index();
1204 var cell = this.get_cell(index);
1205 var cell = this.get_cell(index);
1205 if (!cell.is_mergeable()) {
1206 if (!cell.is_mergeable()) {
1206 return;
1207 return;
1207 }
1208 }
1208 if (index > 0) {
1209 if (index > 0) {
1209 var upper_cell = this.get_cell(index-1);
1210 var upper_cell = this.get_cell(index-1);
1210 if (!upper_cell.is_mergeable()) {
1211 if (!upper_cell.is_mergeable()) {
1211 return;
1212 return;
1212 }
1213 }
1213 var upper_text = upper_cell.get_text();
1214 var upper_text = upper_cell.get_text();
1214 var text = cell.get_text();
1215 var text = cell.get_text();
1215 if (cell instanceof IPython.CodeCell) {
1216 if (cell instanceof IPython.CodeCell) {
1216 cell.set_text(upper_text+'\n'+text);
1217 cell.set_text(upper_text+'\n'+text);
1217 } else if (cell instanceof IPython.MarkdownCell) {
1218 } else if (cell instanceof IPython.MarkdownCell) {
1218 cell.edit();
1219 cell.edit();
1219 cell.set_text(upper_text+'\n'+text);
1220 cell.set_text(upper_text+'\n'+text);
1220 cell.render();
1221 cell.render();
1221 };
1222 };
1222 this.delete_cell(index-1);
1223 this.delete_cell(index-1);
1223 this.select(this.find_cell_index(cell));
1224 this.select(this.find_cell_index(cell));
1224 };
1225 };
1225 };
1226 };
1226
1227
1227 /**
1228 /**
1228 * Combine the selected cell into the cell below it.
1229 * Combine the selected cell into the cell below it.
1229 *
1230 *
1230 * @method merge_cell_below
1231 * @method merge_cell_below
1231 */
1232 */
1232 Notebook.prototype.merge_cell_below = function () {
1233 Notebook.prototype.merge_cell_below = function () {
1233 var index = this.get_selected_index();
1234 var index = this.get_selected_index();
1234 var cell = this.get_cell(index);
1235 var cell = this.get_cell(index);
1235 if (!cell.is_mergeable()) {
1236 if (!cell.is_mergeable()) {
1236 return;
1237 return;
1237 }
1238 }
1238 if (index < this.ncells()-1) {
1239 if (index < this.ncells()-1) {
1239 var lower_cell = this.get_cell(index+1);
1240 var lower_cell = this.get_cell(index+1);
1240 if (!lower_cell.is_mergeable()) {
1241 if (!lower_cell.is_mergeable()) {
1241 return;
1242 return;
1242 }
1243 }
1243 var lower_text = lower_cell.get_text();
1244 var lower_text = lower_cell.get_text();
1244 var text = cell.get_text();
1245 var text = cell.get_text();
1245 if (cell instanceof IPython.CodeCell) {
1246 if (cell instanceof IPython.CodeCell) {
1246 cell.set_text(text+'\n'+lower_text);
1247 cell.set_text(text+'\n'+lower_text);
1247 } else if (cell instanceof IPython.MarkdownCell) {
1248 } else if (cell instanceof IPython.MarkdownCell) {
1248 cell.edit();
1249 cell.edit();
1249 cell.set_text(text+'\n'+lower_text);
1250 cell.set_text(text+'\n'+lower_text);
1250 cell.render();
1251 cell.render();
1251 };
1252 };
1252 this.delete_cell(index+1);
1253 this.delete_cell(index+1);
1253 this.select(this.find_cell_index(cell));
1254 this.select(this.find_cell_index(cell));
1254 };
1255 };
1255 };
1256 };
1256
1257
1257
1258
1258 // Cell collapsing and output clearing
1259 // Cell collapsing and output clearing
1259
1260
1260 /**
1261 /**
1261 * Hide a cell's output.
1262 * Hide a cell's output.
1262 *
1263 *
1263 * @method collapse
1264 * @method collapse
1264 * @param {Number} index A cell's numeric index
1265 * @param {Number} index A cell's numeric index
1265 */
1266 */
1266 Notebook.prototype.collapse = function (index) {
1267 Notebook.prototype.collapse = function (index) {
1267 var i = this.index_or_selected(index);
1268 var i = this.index_or_selected(index);
1268 this.get_cell(i).collapse();
1269 this.get_cell(i).collapse();
1269 this.set_dirty(true);
1270 this.set_dirty(true);
1270 };
1271 };
1271
1272
1272 /**
1273 /**
1273 * Show a cell's output.
1274 * Show a cell's output.
1274 *
1275 *
1275 * @method expand
1276 * @method expand
1276 * @param {Number} index A cell's numeric index
1277 * @param {Number} index A cell's numeric index
1277 */
1278 */
1278 Notebook.prototype.expand = function (index) {
1279 Notebook.prototype.expand = function (index) {
1279 var i = this.index_or_selected(index);
1280 var i = this.index_or_selected(index);
1280 this.get_cell(i).expand();
1281 this.get_cell(i).expand();
1281 this.set_dirty(true);
1282 this.set_dirty(true);
1282 };
1283 };
1283
1284
1284 /** Toggle whether a cell's output is collapsed or expanded.
1285 /** Toggle whether a cell's output is collapsed or expanded.
1285 *
1286 *
1286 * @method toggle_output
1287 * @method toggle_output
1287 * @param {Number} index A cell's numeric index
1288 * @param {Number} index A cell's numeric index
1288 */
1289 */
1289 Notebook.prototype.toggle_output = function (index) {
1290 Notebook.prototype.toggle_output = function (index) {
1290 var i = this.index_or_selected(index);
1291 var i = this.index_or_selected(index);
1291 this.get_cell(i).toggle_output();
1292 this.get_cell(i).toggle_output();
1292 this.set_dirty(true);
1293 this.set_dirty(true);
1293 };
1294 };
1294
1295
1295 /**
1296 /**
1296 * Toggle a scrollbar for long cell outputs.
1297 * Toggle a scrollbar for long cell outputs.
1297 *
1298 *
1298 * @method toggle_output_scroll
1299 * @method toggle_output_scroll
1299 * @param {Number} index A cell's numeric index
1300 * @param {Number} index A cell's numeric index
1300 */
1301 */
1301 Notebook.prototype.toggle_output_scroll = function (index) {
1302 Notebook.prototype.toggle_output_scroll = function (index) {
1302 var i = this.index_or_selected(index);
1303 var i = this.index_or_selected(index);
1303 this.get_cell(i).toggle_output_scroll();
1304 this.get_cell(i).toggle_output_scroll();
1304 };
1305 };
1305
1306
1306 /**
1307 /**
1307 * Hide each code cell's output area.
1308 * Hide each code cell's output area.
1308 *
1309 *
1309 * @method collapse_all_output
1310 * @method collapse_all_output
1310 */
1311 */
1311 Notebook.prototype.collapse_all_output = function () {
1312 Notebook.prototype.collapse_all_output = function () {
1312 var ncells = this.ncells();
1313 var ncells = this.ncells();
1313 var cells = this.get_cells();
1314 var cells = this.get_cells();
1314 for (var i=0; i<ncells; i++) {
1315 for (var i=0; i<ncells; i++) {
1315 if (cells[i] instanceof IPython.CodeCell) {
1316 if (cells[i] instanceof IPython.CodeCell) {
1316 cells[i].output_area.collapse();
1317 cells[i].output_area.collapse();
1317 }
1318 }
1318 };
1319 };
1319 // this should not be set if the `collapse` key is removed from nbformat
1320 // this should not be set if the `collapse` key is removed from nbformat
1320 this.set_dirty(true);
1321 this.set_dirty(true);
1321 };
1322 };
1322
1323
1323 /**
1324 /**
1324 * Expand each code cell's output area, and add a scrollbar for long output.
1325 * Expand each code cell's output area, and add a scrollbar for long output.
1325 *
1326 *
1326 * @method scroll_all_output
1327 * @method scroll_all_output
1327 */
1328 */
1328 Notebook.prototype.scroll_all_output = function () {
1329 Notebook.prototype.scroll_all_output = function () {
1329 var ncells = this.ncells();
1330 var ncells = this.ncells();
1330 var cells = this.get_cells();
1331 var cells = this.get_cells();
1331 for (var i=0; i<ncells; i++) {
1332 for (var i=0; i<ncells; i++) {
1332 if (cells[i] instanceof IPython.CodeCell) {
1333 if (cells[i] instanceof IPython.CodeCell) {
1333 cells[i].output_area.expand();
1334 cells[i].output_area.expand();
1334 cells[i].output_area.scroll_if_long();
1335 cells[i].output_area.scroll_if_long();
1335 }
1336 }
1336 };
1337 };
1337 // this should not be set if the `collapse` key is removed from nbformat
1338 // this should not be set if the `collapse` key is removed from nbformat
1338 this.set_dirty(true);
1339 this.set_dirty(true);
1339 };
1340 };
1340
1341
1341 /**
1342 /**
1342 * Expand each code cell's output area, and remove scrollbars.
1343 * Expand each code cell's output area, and remove scrollbars.
1343 *
1344 *
1344 * @method expand_all_output
1345 * @method expand_all_output
1345 */
1346 */
1346 Notebook.prototype.expand_all_output = function () {
1347 Notebook.prototype.expand_all_output = function () {
1347 var ncells = this.ncells();
1348 var ncells = this.ncells();
1348 var cells = this.get_cells();
1349 var cells = this.get_cells();
1349 for (var i=0; i<ncells; i++) {
1350 for (var i=0; i<ncells; i++) {
1350 if (cells[i] instanceof IPython.CodeCell) {
1351 if (cells[i] instanceof IPython.CodeCell) {
1351 cells[i].output_area.expand();
1352 cells[i].output_area.expand();
1352 cells[i].output_area.unscroll_area();
1353 cells[i].output_area.unscroll_area();
1353 }
1354 }
1354 };
1355 };
1355 // this should not be set if the `collapse` key is removed from nbformat
1356 // this should not be set if the `collapse` key is removed from nbformat
1356 this.set_dirty(true);
1357 this.set_dirty(true);
1357 };
1358 };
1358
1359
1359 /**
1360 /**
1360 * Clear each code cell's output area.
1361 * Clear each code cell's output area.
1361 *
1362 *
1362 * @method clear_all_output
1363 * @method clear_all_output
1363 */
1364 */
1364 Notebook.prototype.clear_all_output = function () {
1365 Notebook.prototype.clear_all_output = function () {
1365 var ncells = this.ncells();
1366 var ncells = this.ncells();
1366 var cells = this.get_cells();
1367 var cells = this.get_cells();
1367 for (var i=0; i<ncells; i++) {
1368 for (var i=0; i<ncells; i++) {
1368 if (cells[i] instanceof IPython.CodeCell) {
1369 if (cells[i] instanceof IPython.CodeCell) {
1369 cells[i].clear_output();
1370 cells[i].clear_output();
1370 // Make all In[] prompts blank, as well
1371 // Make all In[] prompts blank, as well
1371 // TODO: make this configurable (via checkbox?)
1372 // TODO: make this configurable (via checkbox?)
1372 cells[i].set_input_prompt();
1373 cells[i].set_input_prompt();
1373 }
1374 }
1374 };
1375 };
1375 this.set_dirty(true);
1376 this.set_dirty(true);
1376 };
1377 };
1377
1378
1378
1379
1379 // Other cell functions: line numbers, ...
1380 // Other cell functions: line numbers, ...
1380
1381
1381 /**
1382 /**
1382 * Toggle line numbers in the selected cell's input area.
1383 * Toggle line numbers in the selected cell's input area.
1383 *
1384 *
1384 * @method cell_toggle_line_numbers
1385 * @method cell_toggle_line_numbers
1385 */
1386 */
1386 Notebook.prototype.cell_toggle_line_numbers = function() {
1387 Notebook.prototype.cell_toggle_line_numbers = function() {
1387 this.get_selected_cell().toggle_line_numbers();
1388 this.get_selected_cell().toggle_line_numbers();
1388 };
1389 };
1389
1390
1390 // Session related things
1391 // Session related things
1391
1392
1392 /**
1393 /**
1393 * Start a new session and set it on each code cell.
1394 * Start a new session and set it on each code cell.
1394 *
1395 *
1395 * @method start_session
1396 * @method start_session
1396 */
1397 */
1397 Notebook.prototype.start_session = function () {
1398 Notebook.prototype.start_session = function () {
1398 var notebook_info = this.notebookPath() + this.notebook_name;
1399 var notebook_info = this.notebookPath() + this.notebook_name;
1399 this.session = new IPython.Session(notebook_info, this);
1400 this.session = new IPython.Session(notebook_info, this);
1400 this.session.start();
1401 this.session.start();
1401 this.link_cells_to_session();
1402 this.link_cells_to_session();
1402 };
1403 };
1403
1404
1404
1405
1405 /**
1406 /**
1406 * Once a session is started, link the code cells to the session
1407 * Once a session is started, link the code cells to the session
1407 *
1408 *
1408 */
1409 */
1409 Notebook.prototype.link_cells_to_session= function(){
1410 Notebook.prototype.link_cells_to_session= function(){
1410 var ncells = this.ncells();
1411 var ncells = this.ncells();
1411 for (var i=0; i<ncells; i++) {
1412 for (var i=0; i<ncells; i++) {
1412 var cell = this.get_cell(i);
1413 var cell = this.get_cell(i);
1413 if (cell instanceof IPython.CodeCell) {
1414 if (cell instanceof IPython.CodeCell) {
1414 cell.set_session(this.session);
1415 cell.set_session(this.session);
1415 };
1416 };
1416 };
1417 };
1417 };
1418 };
1418
1419
1419 /**
1420 /**
1420 * Prompt the user to restart the IPython kernel.
1421 * Prompt the user to restart the IPython kernel.
1421 *
1422 *
1422 * @method restart_kernel
1423 * @method restart_kernel
1423 */
1424 */
1424 Notebook.prototype.restart_kernel = function () {
1425 Notebook.prototype.restart_kernel = function () {
1425 var that = this;
1426 var that = this;
1426 IPython.dialog.modal({
1427 IPython.dialog.modal({
1427 title : "Restart kernel or continue running?",
1428 title : "Restart kernel or continue running?",
1428 body : $("<p/>").html(
1429 body : $("<p/>").html(
1429 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1430 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1430 ),
1431 ),
1431 buttons : {
1432 buttons : {
1432 "Continue running" : {},
1433 "Continue running" : {},
1433 "Restart" : {
1434 "Restart" : {
1434 "class" : "btn-danger",
1435 "class" : "btn-danger",
1435 "click" : function() {
1436 "click" : function() {
1436 that.session.restart_kernel();
1437 that.session.restart_kernel();
1437 }
1438 }
1438 }
1439 }
1439 }
1440 }
1440 });
1441 });
1441 };
1442 };
1442
1443
1443 /**
1444 /**
1444 * Run the selected cell.
1445 * Run the selected cell.
1445 *
1446 *
1446 * Execute or render cell outputs.
1447 * Execute or render cell outputs.
1447 *
1448 *
1448 * @method execute_selected_cell
1449 * @method execute_selected_cell
1449 * @param {Object} options Customize post-execution behavior
1450 * @param {Object} options Customize post-execution behavior
1450 */
1451 */
1451 Notebook.prototype.execute_selected_cell = function (options) {
1452 Notebook.prototype.execute_selected_cell = function (options) {
1452 // add_new: should a new cell be added if we are at the end of the nb
1453 // add_new: should a new cell be added if we are at the end of the nb
1453 // terminal: execute in terminal mode, which stays in the current cell
1454 // terminal: execute in terminal mode, which stays in the current cell
1454 var default_options = {terminal: false, add_new: true};
1455 var default_options = {terminal: false, add_new: true};
1455 $.extend(default_options, options);
1456 $.extend(default_options, options);
1456 var that = this;
1457 var that = this;
1457 var cell = that.get_selected_cell();
1458 var cell = that.get_selected_cell();
1458 var cell_index = that.find_cell_index(cell);
1459 var cell_index = that.find_cell_index(cell);
1459 if (cell instanceof IPython.CodeCell) {
1460 if (cell instanceof IPython.CodeCell) {
1460 cell.execute();
1461 cell.execute();
1461 }
1462 }
1462 if (default_options.terminal) {
1463 if (default_options.terminal) {
1463 cell.select_all();
1464 cell.select_all();
1464 } else {
1465 } else {
1465 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1466 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1466 that.insert_cell_below('code');
1467 that.insert_cell_below('code');
1467 // If we are adding a new cell at the end, scroll down to show it.
1468 // If we are adding a new cell at the end, scroll down to show it.
1468 that.scroll_to_bottom();
1469 that.scroll_to_bottom();
1469 } else {
1470 } else {
1470 that.select(cell_index+1);
1471 that.select(cell_index+1);
1471 };
1472 };
1472 };
1473 };
1473 this.set_dirty(true);
1474 this.set_dirty(true);
1474 };
1475 };
1475
1476
1476 /**
1477 /**
1477 * Execute all cells below the selected cell.
1478 * Execute all cells below the selected cell.
1478 *
1479 *
1479 * @method execute_cells_below
1480 * @method execute_cells_below
1480 */
1481 */
1481 Notebook.prototype.execute_cells_below = function () {
1482 Notebook.prototype.execute_cells_below = function () {
1482 this.execute_cell_range(this.get_selected_index(), this.ncells());
1483 this.execute_cell_range(this.get_selected_index(), this.ncells());
1483 this.scroll_to_bottom();
1484 this.scroll_to_bottom();
1484 };
1485 };
1485
1486
1486 /**
1487 /**
1487 * Execute all cells above the selected cell.
1488 * Execute all cells above the selected cell.
1488 *
1489 *
1489 * @method execute_cells_above
1490 * @method execute_cells_above
1490 */
1491 */
1491 Notebook.prototype.execute_cells_above = function () {
1492 Notebook.prototype.execute_cells_above = function () {
1492 this.execute_cell_range(0, this.get_selected_index());
1493 this.execute_cell_range(0, this.get_selected_index());
1493 };
1494 };
1494
1495
1495 /**
1496 /**
1496 * Execute all cells.
1497 * Execute all cells.
1497 *
1498 *
1498 * @method execute_all_cells
1499 * @method execute_all_cells
1499 */
1500 */
1500 Notebook.prototype.execute_all_cells = function () {
1501 Notebook.prototype.execute_all_cells = function () {
1501 this.execute_cell_range(0, this.ncells());
1502 this.execute_cell_range(0, this.ncells());
1502 this.scroll_to_bottom();
1503 this.scroll_to_bottom();
1503 };
1504 };
1504
1505
1505 /**
1506 /**
1506 * Execute a contiguous range of cells.
1507 * Execute a contiguous range of cells.
1507 *
1508 *
1508 * @method execute_cell_range
1509 * @method execute_cell_range
1509 * @param {Number} start Index of the first cell to execute (inclusive)
1510 * @param {Number} start Index of the first cell to execute (inclusive)
1510 * @param {Number} end Index of the last cell to execute (exclusive)
1511 * @param {Number} end Index of the last cell to execute (exclusive)
1511 */
1512 */
1512 Notebook.prototype.execute_cell_range = function (start, end) {
1513 Notebook.prototype.execute_cell_range = function (start, end) {
1513 for (var i=start; i<end; i++) {
1514 for (var i=start; i<end; i++) {
1514 this.select(i);
1515 this.select(i);
1515 this.execute_selected_cell({add_new:false});
1516 this.execute_selected_cell({add_new:false});
1516 };
1517 };
1517 };
1518 };
1518
1519
1519 // Persistance and loading
1520 // Persistance and loading
1520
1521
1521 /**
1522 /**
1522 * Getter method for this notebook's ID.
1523 * Getter method for this notebook's ID.
1523 *
1524 *
1524 * @method get_notebook_id
1525 * @method get_notebook_id
1525 * @return {String} This notebook's ID
1526 * @return {String} This notebook's ID
1526 */
1527 */
1527 Notebook.prototype.get_notebook_id = function () {
1528 Notebook.prototype.get_notebook_id = function () {
1528 return this.notebook_id;
1529 return this.notebook_id;
1529 };
1530 };
1530
1531
1531 /**
1532 /**
1532 * Getter method for this notebook's name.
1533 * Getter method for this notebook's name.
1533 *
1534 *
1534 * @method get_notebook_name
1535 * @method get_notebook_name
1535 * @return {String} This notebook's name
1536 * @return {String} This notebook's name
1536 */
1537 */
1537 Notebook.prototype.get_notebook_name = function () {
1538 Notebook.prototype.get_notebook_name = function () {
1538 nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1539 nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1539 return nbname;
1540 return nbname;
1540 };
1541 };
1541
1542
1542 /**
1543 /**
1543 * Setter method for this notebook's name.
1544 * Setter method for this notebook's name.
1544 *
1545 *
1545 * @method set_notebook_name
1546 * @method set_notebook_name
1546 * @param {String} name A new name for this notebook
1547 * @param {String} name A new name for this notebook
1547 */
1548 */
1548 Notebook.prototype.set_notebook_name = function (name) {
1549 Notebook.prototype.set_notebook_name = function (name) {
1549 this.notebook_name = name;
1550 this.notebook_name = name;
1550 };
1551 };
1551
1552
1552 /**
1553 /**
1553 * Check that a notebook's name is valid.
1554 * Check that a notebook's name is valid.
1554 *
1555 *
1555 * @method test_notebook_name
1556 * @method test_notebook_name
1556 * @param {String} nbname A name for this notebook
1557 * @param {String} nbname A name for this notebook
1557 * @return {Boolean} True if the name is valid, false if invalid
1558 * @return {Boolean} True if the name is valid, false if invalid
1558 */
1559 */
1559 Notebook.prototype.test_notebook_name = function (nbname) {
1560 Notebook.prototype.test_notebook_name = function (nbname) {
1560 nbname = nbname || '';
1561 nbname = nbname || '';
1561 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1562 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1562 return true;
1563 return true;
1563 } else {
1564 } else {
1564 return false;
1565 return false;
1565 };
1566 };
1566 };
1567 };
1567
1568
1568 /**
1569 /**
1569 * Load a notebook from JSON (.ipynb).
1570 * Load a notebook from JSON (.ipynb).
1570 *
1571 *
1571 * This currently handles one worksheet: others are deleted.
1572 * This currently handles one worksheet: others are deleted.
1572 *
1573 *
1573 * @method fromJSON
1574 * @method fromJSON
1574 * @param {Object} data JSON representation of a notebook
1575 * @param {Object} data JSON representation of a notebook
1575 */
1576 */
1576 Notebook.prototype.fromJSON = function (data) {
1577 Notebook.prototype.fromJSON = function (data) {
1577 data = data.content;
1578 data = data.content;
1578 var ncells = this.ncells();
1579 var ncells = this.ncells();
1579 var i;
1580 var i;
1580 for (i=0; i<ncells; i++) {
1581 for (i=0; i<ncells; i++) {
1581 // Always delete cell 0 as they get renumbered as they are deleted.
1582 // Always delete cell 0 as they get renumbered as they are deleted.
1582 this.delete_cell(0);
1583 this.delete_cell(0);
1583 };
1584 };
1584 // Save the metadata and name.
1585 // Save the metadata and name.
1585 this.metadata = data.metadata;
1586 this.metadata = data.metadata;
1586 this.notebook_name = data.metadata.name +'.ipynb';
1587 this.notebook_name = data.metadata.name +'.ipynb';
1587 // Only handle 1 worksheet for now.
1588 // Only handle 1 worksheet for now.
1588 var worksheet = data.worksheets[0];
1589 var worksheet = data.worksheets[0];
1589 if (worksheet !== undefined) {
1590 if (worksheet !== undefined) {
1590 if (worksheet.metadata) {
1591 if (worksheet.metadata) {
1591 this.worksheet_metadata = worksheet.metadata;
1592 this.worksheet_metadata = worksheet.metadata;
1592 }
1593 }
1593 var new_cells = worksheet.cells;
1594 var new_cells = worksheet.cells;
1594 ncells = new_cells.length;
1595 ncells = new_cells.length;
1595 var cell_data = null;
1596 var cell_data = null;
1596 var new_cell = null;
1597 var new_cell = null;
1597 for (i=0; i<ncells; i++) {
1598 for (i=0; i<ncells; i++) {
1598 cell_data = new_cells[i];
1599 cell_data = new_cells[i];
1599 // VERSIONHACK: plaintext -> raw
1600 // VERSIONHACK: plaintext -> raw
1600 // handle never-released plaintext name for raw cells
1601 // handle never-released plaintext name for raw cells
1601 if (cell_data.cell_type === 'plaintext'){
1602 if (cell_data.cell_type === 'plaintext'){
1602 cell_data.cell_type = 'raw';
1603 cell_data.cell_type = 'raw';
1603 }
1604 }
1604
1605
1605 new_cell = this.insert_cell_below(cell_data.cell_type);
1606 new_cell = this.insert_cell_below(cell_data.cell_type);
1606 new_cell.fromJSON(cell_data);
1607 new_cell.fromJSON(cell_data);
1607 };
1608 };
1608 };
1609 };
1609 if (data.worksheets.length > 1) {
1610 if (data.worksheets.length > 1) {
1610 IPython.dialog.modal({
1611 IPython.dialog.modal({
1611 title : "Multiple worksheets",
1612 title : "Multiple worksheets",
1612 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1613 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1613 "but this version of IPython can only handle the first. " +
1614 "but this version of IPython can only handle the first. " +
1614 "If you save this notebook, worksheets after the first will be lost.",
1615 "If you save this notebook, worksheets after the first will be lost.",
1615 buttons : {
1616 buttons : {
1616 OK : {
1617 OK : {
1617 class : "btn-danger"
1618 class : "btn-danger"
1618 }
1619 }
1619 }
1620 }
1620 });
1621 });
1621 }
1622 }
1622 };
1623 };
1623
1624
1624 /**
1625 /**
1625 * Dump this notebook into a JSON-friendly object.
1626 * Dump this notebook into a JSON-friendly object.
1626 *
1627 *
1627 * @method toJSON
1628 * @method toJSON
1628 * @return {Object} A JSON-friendly representation of this notebook.
1629 * @return {Object} A JSON-friendly representation of this notebook.
1629 */
1630 */
1630 Notebook.prototype.toJSON = function () {
1631 Notebook.prototype.toJSON = function () {
1631 var cells = this.get_cells();
1632 var cells = this.get_cells();
1632 var ncells = cells.length;
1633 var ncells = cells.length;
1633 var cell_array = new Array(ncells);
1634 var cell_array = new Array(ncells);
1634 for (var i=0; i<ncells; i++) {
1635 for (var i=0; i<ncells; i++) {
1635 cell_array[i] = cells[i].toJSON();
1636 cell_array[i] = cells[i].toJSON();
1636 };
1637 };
1637 var data = {
1638 var data = {
1638 // Only handle 1 worksheet for now.
1639 // Only handle 1 worksheet for now.
1639 worksheets : [{
1640 worksheets : [{
1640 cells: cell_array,
1641 cells: cell_array,
1641 metadata: this.worksheet_metadata
1642 metadata: this.worksheet_metadata
1642 }],
1643 }],
1643 metadata : this.metadata
1644 metadata : this.metadata
1644 };
1645 };
1645 return data;
1646 return data;
1646 };
1647 };
1647
1648
1648 /**
1649 /**
1649 * Start an autosave timer, for periodically saving the notebook.
1650 * Start an autosave timer, for periodically saving the notebook.
1650 *
1651 *
1651 * @method set_autosave_interval
1652 * @method set_autosave_interval
1652 * @param {Integer} interval the autosave interval in milliseconds
1653 * @param {Integer} interval the autosave interval in milliseconds
1653 */
1654 */
1654 Notebook.prototype.set_autosave_interval = function (interval) {
1655 Notebook.prototype.set_autosave_interval = function (interval) {
1655 var that = this;
1656 var that = this;
1656 // clear previous interval, so we don't get simultaneous timers
1657 // clear previous interval, so we don't get simultaneous timers
1657 if (this.autosave_timer) {
1658 if (this.autosave_timer) {
1658 clearInterval(this.autosave_timer);
1659 clearInterval(this.autosave_timer);
1659 }
1660 }
1660
1661
1661 this.autosave_interval = this.minimum_autosave_interval = interval;
1662 this.autosave_interval = this.minimum_autosave_interval = interval;
1662 if (interval) {
1663 if (interval) {
1663 this.autosave_timer = setInterval(function() {
1664 this.autosave_timer = setInterval(function() {
1664 if (that.dirty) {
1665 if (that.dirty) {
1665 that.save_notebook();
1666 that.save_notebook();
1666 }
1667 }
1667 }, interval);
1668 }, interval);
1668 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1669 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1669 } else {
1670 } else {
1670 this.autosave_timer = null;
1671 this.autosave_timer = null;
1671 $([IPython.events]).trigger("autosave_disabled.Notebook");
1672 $([IPython.events]).trigger("autosave_disabled.Notebook");
1672 };
1673 };
1673 };
1674 };
1674
1675
1675 /**
1676 /**
1676 * Save this notebook on the server.
1677 * Save this notebook on the server.
1677 *
1678 *
1678 * @method save_notebook
1679 * @method save_notebook
1679 */
1680 */
1680 Notebook.prototype.save_notebook = function () {
1681 Notebook.prototype.save_notebook = function () {
1681 // We may want to move the name/id/nbformat logic inside toJSON?
1682 // We may want to move the name/id/nbformat logic inside toJSON?
1682 var data = this.toJSON();
1683 var data = this.toJSON();
1683 data.metadata.name = this.notebook_name;
1684 data.metadata.name = this.notebook_name;
1684 data.nbformat = this.nbformat;
1685 data.nbformat = this.nbformat;
1685 data.nbformat_minor = this.nbformat_minor;
1686 data.nbformat_minor = this.nbformat_minor;
1686
1687
1687 // time the ajax call for autosave tuning purposes.
1688 // time the ajax call for autosave tuning purposes.
1688 var start = new Date().getTime();
1689 var start = new Date().getTime();
1689 // We do the call with settings so we can set cache to false.
1690 // We do the call with settings so we can set cache to false.
1690 var settings = {
1691 var settings = {
1691 processData : false,
1692 processData : false,
1692 cache : false,
1693 cache : false,
1693 type : "PUT",
1694 type : "PUT",
1694 data : JSON.stringify(data),
1695 data : JSON.stringify(data),
1695 headers : {'Content-Type': 'application/json'},
1696 headers : {'Content-Type': 'application/json'},
1696 success : $.proxy(this.save_notebook_success, this, start),
1697 success : $.proxy(this.save_notebook_success, this, start),
1697 error : $.proxy(this.save_notebook_error, this)
1698 error : $.proxy(this.save_notebook_error, this)
1698 };
1699 };
1699 $([IPython.events]).trigger('notebook_saving.Notebook');
1700 $([IPython.events]).trigger('notebook_saving.Notebook');
1700 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath()+ this.notebook_name;
1701 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath()+ this.notebook_name;
1701 $.ajax(url, settings);
1702 $.ajax(url, settings);
1702 };
1703 };
1703
1704
1704 /**
1705 /**
1705 * Success callback for saving a notebook.
1706 * Success callback for saving a notebook.
1706 *
1707 *
1707 * @method save_notebook_success
1708 * @method save_notebook_success
1708 * @param {Integer} start the time when the save request started
1709 * @param {Integer} start the time when the save request started
1709 * @param {Object} data JSON representation of a notebook
1710 * @param {Object} data JSON representation of a notebook
1710 * @param {String} status Description of response status
1711 * @param {String} status Description of response status
1711 * @param {jqXHR} xhr jQuery Ajax object
1712 * @param {jqXHR} xhr jQuery Ajax object
1712 */
1713 */
1713 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1714 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1714 this.set_dirty(false);
1715 this.set_dirty(false);
1715 $([IPython.events]).trigger('notebook_saved.Notebook');
1716 $([IPython.events]).trigger('notebook_saved.Notebook');
1716 this._update_autosave_interval(start);
1717 this._update_autosave_interval(start);
1717 if (this._checkpoint_after_save) {
1718 if (this._checkpoint_after_save) {
1718 this.create_checkpoint();
1719 this.create_checkpoint();
1719 this._checkpoint_after_save = false;
1720 this._checkpoint_after_save = false;
1720 };
1721 };
1721 };
1722 };
1722
1723
1723 /**
1724 /**
1724 * update the autosave interval based on how long the last save took
1725 * update the autosave interval based on how long the last save took
1725 *
1726 *
1726 * @method _update_autosave_interval
1727 * @method _update_autosave_interval
1727 * @param {Integer} timestamp when the save request started
1728 * @param {Integer} timestamp when the save request started
1728 */
1729 */
1729 Notebook.prototype._update_autosave_interval = function (start) {
1730 Notebook.prototype._update_autosave_interval = function (start) {
1730 var duration = (new Date().getTime() - start);
1731 var duration = (new Date().getTime() - start);
1731 if (this.autosave_interval) {
1732 if (this.autosave_interval) {
1732 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1733 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1733 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1734 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1734 // round to 10 seconds, otherwise we will be setting a new interval too often
1735 // round to 10 seconds, otherwise we will be setting a new interval too often
1735 interval = 10000 * Math.round(interval / 10000);
1736 interval = 10000 * Math.round(interval / 10000);
1736 // set new interval, if it's changed
1737 // set new interval, if it's changed
1737 if (interval != this.autosave_interval) {
1738 if (interval != this.autosave_interval) {
1738 this.set_autosave_interval(interval);
1739 this.set_autosave_interval(interval);
1739 }
1740 }
1740 }
1741 }
1741 };
1742 };
1742
1743
1743 /**
1744 /**
1744 * Failure callback for saving a notebook.
1745 * Failure callback for saving a notebook.
1745 *
1746 *
1746 * @method save_notebook_error
1747 * @method save_notebook_error
1747 * @param {jqXHR} xhr jQuery Ajax object
1748 * @param {jqXHR} xhr jQuery Ajax object
1748 * @param {String} status Description of response status
1749 * @param {String} status Description of response status
1749 * @param {String} error_msg HTTP error message
1750 * @param {String} error_msg HTTP error message
1750 */
1751 */
1751 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1752 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1752 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1753 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1753 };
1754 };
1754
1755
1755
1756
1756 Notebook.prototype.notebook_rename = function (nbname) {
1757 Notebook.prototype.notebook_rename = function (nbname) {
1757 var that = this;
1758 var that = this;
1758 var new_name = nbname + '.ipynb'
1759 var new_name = nbname + '.ipynb'
1759 var name = {'notebook_name': new_name};
1760 var name = {'notebook_name': new_name};
1760 var settings = {
1761 var settings = {
1761 processData : false,
1762 processData : false,
1762 cache : false,
1763 cache : false,
1763 type : "PATCH",
1764 type : "PATCH",
1764 data : JSON.stringify(name),
1765 data : JSON.stringify(name),
1765 dataType: "json",
1766 dataType: "json",
1766 headers : {'Content-Type': 'application/json'},
1767 headers : {'Content-Type': 'application/json'},
1767 success : $.proxy(that.rename_success, this)
1768 success : $.proxy(that.rename_success, this)
1768 };
1769 };
1769 $([IPython.events]).trigger('notebook_rename.Notebook');
1770 $([IPython.events]).trigger('notebook_rename.Notebook');
1770 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath()+ this.notebook_name;
1771 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath()+ this.notebook_name;
1771 $.ajax(url, settings);
1772 $.ajax(url, settings);
1772 };
1773 };
1773
1774
1774
1775
1775 Notebook.prototype.rename_success = function (json, status, xhr) {
1776 Notebook.prototype.rename_success = function (json, status, xhr) {
1776 this.notebook_name = json.notebook_name
1777 this.notebook_name = json.notebook_name
1777 var notebook_path = this.notebookPath() + this.notebook_name;
1778 var notebook_path = this.notebookPath() + this.notebook_name;
1778 this.session.notebook_rename(notebook_path);
1779 this.session.notebook_rename(notebook_path);
1779 $([IPython.events]).trigger('notebook_renamed.Notebook');
1780 $([IPython.events]).trigger('notebook_renamed.Notebook');
1780 }
1781 }
1781
1782
1782 /**
1783 /**
1783 * Request a notebook's data from the server.
1784 * Request a notebook's data from the server.
1784 *
1785 *
1785 * @method load_notebook
1786 * @method load_notebook
1786 * @param {String} notebook_naem and path A notebook to load
1787 * @param {String} notebook_naem and path A notebook to load
1787 */
1788 */
1788 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1789 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1789 var that = this;
1790 var that = this;
1790 this.notebook_name = notebook_name;
1791 this.notebook_name = notebook_name;
1791 this.notebook_path = notebook_path;
1792 this.notebook_path = notebook_path;
1792 // We do the call with settings so we can set cache to false.
1793 // We do the call with settings so we can set cache to false.
1793 var settings = {
1794 var settings = {
1794 processData : false,
1795 processData : false,
1795 cache : false,
1796 cache : false,
1796 type : "GET",
1797 type : "GET",
1797 dataType : "json",
1798 dataType : "json",
1798 success : $.proxy(this.load_notebook_success,this),
1799 success : $.proxy(this.load_notebook_success,this),
1799 error : $.proxy(this.load_notebook_error,this),
1800 error : $.proxy(this.load_notebook_error,this),
1800 };
1801 };
1801 $([IPython.events]).trigger('notebook_loading.Notebook');
1802 $([IPython.events]).trigger('notebook_loading.Notebook');
1802 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name;
1803 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name;
1803 $.ajax(url, settings);
1804 $.ajax(url, settings);
1804 };
1805 };
1805
1806
1806 /**
1807 /**
1807 * Success callback for loading a notebook from the server.
1808 * Success callback for loading a notebook from the server.
1808 *
1809 *
1809 * Load notebook data from the JSON response.
1810 * Load notebook data from the JSON response.
1810 *
1811 *
1811 * @method load_notebook_success
1812 * @method load_notebook_success
1812 * @param {Object} data JSON representation of a notebook
1813 * @param {Object} data JSON representation of a notebook
1813 * @param {String} status Description of response status
1814 * @param {String} status Description of response status
1814 * @param {jqXHR} xhr jQuery Ajax object
1815 * @param {jqXHR} xhr jQuery Ajax object
1815 */
1816 */
1816 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1817 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1817 this.fromJSON(data);
1818 this.fromJSON(data);
1818 if (this.ncells() === 0) {
1819 if (this.ncells() === 0) {
1819 this.insert_cell_below('code');
1820 this.insert_cell_below('code');
1820 };
1821 };
1821 this.set_dirty(false);
1822 this.set_dirty(false);
1822 this.select(0);
1823 this.select(0);
1823 this.scroll_to_top();
1824 this.scroll_to_top();
1824 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1825 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1825 var msg = "This notebook has been converted from an older " +
1826 var msg = "This notebook has been converted from an older " +
1826 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1827 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1827 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1828 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1828 "newer notebook format will be used and older versions of IPython " +
1829 "newer notebook format will be used and older versions of IPython " +
1829 "may not be able to read it. To keep the older version, close the " +
1830 "may not be able to read it. To keep the older version, close the " +
1830 "notebook without saving it.";
1831 "notebook without saving it.";
1831 IPython.dialog.modal({
1832 IPython.dialog.modal({
1832 title : "Notebook converted",
1833 title : "Notebook converted",
1833 body : msg,
1834 body : msg,
1834 buttons : {
1835 buttons : {
1835 OK : {
1836 OK : {
1836 class : "btn-primary"
1837 class : "btn-primary"
1837 }
1838 }
1838 }
1839 }
1839 });
1840 });
1840 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1841 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1841 var that = this;
1842 var that = this;
1842 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1843 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1843 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1844 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1844 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1845 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1845 this_vs + ". You can still work with this notebook, but some features " +
1846 this_vs + ". You can still work with this notebook, but some features " +
1846 "introduced in later notebook versions may not be available."
1847 "introduced in later notebook versions may not be available."
1847
1848
1848 IPython.dialog.modal({
1849 IPython.dialog.modal({
1849 title : "Newer Notebook",
1850 title : "Newer Notebook",
1850 body : msg,
1851 body : msg,
1851 buttons : {
1852 buttons : {
1852 OK : {
1853 OK : {
1853 class : "btn-danger"
1854 class : "btn-danger"
1854 }
1855 }
1855 }
1856 }
1856 });
1857 });
1857
1858
1858 }
1859 }
1859
1860
1860 // Create the session after the notebook is completely loaded to prevent
1861 // Create the session after the notebook is completely loaded to prevent
1861 // code execution upon loading, which is a security risk.
1862 // code execution upon loading, which is a security risk.
1862 if (this.session == null) {
1863 if (this.session == null) {
1863 this.start_session(this.notebook_path);
1864 this.start_session(this.notebook_path);
1864 }
1865 }
1865 // load our checkpoint list
1866 // load our checkpoint list
1866 IPython.notebook.list_checkpoints();
1867 IPython.notebook.list_checkpoints();
1867 $([IPython.events]).trigger('notebook_loaded.Notebook');
1868 $([IPython.events]).trigger('notebook_loaded.Notebook');
1868 };
1869 };
1869
1870
1870 /**
1871 /**
1871 * Failure callback for loading a notebook from the server.
1872 * Failure callback for loading a notebook from the server.
1872 *
1873 *
1873 * @method load_notebook_error
1874 * @method load_notebook_error
1874 * @param {jqXHR} xhr jQuery Ajax object
1875 * @param {jqXHR} xhr jQuery Ajax object
1875 * @param {String} textStatus Description of response status
1876 * @param {String} textStatus Description of response status
1876 * @param {String} errorThrow HTTP error message
1877 * @param {String} errorThrow HTTP error message
1877 */
1878 */
1878 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1879 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1879 if (xhr.status === 400) {
1880 if (xhr.status === 400) {
1880 var msg = errorThrow;
1881 var msg = errorThrow;
1881 } else if (xhr.status === 500) {
1882 } else if (xhr.status === 500) {
1882 var msg = "An unknown error occurred while loading this notebook. " +
1883 var msg = "An unknown error occurred while loading this notebook. " +
1883 "This version can load notebook formats " +
1884 "This version can load notebook formats " +
1884 "v" + this.nbformat + " or earlier.";
1885 "v" + this.nbformat + " or earlier.";
1885 }
1886 }
1886 IPython.dialog.modal({
1887 IPython.dialog.modal({
1887 title: "Error loading notebook",
1888 title: "Error loading notebook",
1888 body : msg,
1889 body : msg,
1889 buttons : {
1890 buttons : {
1890 "OK": {}
1891 "OK": {}
1891 }
1892 }
1892 });
1893 });
1893 }
1894 }
1894
1895
1895 /********************* checkpoint-related *********************/
1896 /********************* checkpoint-related *********************/
1896
1897
1897 /**
1898 /**
1898 * Save the notebook then immediately create a checkpoint.
1899 * Save the notebook then immediately create a checkpoint.
1899 *
1900 *
1900 * @method save_checkpoint
1901 * @method save_checkpoint
1901 */
1902 */
1902 Notebook.prototype.save_checkpoint = function () {
1903 Notebook.prototype.save_checkpoint = function () {
1903 this._checkpoint_after_save = true;
1904 this._checkpoint_after_save = true;
1904 this.save_notebook();
1905 this.save_notebook();
1905 };
1906 };
1906
1907
1907 /**
1908 /**
1908 * Add a checkpoint for this notebook.
1909 * Add a checkpoint for this notebook.
1909 * for use as a callback from checkpoint creation.
1910 * for use as a callback from checkpoint creation.
1910 *
1911 *
1911 * @method add_checkpoint
1912 * @method add_checkpoint
1912 */
1913 */
1913 Notebook.prototype.add_checkpoint = function (checkpoint) {
1914 Notebook.prototype.add_checkpoint = function (checkpoint) {
1914 var found = false;
1915 var found = false;
1915 for (var i = 0; i < this.checkpoints.length; i++) {
1916 for (var i = 0; i < this.checkpoints.length; i++) {
1916 var existing = this.checkpoints[i];
1917 var existing = this.checkpoints[i];
1917 if (existing.checkpoint_id == checkpoint.checkpoint_id) {
1918 if (existing.checkpoint_id == checkpoint.checkpoint_id) {
1918 found = true;
1919 found = true;
1919 this.checkpoints[i] = checkpoint;
1920 this.checkpoints[i] = checkpoint;
1920 break;
1921 break;
1921 }
1922 }
1922 }
1923 }
1923 if (!found) {
1924 if (!found) {
1924 this.checkpoints.push(checkpoint);
1925 this.checkpoints.push(checkpoint);
1925 }
1926 }
1926 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1927 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1927 };
1928 };
1928
1929
1929 /**
1930 /**
1930 * List checkpoints for this notebook.
1931 * List checkpoints for this notebook.
1931 *
1932 *
1932 * @method list_checkpoints
1933 * @method list_checkpoints
1933 */
1934 */
1934 Notebook.prototype.list_checkpoints = function () {
1935 Notebook.prototype.list_checkpoints = function () {
1935 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints';
1936 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints';
1936 $.get(url).done(
1937 $.get(url).done(
1937 $.proxy(this.list_checkpoints_success, this)
1938 $.proxy(this.list_checkpoints_success, this)
1938 ).fail(
1939 ).fail(
1939 $.proxy(this.list_checkpoints_error, this)
1940 $.proxy(this.list_checkpoints_error, this)
1940 );
1941 );
1941 };
1942 };
1942
1943
1943 /**
1944 /**
1944 * Success callback for listing checkpoints.
1945 * Success callback for listing checkpoints.
1945 *
1946 *
1946 * @method list_checkpoint_success
1947 * @method list_checkpoint_success
1947 * @param {Object} data JSON representation of a checkpoint
1948 * @param {Object} data JSON representation of a checkpoint
1948 * @param {String} status Description of response status
1949 * @param {String} status Description of response status
1949 * @param {jqXHR} xhr jQuery Ajax object
1950 * @param {jqXHR} xhr jQuery Ajax object
1950 */
1951 */
1951 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1952 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1952 var data = $.parseJSON(data);
1953 var data = $.parseJSON(data);
1953 this.checkpoints = data;
1954 this.checkpoints = data;
1954 if (data.length) {
1955 if (data.length) {
1955 this.last_checkpoint = data[data.length - 1];
1956 this.last_checkpoint = data[data.length - 1];
1956 } else {
1957 } else {
1957 this.last_checkpoint = null;
1958 this.last_checkpoint = null;
1958 }
1959 }
1959 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1960 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1960 };
1961 };
1961
1962
1962 /**
1963 /**
1963 * Failure callback for listing a checkpoint.
1964 * Failure callback for listing a checkpoint.
1964 *
1965 *
1965 * @method list_checkpoint_error
1966 * @method list_checkpoint_error
1966 * @param {jqXHR} xhr jQuery Ajax object
1967 * @param {jqXHR} xhr jQuery Ajax object
1967 * @param {String} status Description of response status
1968 * @param {String} status Description of response status
1968 * @param {String} error_msg HTTP error message
1969 * @param {String} error_msg HTTP error message
1969 */
1970 */
1970 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1971 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1971 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1972 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1972 };
1973 };
1973
1974
1974 /**
1975 /**
1975 * Create a checkpoint of this notebook on the server from the most recent save.
1976 * Create a checkpoint of this notebook on the server from the most recent save.
1976 *
1977 *
1977 * @method create_checkpoint
1978 * @method create_checkpoint
1978 */
1979 */
1979 Notebook.prototype.create_checkpoint = function () {
1980 Notebook.prototype.create_checkpoint = function () {
1980 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints';
1981 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints';
1981 $.post(url).done(
1982 $.post(url).done(
1982 $.proxy(this.create_checkpoint_success, this)
1983 $.proxy(this.create_checkpoint_success, this)
1983 ).fail(
1984 ).fail(
1984 $.proxy(this.create_checkpoint_error, this)
1985 $.proxy(this.create_checkpoint_error, this)
1985 );
1986 );
1986 };
1987 };
1987
1988
1988 /**
1989 /**
1989 * Success callback for creating a checkpoint.
1990 * Success callback for creating a checkpoint.
1990 *
1991 *
1991 * @method create_checkpoint_success
1992 * @method create_checkpoint_success
1992 * @param {Object} data JSON representation of a checkpoint
1993 * @param {Object} data JSON representation of a checkpoint
1993 * @param {String} status Description of response status
1994 * @param {String} status Description of response status
1994 * @param {jqXHR} xhr jQuery Ajax object
1995 * @param {jqXHR} xhr jQuery Ajax object
1995 */
1996 */
1996 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1997 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1997 var data = $.parseJSON(data);
1998 var data = $.parseJSON(data);
1998 this.add_checkpoint(data);
1999 this.add_checkpoint(data);
1999 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2000 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2000 };
2001 };
2001
2002
2002 /**
2003 /**
2003 * Failure callback for creating a checkpoint.
2004 * Failure callback for creating a checkpoint.
2004 *
2005 *
2005 * @method create_checkpoint_error
2006 * @method create_checkpoint_error
2006 * @param {jqXHR} xhr jQuery Ajax object
2007 * @param {jqXHR} xhr jQuery Ajax object
2007 * @param {String} status Description of response status
2008 * @param {String} status Description of response status
2008 * @param {String} error_msg HTTP error message
2009 * @param {String} error_msg HTTP error message
2009 */
2010 */
2010 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2011 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2011 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2012 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2012 };
2013 };
2013
2014
2014 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2015 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2015 var that = this;
2016 var that = this;
2016 var checkpoint = checkpoint || this.last_checkpoint;
2017 var checkpoint = checkpoint || this.last_checkpoint;
2017 if ( ! checkpoint ) {
2018 if ( ! checkpoint ) {
2018 console.log("restore dialog, but no checkpoint to restore to!");
2019 console.log("restore dialog, but no checkpoint to restore to!");
2019 return;
2020 return;
2020 }
2021 }
2021 var body = $('<div/>').append(
2022 var body = $('<div/>').append(
2022 $('<p/>').addClass("p-space").text(
2023 $('<p/>').addClass("p-space").text(
2023 "Are you sure you want to revert the notebook to " +
2024 "Are you sure you want to revert the notebook to " +
2024 "the latest checkpoint?"
2025 "the latest checkpoint?"
2025 ).append(
2026 ).append(
2026 $("<strong/>").text(
2027 $("<strong/>").text(
2027 " This cannot be undone."
2028 " This cannot be undone."
2028 )
2029 )
2029 )
2030 )
2030 ).append(
2031 ).append(
2031 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2032 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2032 ).append(
2033 ).append(
2033 $('<p/>').addClass("p-space").text(
2034 $('<p/>').addClass("p-space").text(
2034 Date(checkpoint.last_modified)
2035 Date(checkpoint.last_modified)
2035 ).css("text-align", "center")
2036 ).css("text-align", "center")
2036 );
2037 );
2037
2038
2038 IPython.dialog.modal({
2039 IPython.dialog.modal({
2039 title : "Revert notebook to checkpoint",
2040 title : "Revert notebook to checkpoint",
2040 body : body,
2041 body : body,
2041 buttons : {
2042 buttons : {
2042 Revert : {
2043 Revert : {
2043 class : "btn-danger",
2044 class : "btn-danger",
2044 click : function () {
2045 click : function () {
2045 that.restore_checkpoint(checkpoint.checkpoint_id);
2046 that.restore_checkpoint(checkpoint.checkpoint_id);
2046 }
2047 }
2047 },
2048 },
2048 Cancel : {}
2049 Cancel : {}
2049 }
2050 }
2050 });
2051 });
2051 }
2052 }
2052
2053
2053 /**
2054 /**
2054 * Restore the notebook to a checkpoint state.
2055 * Restore the notebook to a checkpoint state.
2055 *
2056 *
2056 * @method restore_checkpoint
2057 * @method restore_checkpoint
2057 * @param {String} checkpoint ID
2058 * @param {String} checkpoint ID
2058 */
2059 */
2059 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2060 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2060 <<<<<<< HEAD
2061 <<<<<<< HEAD
2061 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
2062 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
2062 if (this.notebook_path != "") {
2063 if (this.notebook_path != "") {
2063 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebook_path + this.notebook_name + '/checkpoints/' + checkpoint;
2064 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebook_path + this.notebook_name + '/checkpoints/' + checkpoint;
2064 }
2065 }
2065 else {
2066 else {
2066 var url = this.baseProjectUrl() + 'api/notebooks/' +this.notebook_name + '/checkpoints/' + checkpoint;
2067 var url = this.baseProjectUrl() + 'api/notebooks/' +this.notebook_name + '/checkpoints/' + checkpoint;
2067 }
2068 }
2068 =======
2069 =======
2069 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2070 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2070 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints/' + checkpoint;
2071 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints/' + checkpoint;
2071 >>>>>>> fixing path redirects, cleaning path logic
2072 >>>>>>> fixing path redirects, cleaning path logic
2072 $.post(url).done(
2073 $.post(url).done(
2073 $.proxy(this.restore_checkpoint_success, this)
2074 $.proxy(this.restore_checkpoint_success, this)
2074 ).fail(
2075 ).fail(
2075 $.proxy(this.restore_checkpoint_error, this)
2076 $.proxy(this.restore_checkpoint_error, this)
2076 );
2077 );
2077 };
2078 };
2078
2079
2079 /**
2080 /**
2080 * Success callback for restoring a notebook to a checkpoint.
2081 * Success callback for restoring a notebook to a checkpoint.
2081 *
2082 *
2082 * @method restore_checkpoint_success
2083 * @method restore_checkpoint_success
2083 * @param {Object} data (ignored, should be empty)
2084 * @param {Object} data (ignored, should be empty)
2084 * @param {String} status Description of response status
2085 * @param {String} status Description of response status
2085 * @param {jqXHR} xhr jQuery Ajax object
2086 * @param {jqXHR} xhr jQuery Ajax object
2086 */
2087 */
2087 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2088 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2088 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2089 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2089 this.load_notebook(this.notebook_name, this.notebook_path);
2090 this.load_notebook(this.notebook_name, this.notebook_path);
2090 };
2091 };
2091
2092
2092 /**
2093 /**
2093 * Failure callback for restoring a notebook to a checkpoint.
2094 * Failure callback for restoring a notebook to a checkpoint.
2094 *
2095 *
2095 * @method restore_checkpoint_error
2096 * @method restore_checkpoint_error
2096 * @param {jqXHR} xhr jQuery Ajax object
2097 * @param {jqXHR} xhr jQuery Ajax object
2097 * @param {String} status Description of response status
2098 * @param {String} status Description of response status
2098 * @param {String} error_msg HTTP error message
2099 * @param {String} error_msg HTTP error message
2099 */
2100 */
2100 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2101 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2101 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2102 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2102 };
2103 };
2103
2104
2104 /**
2105 /**
2105 * Delete a notebook checkpoint.
2106 * Delete a notebook checkpoint.
2106 *
2107 *
2107 * @method delete_checkpoint
2108 * @method delete_checkpoint
2108 * @param {String} checkpoint ID
2109 * @param {String} checkpoint ID
2109 */
2110 */
2110 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2111 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2111 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2112 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2112 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints/' + checkpoint;
2113 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints/' + checkpoint;
2113 $.ajax(url, {
2114 $.ajax(url, {
2114 type: 'DELETE',
2115 type: 'DELETE',
2115 success: $.proxy(this.delete_checkpoint_success, this),
2116 success: $.proxy(this.delete_checkpoint_success, this),
2116 error: $.proxy(this.delete_notebook_error,this)
2117 error: $.proxy(this.delete_notebook_error,this)
2117 });
2118 });
2118 };
2119 };
2119
2120
2120 /**
2121 /**
2121 * Success callback for deleting a notebook checkpoint
2122 * Success callback for deleting a notebook checkpoint
2122 *
2123 *
2123 * @method delete_checkpoint_success
2124 * @method delete_checkpoint_success
2124 * @param {Object} data (ignored, should be empty)
2125 * @param {Object} data (ignored, should be empty)
2125 * @param {String} status Description of response status
2126 * @param {String} status Description of response status
2126 * @param {jqXHR} xhr jQuery Ajax object
2127 * @param {jqXHR} xhr jQuery Ajax object
2127 */
2128 */
2128 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2129 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2129 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2130 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2130 this.load_notebook(this.notebook_name, this.notebook_path);
2131 this.load_notebook(this.notebook_name, this.notebook_path);
2131 };
2132 };
2132
2133
2133 /**
2134 /**
2134 * Failure callback for deleting a notebook checkpoint.
2135 * Failure callback for deleting a notebook checkpoint.
2135 *
2136 *
2136 * @method delete_checkpoint_error
2137 * @method delete_checkpoint_error
2137 * @param {jqXHR} xhr jQuery Ajax object
2138 * @param {jqXHR} xhr jQuery Ajax object
2138 * @param {String} status Description of response status
2139 * @param {String} status Description of response status
2139 * @param {String} error_msg HTTP error message
2140 * @param {String} error_msg HTTP error message
2140 */
2141 */
2141 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2142 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2142 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2143 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2143 };
2144 };
2144
2145
2145
2146
2146 IPython.Notebook = Notebook;
2147 IPython.Notebook = Notebook;
2147
2148
2148
2149
2149 return IPython;
2150 return IPython;
2150
2151
2151 }(IPython));
2152 }(IPython));
2152
2153
@@ -1,345 +1,346 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // NotebookList
9 // NotebookList
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var NotebookList = function (selector) {
14 var NotebookList = function (selector) {
15 this.selector = selector;
15 this.selector = selector;
16 if (this.selector !== undefined) {
16 if (this.selector !== undefined) {
17 this.element = $(selector);
17 this.element = $(selector);
18 this.style();
18 this.style();
19 this.bind_events();
19 this.bind_events();
20 }
20 }
21 this.notebooks_list = new Array();
21 this.notebooks_list = new Array();
22 this.sessions = new Object();
22 this.sessions = new Object();
23 };
23 };
24
24
25 NotebookList.prototype.baseProjectUrl = function () {
25 NotebookList.prototype.baseProjectUrl = function () {
26 return $('body').data('baseProjectUrl');
26 return $('body').data('baseProjectUrl');
27 };
27 };
28
28
29 NotebookList.prototype.notebookPath = function() {
29 NotebookList.prototype.notebookPath = function() {
30 var path = $('body').data('notebookPath');
30 var path = $('body').data('notebookPath');
31 path = decodeURIComponent(path);
31 if (path != "") {
32 if (path != "") {
32 if (path[path.length-1] != '/') {
33 if (path[path.length-1] != '/') {
33 path = path.substring(0,path.length);
34 path = path.substring(0,path.length);
34 };
35 };
35 return path;
36 return path;
36 } else {
37 } else {
37 return path;
38 return path;
38 };
39 };
39 };
40 };
40
41
41 NotebookList.prototype.url_name = function(name){
42 NotebookList.prototype.url_name = function(name){
42 return encodeURIComponent(name);
43 return encodeURIComponent(name);
43 };
44 };
44
45
45 NotebookList.prototype.style = function () {
46 NotebookList.prototype.style = function () {
46 $('#notebook_toolbar').addClass('list_toolbar');
47 $('#notebook_toolbar').addClass('list_toolbar');
47 $('#drag_info').addClass('toolbar_info');
48 $('#drag_info').addClass('toolbar_info');
48 $('#notebook_buttons').addClass('toolbar_buttons');
49 $('#notebook_buttons').addClass('toolbar_buttons');
49 $('#notebook_list_header').addClass('list_header');
50 $('#notebook_list_header').addClass('list_header');
50 this.element.addClass("list_container");
51 this.element.addClass("list_container");
51 };
52 };
52
53
53
54
54 NotebookList.prototype.bind_events = function () {
55 NotebookList.prototype.bind_events = function () {
55 var that = this;
56 var that = this;
56 $('#refresh_notebook_list').click(function () {
57 $('#refresh_notebook_list').click(function () {
57 that.load_list();
58 that.load_list();
58 });
59 });
59 this.element.bind('dragover', function () {
60 this.element.bind('dragover', function () {
60 return false;
61 return false;
61 });
62 });
62 this.element.bind('drop', function(event){
63 this.element.bind('drop', function(event){
63 that.handelFilesUpload(event,'drop');
64 that.handelFilesUpload(event,'drop');
64 return false;
65 return false;
65 });
66 });
66 };
67 };
67
68
68 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
69 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
69 var that = this;
70 var that = this;
70 var files;
71 var files;
71 if(dropOrForm =='drop'){
72 if(dropOrForm =='drop'){
72 files = event.originalEvent.dataTransfer.files;
73 files = event.originalEvent.dataTransfer.files;
73 } else
74 } else
74 {
75 {
75 files = event.originalEvent.target.files
76 files = event.originalEvent.target.files
76 }
77 }
77 for (var i = 0, f; f = files[i]; i++) {
78 for (var i = 0, f; f = files[i]; i++) {
78 var reader = new FileReader();
79 var reader = new FileReader();
79 reader.readAsText(f);
80 reader.readAsText(f);
80 var fname = f.name.split('.');
81 var fname = f.name.split('.');
81 var nbname = fname.slice(0,-1).join('.');
82 var nbname = fname.slice(0,-1).join('.');
82 var nbformat = fname.slice(-1)[0];
83 var nbformat = fname.slice(-1)[0];
83 if (nbformat === 'ipynb') {nbformat = 'json';};
84 if (nbformat === 'ipynb') {nbformat = 'json';};
84 if (nbformat === 'py' || nbformat === 'json') {
85 if (nbformat === 'py' || nbformat === 'json') {
85 var item = that.new_notebook_item(0);
86 var item = that.new_notebook_item(0);
86 that.add_name_input(nbname, item);
87 that.add_name_input(nbname, item);
87 item.data('nbformat', nbformat);
88 item.data('nbformat', nbformat);
88 // Store the notebook item in the reader so we can use it later
89 // Store the notebook item in the reader so we can use it later
89 // to know which item it belongs to.
90 // to know which item it belongs to.
90 $(reader).data('item', item);
91 $(reader).data('item', item);
91 reader.onload = function (event) {
92 reader.onload = function (event) {
92 var nbitem = $(event.target).data('item');
93 var nbitem = $(event.target).data('item');
93 that.add_notebook_data(event.target.result, nbitem);
94 that.add_notebook_data(event.target.result, nbitem);
94 that.add_upload_button(nbitem);
95 that.add_upload_button(nbitem);
95 };
96 };
96 };
97 };
97 }
98 }
98 return false;
99 return false;
99 };
100 };
100
101
101 NotebookList.prototype.clear_list = function () {
102 NotebookList.prototype.clear_list = function () {
102 this.element.children('.list_item').remove();
103 this.element.children('.list_item').remove();
103 };
104 };
104
105
105 NotebookList.prototype.load_sessions = function(){
106 NotebookList.prototype.load_sessions = function(){
106 var that = this;
107 var that = this;
107 var settings = {
108 var settings = {
108 processData : false,
109 processData : false,
109 cache : false,
110 cache : false,
110 type : "GET",
111 type : "GET",
111 dataType : "json",
112 dataType : "json",
112 success : $.proxy(that.sessions_loaded, this)
113 success : $.proxy(that.sessions_loaded, this)
113 };
114 };
114 var url = this.baseProjectUrl() + 'api/sessions';
115 var url = this.baseProjectUrl() + 'api/sessions';
115 $.ajax(url,settings);
116 $.ajax(url,settings);
116 };
117 };
117
118
118
119
119 NotebookList.prototype.sessions_loaded = function(data){
120 NotebookList.prototype.sessions_loaded = function(data){
120 this.sessions = new Object();
121 this.sessions = new Object();
121 var len = data.length;
122 var len = data.length;
122 if (len != 0) {
123 if (len != 0) {
123 for (var i=0; i<len; i++) {
124 for (var i=0; i<len; i++) {
124 if (data[i]['notebook_path']==null) {
125 if (data[i]['notebook_path']==null) {
125 nb_path = data[i]['notebook_name'];
126 nb_path = data[i]['notebook_name'];
126 }
127 }
127 else {
128 else {
128 nb_path = data[i]['notebook_path'] + data[i]['notebook_name'];
129 nb_path = data[i]['notebook_path'] + data[i]['notebook_name'];
129 }
130 }
130 this.sessions[nb_path]= data[i]['session_id'];
131 this.sessions[nb_path]= data[i]['session_id'];
131 }
132 }
132 };
133 };
133 this.load_list();
134 this.load_list();
134 };
135 };
135
136
136 NotebookList.prototype.load_list = function () {
137 NotebookList.prototype.load_list = function () {
137 var that = this;
138 var that = this;
138 var settings = {
139 var settings = {
139 processData : false,
140 processData : false,
140 cache : false,
141 cache : false,
141 type : "GET",
142 type : "GET",
142 dataType : "json",
143 dataType : "json",
143 success : $.proxy(this.list_loaded, this),
144 success : $.proxy(this.list_loaded, this),
144 error : $.proxy( function(){
145 error : $.proxy( function(){
145 that.list_loaded([], null, null, {msg:"Error connecting to server."});
146 that.list_loaded([], null, null, {msg:"Error connecting to server."});
146 },this)
147 },this)
147 };
148 };
148
149
149 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath();
150 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath();
150 $.ajax(url, settings);
151 $.ajax(url, settings);
151 };
152 };
152
153
153
154
154 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
155 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
155 var message = 'Notebook list empty.';
156 var message = 'Notebook list empty.';
156 if (param !== undefined && param.msg) {
157 if (param !== undefined && param.msg) {
157 var message = param.msg;
158 var message = param.msg;
158 }
159 }
159 var len = data.length;
160 var len = data.length;
160 this.clear_list();
161 this.clear_list();
161 if(len == 0)
162 if(len == 0)
162 {
163 {
163 $(this.new_notebook_item(0))
164 $(this.new_notebook_item(0))
164 .append(
165 .append(
165 $('<div style="margin:auto;text-align:center;color:grey"/>')
166 $('<div style="margin:auto;text-align:center;color:grey"/>')
166 .text(message)
167 .text(message)
167 )
168 )
168 }
169 }
169 for (var i=0; i<len; i++) {
170 for (var i=0; i<len; i++) {
170 var name = data[i].notebook_name;
171 var name = data[i].notebook_name;
171 var path = this.notebookPath();
172 var path = this.notebookPath();
172 var nbname = name.split(".")[0];
173 var nbname = name.split(".")[0];
173 var item = this.new_notebook_item(i);
174 var item = this.new_notebook_item(i);
174 this.add_link(path, nbname, item);
175 this.add_link(path, nbname, item);
175 name = this.notebookPath() + name;
176 name = this.notebookPath() + name;
176 if(this.sessions[name] == undefined){
177 if(this.sessions[name] == undefined){
177 this.add_delete_button(item);
178 this.add_delete_button(item);
178 } else {
179 } else {
179 this.add_shutdown_button(item,this.sessions[name]);
180 this.add_shutdown_button(item,this.sessions[name]);
180 }
181 }
181 };
182 };
182 };
183 };
183
184
184
185
185 NotebookList.prototype.new_notebook_item = function (index) {
186 NotebookList.prototype.new_notebook_item = function (index) {
186 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
187 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
187 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
188 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
188 // item.css('border-top-style','none');
189 // item.css('border-top-style','none');
189 item.append($("<div/>").addClass("span12").append(
190 item.append($("<div/>").addClass("span12").append(
190 $("<a/>").addClass("item_link").append(
191 $("<a/>").addClass("item_link").append(
191 $("<span/>").addClass("item_name")
192 $("<span/>").addClass("item_name")
192 )
193 )
193 ).append(
194 ).append(
194 $('<div/>').addClass("item_buttons btn-group pull-right")
195 $('<div/>').addClass("item_buttons btn-group pull-right")
195 ));
196 ));
196
197
197 if (index === -1) {
198 if (index === -1) {
198 this.element.append(item);
199 this.element.append(item);
199 } else {
200 } else {
200 this.element.children().eq(index).after(item);
201 this.element.children().eq(index).after(item);
201 }
202 }
202 return item;
203 return item;
203 };
204 };
204
205
205
206
206 NotebookList.prototype.add_link = function (path, nbname, item) {
207 NotebookList.prototype.add_link = function (path, nbname, item) {
207 item.data('nbname', nbname);
208 item.data('nbname', nbname);
208 item.data('path', path);
209 item.data('path', path);
209 item.find(".item_name").text(nbname);
210 item.find(".item_name").text(nbname);
210 item.find("a.item_link")
211 item.find("a.item_link")
211 .attr('href', this.baseProjectUrl() + "notebooks/" + this.notebookPath() + nbname + ".ipynb")
212 .attr('href', this.baseProjectUrl() + "notebooks/" + this.notebookPath() + nbname + ".ipynb")
212 .attr('target','_blank');
213 .attr('target','_blank');
213 };
214 };
214
215
215
216
216 NotebookList.prototype.add_name_input = function (nbname, item) {
217 NotebookList.prototype.add_name_input = function (nbname, item) {
217 item.data('nbname', nbname);
218 item.data('nbname', nbname);
218 item.find(".item_name").empty().append(
219 item.find(".item_name").empty().append(
219 $('<input/>')
220 $('<input/>')
220 .addClass("nbname_input")
221 .addClass("nbname_input")
221 .attr('value', nbname)
222 .attr('value', nbname)
222 .attr('size', '30')
223 .attr('size', '30')
223 .attr('type', 'text')
224 .attr('type', 'text')
224 );
225 );
225 };
226 };
226
227
227
228
228 NotebookList.prototype.add_notebook_data = function (data, item) {
229 NotebookList.prototype.add_notebook_data = function (data, item) {
229 item.data('nbdata',data);
230 item.data('nbdata',data);
230 };
231 };
231
232
232
233
233 NotebookList.prototype.add_shutdown_button = function (item, session) {
234 NotebookList.prototype.add_shutdown_button = function (item, session) {
234 var that = this;
235 var that = this;
235 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini").
236 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini").
236 click(function (e) {
237 click(function (e) {
237 var settings = {
238 var settings = {
238 processData : false,
239 processData : false,
239 cache : false,
240 cache : false,
240 type : "DELETE",
241 type : "DELETE",
241 dataType : "json",
242 dataType : "json",
242 success : function () {
243 success : function () {
243 that.load_sessions();
244 that.load_sessions();
244 }
245 }
245 };
246 };
246 var url = that.baseProjectUrl() + 'api/sessions/' + session;
247 var url = that.baseProjectUrl() + 'api/sessions/' + session;
247 $.ajax(url, settings);
248 $.ajax(url, settings);
248 return false;
249 return false;
249 });
250 });
250 // var new_buttons = item.find('a'); // shutdown_button;
251 // var new_buttons = item.find('a'); // shutdown_button;
251 item.find(".item_buttons").html("").append(shutdown_button);
252 item.find(".item_buttons").html("").append(shutdown_button);
252 };
253 };
253
254
254 NotebookList.prototype.add_delete_button = function (item) {
255 NotebookList.prototype.add_delete_button = function (item) {
255 var new_buttons = $('<span/>').addClass("btn-group pull-right");
256 var new_buttons = $('<span/>').addClass("btn-group pull-right");
256 var notebooklist = this;
257 var notebooklist = this;
257 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
258 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
258 click(function (e) {
259 click(function (e) {
259 // $(this) is the button that was clicked.
260 // $(this) is the button that was clicked.
260 var that = $(this);
261 var that = $(this);
261 // We use the nbname and notebook_id from the parent notebook_item element's
262 // We use the nbname and notebook_id from the parent notebook_item element's
262 // data because the outer scopes values change as we iterate through the loop.
263 // data because the outer scopes values change as we iterate through the loop.
263 var parent_item = that.parents('div.list_item');
264 var parent_item = that.parents('div.list_item');
264 var nbname = parent_item.data('nbname');
265 var nbname = parent_item.data('nbname');
265 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
266 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
266 IPython.dialog.modal({
267 IPython.dialog.modal({
267 title : "Delete notebook",
268 title : "Delete notebook",
268 body : message,
269 body : message,
269 buttons : {
270 buttons : {
270 Delete : {
271 Delete : {
271 class: "btn-danger",
272 class: "btn-danger",
272 click: function() {
273 click: function() {
273 var settings = {
274 var settings = {
274 processData : false,
275 processData : false,
275 cache : false,
276 cache : false,
276 type : "DELETE",
277 type : "DELETE",
277 dataType : "json",
278 dataType : "json",
278 success : function (data, status, xhr) {
279 success : function (data, status, xhr) {
279 parent_item.remove();
280 parent_item.remove();
280 }
281 }
281 };
282 };
282 var url = notebooklist.baseProjectUrl() + 'api/notebooks/' + notebooklist.notebookPath() + nbname + '.ipynb';
283 var url = notebooklist.baseProjectUrl() + 'api/notebooks/' + notebooklist.notebookPath() + nbname + '.ipynb';
283 $.ajax(url, settings);
284 $.ajax(url, settings);
284 }
285 }
285 },
286 },
286 Cancel : {}
287 Cancel : {}
287 }
288 }
288 });
289 });
289 return false;
290 return false;
290 });
291 });
291 item.find(".item_buttons").html("").append(delete_button);
292 item.find(".item_buttons").html("").append(delete_button);
292 };
293 };
293
294
294
295
295 NotebookList.prototype.add_upload_button = function (item) {
296 NotebookList.prototype.add_upload_button = function (item) {
296 var that = this;
297 var that = this;
297 var upload_button = $('<button/>').text("Upload")
298 var upload_button = $('<button/>').text("Upload")
298 .addClass('btn btn-primary btn-mini upload_button')
299 .addClass('btn btn-primary btn-mini upload_button')
299 .click(function (e) {
300 .click(function (e) {
300 var nbname = item.find('.item_name > input').attr('value');
301 var nbname = item.find('.item_name > input').attr('value');
301 var nbformat = item.data('nbformat');
302 var nbformat = item.data('nbformat');
302 var nbdata = item.data('nbdata');
303 var nbdata = item.data('nbdata');
303 var content_type = 'text/plain';
304 var content_type = 'text/plain';
304 if (nbformat === 'json') {
305 if (nbformat === 'json') {
305 content_type = 'application/json';
306 content_type = 'application/json';
306 } else if (nbformat === 'py') {
307 } else if (nbformat === 'py') {
307 content_type = 'application/x-python';
308 content_type = 'application/x-python';
308 };
309 };
309 var settings = {
310 var settings = {
310 processData : false,
311 processData : false,
311 cache : false,
312 cache : false,
312 type : 'POST',
313 type : 'POST',
313 dataType : 'json',
314 dataType : 'json',
314 data : nbdata,
315 data : nbdata,
315 headers : {'Content-Type': content_type},
316 headers : {'Content-Type': content_type},
316 success : function (data, status, xhr) {
317 success : function (data, status, xhr) {
317 that.add_link(data, nbname, item);
318 that.add_link(data, nbname, item);
318 that.add_delete_button(item);
319 that.add_delete_button(item);
319 }
320 }
320 };
321 };
321
322
322 var qs = $.param({name:nbname, format:nbformat});
323 var qs = $.param({name:nbname, format:nbformat});
323 var url = that.baseProjectUrl() + 'notebooks?' + qs;
324 var url = that.baseProjectUrl() + 'notebooks?' + qs;
324 $.ajax(url, settings);
325 $.ajax(url, settings);
325 return false;
326 return false;
326 });
327 });
327 var cancel_button = $('<button/>').text("Cancel")
328 var cancel_button = $('<button/>').text("Cancel")
328 .addClass("btn btn-mini")
329 .addClass("btn btn-mini")
329 .click(function (e) {
330 .click(function (e) {
330 console.log('cancel click');
331 console.log('cancel click');
331 item.remove();
332 item.remove();
332 return false;
333 return false;
333 });
334 });
334 item.find(".item_buttons").empty()
335 item.find(".item_buttons").empty()
335 .append(upload_button)
336 .append(upload_button)
336 .append(cancel_button);
337 .append(cancel_button);
337 };
338 };
338
339
339
340
340 IPython.NotebookList = NotebookList;
341 IPython.NotebookList = NotebookList;
341
342
342 return IPython;
343 return IPython;
343
344
344 }(IPython));
345 }(IPython));
345
346
General Comments 0
You need to be logged in to leave comments. Login now