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