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