##// END OF EJS Templates
Merge pull request #6924 from takluyver/contents-promises...
Min RK -
r18863:6fa05ce6 merge
parent child Browse files
Show More
@@ -1,628 +1,644 b''
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 'codemirror/lib/codemirror',
7 'codemirror/lib/codemirror',
8 ], function(IPython, $, CodeMirror){
8 ], function(IPython, $, CodeMirror){
9 "use strict";
9 "use strict";
10
10
11 IPython.load_extensions = function () {
11 IPython.load_extensions = function () {
12 // load one or more IPython notebook extensions with requirejs
12 // load one or more IPython notebook extensions with requirejs
13
13
14 var extensions = [];
14 var extensions = [];
15 var extension_names = arguments;
15 var extension_names = arguments;
16 for (var i = 0; i < extension_names.length; i++) {
16 for (var i = 0; i < extension_names.length; i++) {
17 extensions.push("nbextensions/" + arguments[i]);
17 extensions.push("nbextensions/" + arguments[i]);
18 }
18 }
19
19
20 require(extensions,
20 require(extensions,
21 function () {
21 function () {
22 for (var i = 0; i < arguments.length; i++) {
22 for (var i = 0; i < arguments.length; i++) {
23 var ext = arguments[i];
23 var ext = arguments[i];
24 var ext_name = extension_names[i];
24 var ext_name = extension_names[i];
25 // success callback
25 // success callback
26 console.log("Loaded extension: " + ext_name);
26 console.log("Loaded extension: " + ext_name);
27 if (ext && ext.load_ipython_extension !== undefined) {
27 if (ext && ext.load_ipython_extension !== undefined) {
28 ext.load_ipython_extension();
28 ext.load_ipython_extension();
29 }
29 }
30 }
30 }
31 },
31 },
32 function (err) {
32 function (err) {
33 // failure callback
33 // failure callback
34 console.log("Failed to load extension(s):", err.requireModules, err);
34 console.log("Failed to load extension(s):", err.requireModules, err);
35 }
35 }
36 );
36 );
37 };
37 };
38
38
39 //============================================================================
39 //============================================================================
40 // Cross-browser RegEx Split
40 // Cross-browser RegEx Split
41 //============================================================================
41 //============================================================================
42
42
43 // This code has been MODIFIED from the code licensed below to not replace the
43 // This code has been MODIFIED from the code licensed below to not replace the
44 // default browser split. The license is reproduced here.
44 // default browser split. The license is reproduced here.
45
45
46 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
46 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
47 /*!
47 /*!
48 * Cross-Browser Split 1.1.1
48 * Cross-Browser Split 1.1.1
49 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
49 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
50 * Available under the MIT License
50 * Available under the MIT License
51 * ECMAScript compliant, uniform cross-browser split method
51 * ECMAScript compliant, uniform cross-browser split method
52 */
52 */
53
53
54 /**
54 /**
55 * Splits a string into an array of strings using a regex or string
55 * Splits a string into an array of strings using a regex or string
56 * separator. Matches of the separator are not included in the result array.
56 * separator. Matches of the separator are not included in the result array.
57 * However, if `separator` is a regex that contains capturing groups,
57 * However, if `separator` is a regex that contains capturing groups,
58 * backreferences are spliced into the result each time `separator` is
58 * backreferences are spliced into the result each time `separator` is
59 * matched. Fixes browser bugs compared to the native
59 * matched. Fixes browser bugs compared to the native
60 * `String.prototype.split` and can be used reliably cross-browser.
60 * `String.prototype.split` and can be used reliably cross-browser.
61 * @param {String} str String to split.
61 * @param {String} str String to split.
62 * @param {RegExp|String} separator Regex or string to use for separating
62 * @param {RegExp|String} separator Regex or string to use for separating
63 * the string.
63 * the string.
64 * @param {Number} [limit] Maximum number of items to include in the result
64 * @param {Number} [limit] Maximum number of items to include in the result
65 * array.
65 * array.
66 * @returns {Array} Array of substrings.
66 * @returns {Array} Array of substrings.
67 * @example
67 * @example
68 *
68 *
69 * // Basic use
69 * // Basic use
70 * regex_split('a b c d', ' ');
70 * regex_split('a b c d', ' ');
71 * // -> ['a', 'b', 'c', 'd']
71 * // -> ['a', 'b', 'c', 'd']
72 *
72 *
73 * // With limit
73 * // With limit
74 * regex_split('a b c d', ' ', 2);
74 * regex_split('a b c d', ' ', 2);
75 * // -> ['a', 'b']
75 * // -> ['a', 'b']
76 *
76 *
77 * // Backreferences in result array
77 * // Backreferences in result array
78 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
78 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
79 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
79 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
80 */
80 */
81 var regex_split = function (str, separator, limit) {
81 var regex_split = function (str, separator, limit) {
82 // If `separator` is not a regex, use `split`
82 // If `separator` is not a regex, use `split`
83 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
83 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
84 return split.call(str, separator, limit);
84 return split.call(str, separator, limit);
85 }
85 }
86 var output = [],
86 var output = [],
87 flags = (separator.ignoreCase ? "i" : "") +
87 flags = (separator.ignoreCase ? "i" : "") +
88 (separator.multiline ? "m" : "") +
88 (separator.multiline ? "m" : "") +
89 (separator.extended ? "x" : "") + // Proposed for ES6
89 (separator.extended ? "x" : "") + // Proposed for ES6
90 (separator.sticky ? "y" : ""), // Firefox 3+
90 (separator.sticky ? "y" : ""), // Firefox 3+
91 lastLastIndex = 0,
91 lastLastIndex = 0,
92 // Make `global` and avoid `lastIndex` issues by working with a copy
92 // Make `global` and avoid `lastIndex` issues by working with a copy
93 separator = new RegExp(separator.source, flags + "g"),
93 separator = new RegExp(separator.source, flags + "g"),
94 separator2, match, lastIndex, lastLength;
94 separator2, match, lastIndex, lastLength;
95 str += ""; // Type-convert
95 str += ""; // Type-convert
96
96
97 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
97 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
98 if (!compliantExecNpcg) {
98 if (!compliantExecNpcg) {
99 // Doesn't need flags gy, but they don't hurt
99 // Doesn't need flags gy, but they don't hurt
100 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
100 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
101 }
101 }
102 /* Values for `limit`, per the spec:
102 /* Values for `limit`, per the spec:
103 * If undefined: 4294967295 // Math.pow(2, 32) - 1
103 * If undefined: 4294967295 // Math.pow(2, 32) - 1
104 * If 0, Infinity, or NaN: 0
104 * If 0, Infinity, or NaN: 0
105 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
105 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
106 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
106 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
107 * If other: Type-convert, then use the above rules
107 * If other: Type-convert, then use the above rules
108 */
108 */
109 limit = typeof(limit) === "undefined" ?
109 limit = typeof(limit) === "undefined" ?
110 -1 >>> 0 : // Math.pow(2, 32) - 1
110 -1 >>> 0 : // Math.pow(2, 32) - 1
111 limit >>> 0; // ToUint32(limit)
111 limit >>> 0; // ToUint32(limit)
112 while (match = separator.exec(str)) {
112 while (match = separator.exec(str)) {
113 // `separator.lastIndex` is not reliable cross-browser
113 // `separator.lastIndex` is not reliable cross-browser
114 lastIndex = match.index + match[0].length;
114 lastIndex = match.index + match[0].length;
115 if (lastIndex > lastLastIndex) {
115 if (lastIndex > lastLastIndex) {
116 output.push(str.slice(lastLastIndex, match.index));
116 output.push(str.slice(lastLastIndex, match.index));
117 // Fix browsers whose `exec` methods don't consistently return `undefined` for
117 // Fix browsers whose `exec` methods don't consistently return `undefined` for
118 // nonparticipating capturing groups
118 // nonparticipating capturing groups
119 if (!compliantExecNpcg && match.length > 1) {
119 if (!compliantExecNpcg && match.length > 1) {
120 match[0].replace(separator2, function () {
120 match[0].replace(separator2, function () {
121 for (var i = 1; i < arguments.length - 2; i++) {
121 for (var i = 1; i < arguments.length - 2; i++) {
122 if (typeof(arguments[i]) === "undefined") {
122 if (typeof(arguments[i]) === "undefined") {
123 match[i] = undefined;
123 match[i] = undefined;
124 }
124 }
125 }
125 }
126 });
126 });
127 }
127 }
128 if (match.length > 1 && match.index < str.length) {
128 if (match.length > 1 && match.index < str.length) {
129 Array.prototype.push.apply(output, match.slice(1));
129 Array.prototype.push.apply(output, match.slice(1));
130 }
130 }
131 lastLength = match[0].length;
131 lastLength = match[0].length;
132 lastLastIndex = lastIndex;
132 lastLastIndex = lastIndex;
133 if (output.length >= limit) {
133 if (output.length >= limit) {
134 break;
134 break;
135 }
135 }
136 }
136 }
137 if (separator.lastIndex === match.index) {
137 if (separator.lastIndex === match.index) {
138 separator.lastIndex++; // Avoid an infinite loop
138 separator.lastIndex++; // Avoid an infinite loop
139 }
139 }
140 }
140 }
141 if (lastLastIndex === str.length) {
141 if (lastLastIndex === str.length) {
142 if (lastLength || !separator.test("")) {
142 if (lastLength || !separator.test("")) {
143 output.push("");
143 output.push("");
144 }
144 }
145 } else {
145 } else {
146 output.push(str.slice(lastLastIndex));
146 output.push(str.slice(lastLastIndex));
147 }
147 }
148 return output.length > limit ? output.slice(0, limit) : output;
148 return output.length > limit ? output.slice(0, limit) : output;
149 };
149 };
150
150
151 //============================================================================
151 //============================================================================
152 // End contributed Cross-browser RegEx Split
152 // End contributed Cross-browser RegEx Split
153 //============================================================================
153 //============================================================================
154
154
155
155
156 var uuid = function () {
156 var uuid = function () {
157 // http://www.ietf.org/rfc/rfc4122.txt
157 // http://www.ietf.org/rfc/rfc4122.txt
158 var s = [];
158 var s = [];
159 var hexDigits = "0123456789ABCDEF";
159 var hexDigits = "0123456789ABCDEF";
160 for (var i = 0; i < 32; i++) {
160 for (var i = 0; i < 32; i++) {
161 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
161 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
162 }
162 }
163 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
163 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
164 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
164 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
165
165
166 var uuid = s.join("");
166 var uuid = s.join("");
167 return uuid;
167 return uuid;
168 };
168 };
169
169
170
170
171 //Fix raw text to parse correctly in crazy XML
171 //Fix raw text to parse correctly in crazy XML
172 function xmlencode(string) {
172 function xmlencode(string) {
173 return string.replace(/\&/g,'&'+'amp;')
173 return string.replace(/\&/g,'&'+'amp;')
174 .replace(/</g,'&'+'lt;')
174 .replace(/</g,'&'+'lt;')
175 .replace(/>/g,'&'+'gt;')
175 .replace(/>/g,'&'+'gt;')
176 .replace(/\'/g,'&'+'apos;')
176 .replace(/\'/g,'&'+'apos;')
177 .replace(/\"/g,'&'+'quot;')
177 .replace(/\"/g,'&'+'quot;')
178 .replace(/`/g,'&'+'#96;');
178 .replace(/`/g,'&'+'#96;');
179 }
179 }
180
180
181
181
182 //Map from terminal commands to CSS classes
182 //Map from terminal commands to CSS classes
183 var ansi_colormap = {
183 var ansi_colormap = {
184 "01":"ansibold",
184 "01":"ansibold",
185
185
186 "30":"ansiblack",
186 "30":"ansiblack",
187 "31":"ansired",
187 "31":"ansired",
188 "32":"ansigreen",
188 "32":"ansigreen",
189 "33":"ansiyellow",
189 "33":"ansiyellow",
190 "34":"ansiblue",
190 "34":"ansiblue",
191 "35":"ansipurple",
191 "35":"ansipurple",
192 "36":"ansicyan",
192 "36":"ansicyan",
193 "37":"ansigray",
193 "37":"ansigray",
194
194
195 "40":"ansibgblack",
195 "40":"ansibgblack",
196 "41":"ansibgred",
196 "41":"ansibgred",
197 "42":"ansibggreen",
197 "42":"ansibggreen",
198 "43":"ansibgyellow",
198 "43":"ansibgyellow",
199 "44":"ansibgblue",
199 "44":"ansibgblue",
200 "45":"ansibgpurple",
200 "45":"ansibgpurple",
201 "46":"ansibgcyan",
201 "46":"ansibgcyan",
202 "47":"ansibggray"
202 "47":"ansibggray"
203 };
203 };
204
204
205 function _process_numbers(attrs, numbers) {
205 function _process_numbers(attrs, numbers) {
206 // process ansi escapes
206 // process ansi escapes
207 var n = numbers.shift();
207 var n = numbers.shift();
208 if (ansi_colormap[n]) {
208 if (ansi_colormap[n]) {
209 if ( ! attrs["class"] ) {
209 if ( ! attrs["class"] ) {
210 attrs["class"] = ansi_colormap[n];
210 attrs["class"] = ansi_colormap[n];
211 } else {
211 } else {
212 attrs["class"] += " " + ansi_colormap[n];
212 attrs["class"] += " " + ansi_colormap[n];
213 }
213 }
214 } else if (n == "38" || n == "48") {
214 } else if (n == "38" || n == "48") {
215 // VT100 256 color or 24 bit RGB
215 // VT100 256 color or 24 bit RGB
216 if (numbers.length < 2) {
216 if (numbers.length < 2) {
217 console.log("Not enough fields for VT100 color", numbers);
217 console.log("Not enough fields for VT100 color", numbers);
218 return;
218 return;
219 }
219 }
220
220
221 var index_or_rgb = numbers.shift();
221 var index_or_rgb = numbers.shift();
222 var r,g,b;
222 var r,g,b;
223 if (index_or_rgb == "5") {
223 if (index_or_rgb == "5") {
224 // 256 color
224 // 256 color
225 var idx = parseInt(numbers.shift());
225 var idx = parseInt(numbers.shift());
226 if (idx < 16) {
226 if (idx < 16) {
227 // indexed ANSI
227 // indexed ANSI
228 // ignore bright / non-bright distinction
228 // ignore bright / non-bright distinction
229 idx = idx % 8;
229 idx = idx % 8;
230 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
230 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
231 if ( ! attrs["class"] ) {
231 if ( ! attrs["class"] ) {
232 attrs["class"] = ansiclass;
232 attrs["class"] = ansiclass;
233 } else {
233 } else {
234 attrs["class"] += " " + ansiclass;
234 attrs["class"] += " " + ansiclass;
235 }
235 }
236 return;
236 return;
237 } else if (idx < 232) {
237 } else if (idx < 232) {
238 // 216 color 6x6x6 RGB
238 // 216 color 6x6x6 RGB
239 idx = idx - 16;
239 idx = idx - 16;
240 b = idx % 6;
240 b = idx % 6;
241 g = Math.floor(idx / 6) % 6;
241 g = Math.floor(idx / 6) % 6;
242 r = Math.floor(idx / 36) % 6;
242 r = Math.floor(idx / 36) % 6;
243 // convert to rgb
243 // convert to rgb
244 r = (r * 51);
244 r = (r * 51);
245 g = (g * 51);
245 g = (g * 51);
246 b = (b * 51);
246 b = (b * 51);
247 } else {
247 } else {
248 // grayscale
248 // grayscale
249 idx = idx - 231;
249 idx = idx - 231;
250 // it's 1-24 and should *not* include black or white,
250 // it's 1-24 and should *not* include black or white,
251 // so a 26 point scale
251 // so a 26 point scale
252 r = g = b = Math.floor(idx * 256 / 26);
252 r = g = b = Math.floor(idx * 256 / 26);
253 }
253 }
254 } else if (index_or_rgb == "2") {
254 } else if (index_or_rgb == "2") {
255 // Simple 24 bit RGB
255 // Simple 24 bit RGB
256 if (numbers.length > 3) {
256 if (numbers.length > 3) {
257 console.log("Not enough fields for RGB", numbers);
257 console.log("Not enough fields for RGB", numbers);
258 return;
258 return;
259 }
259 }
260 r = numbers.shift();
260 r = numbers.shift();
261 g = numbers.shift();
261 g = numbers.shift();
262 b = numbers.shift();
262 b = numbers.shift();
263 } else {
263 } else {
264 console.log("unrecognized control", numbers);
264 console.log("unrecognized control", numbers);
265 return;
265 return;
266 }
266 }
267 if (r !== undefined) {
267 if (r !== undefined) {
268 // apply the rgb color
268 // apply the rgb color
269 var line;
269 var line;
270 if (n == "38") {
270 if (n == "38") {
271 line = "color: ";
271 line = "color: ";
272 } else {
272 } else {
273 line = "background-color: ";
273 line = "background-color: ";
274 }
274 }
275 line = line + "rgb(" + r + "," + g + "," + b + ");";
275 line = line + "rgb(" + r + "," + g + "," + b + ");";
276 if ( !attrs.style ) {
276 if ( !attrs.style ) {
277 attrs.style = line;
277 attrs.style = line;
278 } else {
278 } else {
279 attrs.style += " " + line;
279 attrs.style += " " + line;
280 }
280 }
281 }
281 }
282 }
282 }
283 }
283 }
284
284
285 function ansispan(str) {
285 function ansispan(str) {
286 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
286 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
287 // regular ansi escapes (using the table above)
287 // regular ansi escapes (using the table above)
288 var is_open = false;
288 var is_open = false;
289 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
289 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
290 if (!pattern) {
290 if (!pattern) {
291 // [(01|22|39|)m close spans
291 // [(01|22|39|)m close spans
292 if (is_open) {
292 if (is_open) {
293 is_open = false;
293 is_open = false;
294 return "</span>";
294 return "</span>";
295 } else {
295 } else {
296 return "";
296 return "";
297 }
297 }
298 } else {
298 } else {
299 is_open = true;
299 is_open = true;
300
300
301 // consume sequence of color escapes
301 // consume sequence of color escapes
302 var numbers = pattern.match(/\d+/g);
302 var numbers = pattern.match(/\d+/g);
303 var attrs = {};
303 var attrs = {};
304 while (numbers.length > 0) {
304 while (numbers.length > 0) {
305 _process_numbers(attrs, numbers);
305 _process_numbers(attrs, numbers);
306 }
306 }
307
307
308 var span = "<span ";
308 var span = "<span ";
309 for (var attr in attrs) {
309 for (var attr in attrs) {
310 var value = attrs[attr];
310 var value = attrs[attr];
311 span = span + " " + attr + '="' + attrs[attr] + '"';
311 span = span + " " + attr + '="' + attrs[attr] + '"';
312 }
312 }
313 return span + ">";
313 return span + ">";
314 }
314 }
315 });
315 });
316 }
316 }
317
317
318 // Transform ANSI color escape codes into HTML <span> tags with css
318 // Transform ANSI color escape codes into HTML <span> tags with css
319 // classes listed in the above ansi_colormap object. The actual color used
319 // classes listed in the above ansi_colormap object. The actual color used
320 // are set in the css file.
320 // are set in the css file.
321 function fixConsole(txt) {
321 function fixConsole(txt) {
322 txt = xmlencode(txt);
322 txt = xmlencode(txt);
323 var re = /\033\[([\dA-Fa-f;]*?)m/;
323 var re = /\033\[([\dA-Fa-f;]*?)m/;
324 var opened = false;
324 var opened = false;
325 var cmds = [];
325 var cmds = [];
326 var opener = "";
326 var opener = "";
327 var closer = "";
327 var closer = "";
328
328
329 // Strip all ANSI codes that are not color related. Matches
329 // Strip all ANSI codes that are not color related. Matches
330 // all ANSI codes that do not end with "m".
330 // all ANSI codes that do not end with "m".
331 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
331 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
332 txt = txt.replace(ignored_re, "");
332 txt = txt.replace(ignored_re, "");
333
333
334 // color ansi codes
334 // color ansi codes
335 txt = ansispan(txt);
335 txt = ansispan(txt);
336 return txt;
336 return txt;
337 }
337 }
338
338
339 // Remove chunks that should be overridden by the effect of
339 // Remove chunks that should be overridden by the effect of
340 // carriage return characters
340 // carriage return characters
341 function fixCarriageReturn(txt) {
341 function fixCarriageReturn(txt) {
342 var tmp = txt;
342 var tmp = txt;
343 do {
343 do {
344 txt = tmp;
344 txt = tmp;
345 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
345 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
346 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
346 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
347 } while (tmp.length < txt.length);
347 } while (tmp.length < txt.length);
348 return txt;
348 return txt;
349 }
349 }
350
350
351 // Locate any URLs and convert them to a anchor tag
351 // Locate any URLs and convert them to a anchor tag
352 function autoLinkUrls(txt) {
352 function autoLinkUrls(txt) {
353 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
353 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
354 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
354 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
355 }
355 }
356
356
357 var points_to_pixels = function (points) {
357 var points_to_pixels = function (points) {
358 // A reasonably good way of converting between points and pixels.
358 // A reasonably good way of converting between points and pixels.
359 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
359 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
360 $(body).append(test);
360 $(body).append(test);
361 var pixel_per_point = test.width()/10000;
361 var pixel_per_point = test.width()/10000;
362 test.remove();
362 test.remove();
363 return Math.floor(points*pixel_per_point);
363 return Math.floor(points*pixel_per_point);
364 };
364 };
365
365
366 var always_new = function (constructor) {
366 var always_new = function (constructor) {
367 // wrapper around contructor to avoid requiring `var a = new constructor()`
367 // wrapper around contructor to avoid requiring `var a = new constructor()`
368 // useful for passing constructors as callbacks,
368 // useful for passing constructors as callbacks,
369 // not for programmer laziness.
369 // not for programmer laziness.
370 // from http://programmers.stackexchange.com/questions/118798
370 // from http://programmers.stackexchange.com/questions/118798
371 return function () {
371 return function () {
372 var obj = Object.create(constructor.prototype);
372 var obj = Object.create(constructor.prototype);
373 constructor.apply(obj, arguments);
373 constructor.apply(obj, arguments);
374 return obj;
374 return obj;
375 };
375 };
376 };
376 };
377
377
378 var url_path_join = function () {
378 var url_path_join = function () {
379 // join a sequence of url components with '/'
379 // join a sequence of url components with '/'
380 var url = '';
380 var url = '';
381 for (var i = 0; i < arguments.length; i++) {
381 for (var i = 0; i < arguments.length; i++) {
382 if (arguments[i] === '') {
382 if (arguments[i] === '') {
383 continue;
383 continue;
384 }
384 }
385 if (url.length > 0 && url[url.length-1] != '/') {
385 if (url.length > 0 && url[url.length-1] != '/') {
386 url = url + '/' + arguments[i];
386 url = url + '/' + arguments[i];
387 } else {
387 } else {
388 url = url + arguments[i];
388 url = url + arguments[i];
389 }
389 }
390 }
390 }
391 url = url.replace(/\/\/+/, '/');
391 url = url.replace(/\/\/+/, '/');
392 return url;
392 return url;
393 };
393 };
394
394
395 var url_path_split = function (path) {
395 var url_path_split = function (path) {
396 // Like os.path.split for URLs.
396 // Like os.path.split for URLs.
397 // Always returns two strings, the directory path and the base filename
397 // Always returns two strings, the directory path and the base filename
398
398
399 var idx = path.lastIndexOf('/');
399 var idx = path.lastIndexOf('/');
400 if (idx === -1) {
400 if (idx === -1) {
401 return ['', path];
401 return ['', path];
402 } else {
402 } else {
403 return [ path.slice(0, idx), path.slice(idx + 1) ];
403 return [ path.slice(0, idx), path.slice(idx + 1) ];
404 }
404 }
405 };
405 };
406
406
407 var parse_url = function (url) {
407 var parse_url = function (url) {
408 // an `a` element with an href allows attr-access to the parsed segments of a URL
408 // an `a` element with an href allows attr-access to the parsed segments of a URL
409 // a = parse_url("http://localhost:8888/path/name#hash")
409 // a = parse_url("http://localhost:8888/path/name#hash")
410 // a.protocol = "http:"
410 // a.protocol = "http:"
411 // a.host = "localhost:8888"
411 // a.host = "localhost:8888"
412 // a.hostname = "localhost"
412 // a.hostname = "localhost"
413 // a.port = 8888
413 // a.port = 8888
414 // a.pathname = "/path/name"
414 // a.pathname = "/path/name"
415 // a.hash = "#hash"
415 // a.hash = "#hash"
416 var a = document.createElement("a");
416 var a = document.createElement("a");
417 a.href = url;
417 a.href = url;
418 return a;
418 return a;
419 };
419 };
420
420
421 var encode_uri_components = function (uri) {
421 var encode_uri_components = function (uri) {
422 // encode just the components of a multi-segment uri,
422 // encode just the components of a multi-segment uri,
423 // leaving '/' separators
423 // leaving '/' separators
424 return uri.split('/').map(encodeURIComponent).join('/');
424 return uri.split('/').map(encodeURIComponent).join('/');
425 };
425 };
426
426
427 var url_join_encode = function () {
427 var url_join_encode = function () {
428 // join a sequence of url components with '/',
428 // join a sequence of url components with '/',
429 // encoding each component with encodeURIComponent
429 // encoding each component with encodeURIComponent
430 return encode_uri_components(url_path_join.apply(null, arguments));
430 return encode_uri_components(url_path_join.apply(null, arguments));
431 };
431 };
432
432
433
433
434 var splitext = function (filename) {
434 var splitext = function (filename) {
435 // mimic Python os.path.splitext
435 // mimic Python os.path.splitext
436 // Returns ['base', '.ext']
436 // Returns ['base', '.ext']
437 var idx = filename.lastIndexOf('.');
437 var idx = filename.lastIndexOf('.');
438 if (idx > 0) {
438 if (idx > 0) {
439 return [filename.slice(0, idx), filename.slice(idx)];
439 return [filename.slice(0, idx), filename.slice(idx)];
440 } else {
440 } else {
441 return [filename, ''];
441 return [filename, ''];
442 }
442 }
443 };
443 };
444
444
445
445
446 var escape_html = function (text) {
446 var escape_html = function (text) {
447 // escape text to HTML
447 // escape text to HTML
448 return $("<div/>").text(text).html();
448 return $("<div/>").text(text).html();
449 };
449 };
450
450
451
451
452 var get_body_data = function(key) {
452 var get_body_data = function(key) {
453 // get a url-encoded item from body.data and decode it
453 // get a url-encoded item from body.data and decode it
454 // we should never have any encoded URLs anywhere else in code
454 // we should never have any encoded URLs anywhere else in code
455 // until we are building an actual request
455 // until we are building an actual request
456 return decodeURIComponent($('body').data(key));
456 return decodeURIComponent($('body').data(key));
457 };
457 };
458
458
459 var to_absolute_cursor_pos = function (cm, cursor) {
459 var to_absolute_cursor_pos = function (cm, cursor) {
460 // get the absolute cursor position from CodeMirror's col, ch
460 // get the absolute cursor position from CodeMirror's col, ch
461 if (!cursor) {
461 if (!cursor) {
462 cursor = cm.getCursor();
462 cursor = cm.getCursor();
463 }
463 }
464 var cursor_pos = cursor.ch;
464 var cursor_pos = cursor.ch;
465 for (var i = 0; i < cursor.line; i++) {
465 for (var i = 0; i < cursor.line; i++) {
466 cursor_pos += cm.getLine(i).length + 1;
466 cursor_pos += cm.getLine(i).length + 1;
467 }
467 }
468 return cursor_pos;
468 return cursor_pos;
469 };
469 };
470
470
471 var from_absolute_cursor_pos = function (cm, cursor_pos) {
471 var from_absolute_cursor_pos = function (cm, cursor_pos) {
472 // turn absolute cursor postion into CodeMirror col, ch cursor
472 // turn absolute cursor postion into CodeMirror col, ch cursor
473 var i, line;
473 var i, line;
474 var offset = 0;
474 var offset = 0;
475 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
475 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
476 if (offset + line.length < cursor_pos) {
476 if (offset + line.length < cursor_pos) {
477 offset += line.length + 1;
477 offset += line.length + 1;
478 } else {
478 } else {
479 return {
479 return {
480 line : i,
480 line : i,
481 ch : cursor_pos - offset,
481 ch : cursor_pos - offset,
482 };
482 };
483 }
483 }
484 }
484 }
485 // reached end, return endpoint
485 // reached end, return endpoint
486 return {
486 return {
487 ch : line.length - 1,
487 ch : line.length - 1,
488 line : i - 1,
488 line : i - 1,
489 };
489 };
490 };
490 };
491
491
492 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
492 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
493 var browser = (function() {
493 var browser = (function() {
494 if (typeof navigator === 'undefined') {
494 if (typeof navigator === 'undefined') {
495 // navigator undefined in node
495 // navigator undefined in node
496 return 'None';
496 return 'None';
497 }
497 }
498 var N= navigator.appName, ua= navigator.userAgent, tem;
498 var N= navigator.appName, ua= navigator.userAgent, tem;
499 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
499 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
500 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
500 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
501 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
501 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
502 return M;
502 return M;
503 })();
503 })();
504
504
505 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
505 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
506 var platform = (function () {
506 var platform = (function () {
507 if (typeof navigator === 'undefined') {
507 if (typeof navigator === 'undefined') {
508 // navigator undefined in node
508 // navigator undefined in node
509 return 'None';
509 return 'None';
510 }
510 }
511 var OSName="None";
511 var OSName="None";
512 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
512 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
513 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
513 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
514 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
514 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
515 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
515 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
516 return OSName;
516 return OSName;
517 })();
517 })();
518
518
519 var is_or_has = function (a, b) {
519 var is_or_has = function (a, b) {
520 // Is b a child of a or a itself?
520 // Is b a child of a or a itself?
521 return a.has(b).length !==0 || a.is(b);
521 return a.has(b).length !==0 || a.is(b);
522 };
522 };
523
523
524 var is_focused = function (e) {
524 var is_focused = function (e) {
525 // Is element e, or one of its children focused?
525 // Is element e, or one of its children focused?
526 e = $(e);
526 e = $(e);
527 var target = $(document.activeElement);
527 var target = $(document.activeElement);
528 if (target.length > 0) {
528 if (target.length > 0) {
529 if (is_or_has(e, target)) {
529 if (is_or_has(e, target)) {
530 return true;
530 return true;
531 } else {
531 } else {
532 return false;
532 return false;
533 }
533 }
534 } else {
534 } else {
535 return false;
535 return false;
536 }
536 }
537 };
537 };
538
538
539 var mergeopt = function(_class, options, overwrite){
539 var mergeopt = function(_class, options, overwrite){
540 options = options || {};
540 options = options || {};
541 overwrite = overwrite || {};
541 overwrite = overwrite || {};
542 return $.extend(true, {}, _class.options_default, options, overwrite);
542 return $.extend(true, {}, _class.options_default, options, overwrite);
543 };
543 };
544
544
545 var ajax_error_msg = function (jqXHR) {
545 var ajax_error_msg = function (jqXHR) {
546 // Return a JSON error message if there is one,
546 // Return a JSON error message if there is one,
547 // otherwise the basic HTTP status text.
547 // otherwise the basic HTTP status text.
548 if (jqXHR.responseJSON && jqXHR.responseJSON.traceback) {
548 if (jqXHR.responseJSON && jqXHR.responseJSON.traceback) {
549 return jqXHR.responseJSON.traceback;
549 return jqXHR.responseJSON.traceback;
550 } else if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
550 } else if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
551 return jqXHR.responseJSON.message;
551 return jqXHR.responseJSON.message;
552 } else {
552 } else {
553 return jqXHR.statusText;
553 return jqXHR.statusText;
554 }
554 }
555 };
555 };
556 var log_ajax_error = function (jqXHR, status, error) {
556 var log_ajax_error = function (jqXHR, status, error) {
557 // log ajax failures with informative messages
557 // log ajax failures with informative messages
558 var msg = "API request failed (" + jqXHR.status + "): ";
558 var msg = "API request failed (" + jqXHR.status + "): ";
559 console.log(jqXHR);
559 console.log(jqXHR);
560 msg += ajax_error_msg(jqXHR);
560 msg += ajax_error_msg(jqXHR);
561 console.log(msg);
561 console.log(msg);
562 };
562 };
563
563
564 var requireCodeMirrorMode = function (mode, callback, errback) {
564 var requireCodeMirrorMode = function (mode, callback, errback) {
565 // load a mode with requirejs
565 // load a mode with requirejs
566 if (typeof mode != "string") mode = mode.name;
566 if (typeof mode != "string") mode = mode.name;
567 if (CodeMirror.modes.hasOwnProperty(mode)) {
567 if (CodeMirror.modes.hasOwnProperty(mode)) {
568 callback(CodeMirror.modes.mode);
568 callback(CodeMirror.modes.mode);
569 return;
569 return;
570 }
570 }
571 require([
571 require([
572 // might want to use CodeMirror.modeURL here
572 // might want to use CodeMirror.modeURL here
573 ['codemirror/mode', mode, mode].join('/'),
573 ['codemirror/mode', mode, mode].join('/'),
574 ], callback, errback
574 ], callback, errback
575 );
575 );
576 };
576 };
577
577
578 /** Error type for wrapped XHR errors. */
578 /** Error type for wrapped XHR errors. */
579 var XHR_ERROR = 'XhrError';
579 var XHR_ERROR = 'XhrError';
580
580
581 /**
581 /**
582 * Wraps an AJAX error as an Error object.
582 * Wraps an AJAX error as an Error object.
583 */
583 */
584 var wrap_ajax_error = function (jqXHR, status, error) {
584 var wrap_ajax_error = function (jqXHR, status, error) {
585 var wrapped_error = new Error(ajax_error_msg(jqXHR));
585 var wrapped_error = new Error(ajax_error_msg(jqXHR));
586 wrapped_error.name = XHR_ERROR;
586 wrapped_error.name = XHR_ERROR;
587 // provide xhr response
587 // provide xhr response
588 wrapped_error.xhr = jqXHR;
588 wrapped_error.xhr = jqXHR;
589 wrapped_error.xhr_status = status;
589 wrapped_error.xhr_status = status;
590 wrapped_error.xhr_error = error;
590 wrapped_error.xhr_error = error;
591 return wrapped_error;
591 return wrapped_error;
592 };
592 };
593
593
594 var promising_ajax = function(url, settings) {
595 // Like $.ajax, but returning an ES6 promise. success and error settings
596 // will be ignored.
597 return new Promise(function(resolve, reject) {
598 settings.success = function(data, status, jqXHR) {
599 resolve(data);
600 };
601 settings.error = function(jqXHR, status, error) {
602 log_ajax_error(jqXHR, status, error);
603 reject(wrap_ajax_error(jqXHR, status, error));
604 };
605 $.ajax(url, settings);
606 });
607 };
608
594 var utils = {
609 var utils = {
595 regex_split : regex_split,
610 regex_split : regex_split,
596 uuid : uuid,
611 uuid : uuid,
597 fixConsole : fixConsole,
612 fixConsole : fixConsole,
598 fixCarriageReturn : fixCarriageReturn,
613 fixCarriageReturn : fixCarriageReturn,
599 autoLinkUrls : autoLinkUrls,
614 autoLinkUrls : autoLinkUrls,
600 points_to_pixels : points_to_pixels,
615 points_to_pixels : points_to_pixels,
601 get_body_data : get_body_data,
616 get_body_data : get_body_data,
602 parse_url : parse_url,
617 parse_url : parse_url,
603 url_path_split : url_path_split,
618 url_path_split : url_path_split,
604 url_path_join : url_path_join,
619 url_path_join : url_path_join,
605 url_join_encode : url_join_encode,
620 url_join_encode : url_join_encode,
606 encode_uri_components : encode_uri_components,
621 encode_uri_components : encode_uri_components,
607 splitext : splitext,
622 splitext : splitext,
608 escape_html : escape_html,
623 escape_html : escape_html,
609 always_new : always_new,
624 always_new : always_new,
610 to_absolute_cursor_pos : to_absolute_cursor_pos,
625 to_absolute_cursor_pos : to_absolute_cursor_pos,
611 from_absolute_cursor_pos : from_absolute_cursor_pos,
626 from_absolute_cursor_pos : from_absolute_cursor_pos,
612 browser : browser,
627 browser : browser,
613 platform: platform,
628 platform: platform,
614 is_or_has : is_or_has,
629 is_or_has : is_or_has,
615 is_focused : is_focused,
630 is_focused : is_focused,
616 mergeopt: mergeopt,
631 mergeopt: mergeopt,
617 ajax_error_msg : ajax_error_msg,
632 ajax_error_msg : ajax_error_msg,
618 log_ajax_error : log_ajax_error,
633 log_ajax_error : log_ajax_error,
619 requireCodeMirrorMode : requireCodeMirrorMode,
634 requireCodeMirrorMode : requireCodeMirrorMode,
620 XHR_ERROR : XHR_ERROR,
635 XHR_ERROR : XHR_ERROR,
621 wrap_ajax_error : wrap_ajax_error
636 wrap_ajax_error : wrap_ajax_error,
637 promising_ajax : promising_ajax,
622 };
638 };
623
639
624 // Backwards compatability.
640 // Backwards compatability.
625 IPython.utils = utils;
641 IPython.utils = utils;
626
642
627 return utils;
643 return utils;
628 });
644 });
@@ -1,1 +1,1 b''
1 Subproject commit f8f0c3b8958b1f91a888b5f15d2b17404a476016
1 Subproject commit ba94581b824a62ee630dd0b92a5aea8678248a24
@@ -1,349 +1,348 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'base/js/namespace',
6 'base/js/namespace',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/utils',
8 'base/js/utils',
9 'notebook/js/tour',
9 'notebook/js/tour',
10 'bootstrap',
10 'bootstrap',
11 'moment',
11 'moment',
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
13 "use strict";
13 "use strict";
14
14
15 var MenuBar = function (selector, options) {
15 var MenuBar = function (selector, options) {
16 // Constructor
16 // Constructor
17 //
17 //
18 // A MenuBar Class to generate the menubar of IPython notebook
18 // A MenuBar Class to generate the menubar of IPython notebook
19 //
19 //
20 // Parameters:
20 // Parameters:
21 // selector: string
21 // selector: string
22 // options: dictionary
22 // options: dictionary
23 // Dictionary of keyword arguments.
23 // Dictionary of keyword arguments.
24 // notebook: Notebook instance
24 // notebook: Notebook instance
25 // contents: ContentManager instance
25 // contents: ContentManager instance
26 // layout_manager: LayoutManager instance
26 // layout_manager: LayoutManager instance
27 // events: $(Events) instance
27 // events: $(Events) instance
28 // save_widget: SaveWidget instance
28 // save_widget: SaveWidget instance
29 // quick_help: QuickHelp instance
29 // quick_help: QuickHelp instance
30 // base_url : string
30 // base_url : string
31 // notebook_path : string
31 // notebook_path : string
32 // notebook_name : string
32 // notebook_name : string
33 options = options || {};
33 options = options || {};
34 this.base_url = options.base_url || utils.get_body_data("baseUrl");
34 this.base_url = options.base_url || utils.get_body_data("baseUrl");
35 this.selector = selector;
35 this.selector = selector;
36 this.notebook = options.notebook;
36 this.notebook = options.notebook;
37 this.contents = options.contents;
37 this.contents = options.contents;
38 this.layout_manager = options.layout_manager;
38 this.layout_manager = options.layout_manager;
39 this.events = options.events;
39 this.events = options.events;
40 this.save_widget = options.save_widget;
40 this.save_widget = options.save_widget;
41 this.quick_help = options.quick_help;
41 this.quick_help = options.quick_help;
42
42
43 try {
43 try {
44 this.tour = new tour.Tour(this.notebook, this.events);
44 this.tour = new tour.Tour(this.notebook, this.events);
45 } catch (e) {
45 } catch (e) {
46 this.tour = undefined;
46 this.tour = undefined;
47 console.log("Failed to instantiate Notebook Tour", e);
47 console.log("Failed to instantiate Notebook Tour", e);
48 }
48 }
49
49
50 if (this.selector !== undefined) {
50 if (this.selector !== undefined) {
51 this.element = $(selector);
51 this.element = $(selector);
52 this.style();
52 this.style();
53 this.bind_events();
53 this.bind_events();
54 }
54 }
55 };
55 };
56
56
57 // TODO: This has definitively nothing to do with style ...
57 // TODO: This has definitively nothing to do with style ...
58 MenuBar.prototype.style = function () {
58 MenuBar.prototype.style = function () {
59 var that = this;
59 var that = this;
60 this.element.find("li").click(function (event, ui) {
60 this.element.find("li").click(function (event, ui) {
61 // The selected cell loses focus when the menu is entered, so we
61 // The selected cell loses focus when the menu is entered, so we
62 // re-select it upon selection.
62 // re-select it upon selection.
63 var i = that.notebook.get_selected_index();
63 var i = that.notebook.get_selected_index();
64 that.notebook.select(i);
64 that.notebook.select(i);
65 }
65 }
66 );
66 );
67 };
67 };
68
68
69 MenuBar.prototype._nbconvert = function (format, download) {
69 MenuBar.prototype._nbconvert = function (format, download) {
70 download = download || false;
70 download = download || false;
71 var notebook_path = this.notebook.notebook_path;
71 var notebook_path = this.notebook.notebook_path;
72 if (this.notebook.dirty) {
72 if (this.notebook.dirty) {
73 this.notebook.save_notebook({async : false});
73 this.notebook.save_notebook({async : false});
74 }
74 }
75 var url = utils.url_join_encode(
75 var url = utils.url_join_encode(
76 this.base_url,
76 this.base_url,
77 'nbconvert',
77 'nbconvert',
78 format,
78 format,
79 notebook_path
79 notebook_path
80 ) + "?download=" + download.toString();
80 ) + "?download=" + download.toString();
81
81
82 window.open(url);
82 window.open(url);
83 };
83 };
84
84
85 MenuBar.prototype.bind_events = function () {
85 MenuBar.prototype.bind_events = function () {
86 // File
86 // File
87 var that = this;
87 var that = this;
88 this.element.find('#new_notebook').click(function () {
88 this.element.find('#new_notebook').click(function () {
89 var w = window.open();
89 var w = window.open();
90 // Create a new notebook in the same path as the current
90 // Create a new notebook in the same path as the current
91 // notebook's path.
91 // notebook's path.
92 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
92 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
93 that.contents.new_untitled(parent, {
93 that.contents.new_untitled(parent, {type: "notebook"}).then(
94 type: "notebook",
94 function (data) {
95 success: function (data) {
96 w.location = utils.url_join_encode(
95 w.location = utils.url_join_encode(
97 that.base_url, 'notebooks', data.path
96 that.base_url, 'notebooks', data.path
98 );
97 );
99 },
98 },
100 error: function(error) {
99 function(error) {
101 w.close();
100 w.close();
102 dialog.modal({
101 dialog.modal({
103 title : 'Creating Notebook Failed',
102 title : 'Creating Notebook Failed',
104 body : "The error was: " + error.message,
103 body : "The error was: " + error.message,
105 buttons : {'OK' : {'class' : 'btn-primary'}}
104 buttons : {'OK' : {'class' : 'btn-primary'}}
106 });
105 });
107 }
106 }
108 });
107 );
109 });
108 });
110 this.element.find('#open_notebook').click(function () {
109 this.element.find('#open_notebook').click(function () {
111 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
110 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
112 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
111 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
113 });
112 });
114 this.element.find('#copy_notebook').click(function () {
113 this.element.find('#copy_notebook').click(function () {
115 that.notebook.copy_notebook();
114 that.notebook.copy_notebook();
116 return false;
115 return false;
117 });
116 });
118 this.element.find('#download_ipynb').click(function () {
117 this.element.find('#download_ipynb').click(function () {
119 var base_url = that.notebook.base_url;
118 var base_url = that.notebook.base_url;
120 var notebook_path = that.notebook.notebook_path;
119 var notebook_path = that.notebook.notebook_path;
121 if (that.notebook.dirty) {
120 if (that.notebook.dirty) {
122 that.notebook.save_notebook({async : false});
121 that.notebook.save_notebook({async : false});
123 }
122 }
124
123
125 var url = utils.url_join_encode(base_url, 'files', notebook_path);
124 var url = utils.url_join_encode(base_url, 'files', notebook_path);
126 window.open(url + '?download=1');
125 window.open(url + '?download=1');
127 });
126 });
128
127
129 this.element.find('#print_preview').click(function () {
128 this.element.find('#print_preview').click(function () {
130 that._nbconvert('html', false);
129 that._nbconvert('html', false);
131 });
130 });
132
131
133 this.element.find('#download_py').click(function () {
132 this.element.find('#download_py').click(function () {
134 that._nbconvert('python', true);
133 that._nbconvert('python', true);
135 });
134 });
136
135
137 this.element.find('#download_html').click(function () {
136 this.element.find('#download_html').click(function () {
138 that._nbconvert('html', true);
137 that._nbconvert('html', true);
139 });
138 });
140
139
141 this.element.find('#download_rst').click(function () {
140 this.element.find('#download_rst').click(function () {
142 that._nbconvert('rst', true);
141 that._nbconvert('rst', true);
143 });
142 });
144
143
145 this.element.find('#download_pdf').click(function () {
144 this.element.find('#download_pdf').click(function () {
146 that._nbconvert('pdf', true);
145 that._nbconvert('pdf', true);
147 });
146 });
148
147
149 this.element.find('#rename_notebook').click(function () {
148 this.element.find('#rename_notebook').click(function () {
150 that.save_widget.rename_notebook({notebook: that.notebook});
149 that.save_widget.rename_notebook({notebook: that.notebook});
151 });
150 });
152 this.element.find('#save_checkpoint').click(function () {
151 this.element.find('#save_checkpoint').click(function () {
153 that.notebook.save_checkpoint();
152 that.notebook.save_checkpoint();
154 });
153 });
155 this.element.find('#restore_checkpoint').click(function () {
154 this.element.find('#restore_checkpoint').click(function () {
156 });
155 });
157 this.element.find('#trust_notebook').click(function () {
156 this.element.find('#trust_notebook').click(function () {
158 that.notebook.trust_notebook();
157 that.notebook.trust_notebook();
159 });
158 });
160 this.events.on('trust_changed.Notebook', function (event, trusted) {
159 this.events.on('trust_changed.Notebook', function (event, trusted) {
161 if (trusted) {
160 if (trusted) {
162 that.element.find('#trust_notebook')
161 that.element.find('#trust_notebook')
163 .addClass("disabled")
162 .addClass("disabled")
164 .find("a").text("Trusted Notebook");
163 .find("a").text("Trusted Notebook");
165 } else {
164 } else {
166 that.element.find('#trust_notebook')
165 that.element.find('#trust_notebook')
167 .removeClass("disabled")
166 .removeClass("disabled")
168 .find("a").text("Trust Notebook");
167 .find("a").text("Trust Notebook");
169 }
168 }
170 });
169 });
171 this.element.find('#kill_and_exit').click(function () {
170 this.element.find('#kill_and_exit').click(function () {
172 var close_window = function () {
171 var close_window = function () {
173 // allow closing of new tabs in Chromium, impossible in FF
172 // allow closing of new tabs in Chromium, impossible in FF
174 window.open('', '_self', '');
173 window.open('', '_self', '');
175 window.close();
174 window.close();
176 };
175 };
177 // finish with close on success or failure
176 // finish with close on success or failure
178 that.notebook.session.delete(close_window, close_window);
177 that.notebook.session.delete(close_window, close_window);
179 });
178 });
180 // Edit
179 // Edit
181 this.element.find('#cut_cell').click(function () {
180 this.element.find('#cut_cell').click(function () {
182 that.notebook.cut_cell();
181 that.notebook.cut_cell();
183 });
182 });
184 this.element.find('#copy_cell').click(function () {
183 this.element.find('#copy_cell').click(function () {
185 that.notebook.copy_cell();
184 that.notebook.copy_cell();
186 });
185 });
187 this.element.find('#delete_cell').click(function () {
186 this.element.find('#delete_cell').click(function () {
188 that.notebook.delete_cell();
187 that.notebook.delete_cell();
189 });
188 });
190 this.element.find('#undelete_cell').click(function () {
189 this.element.find('#undelete_cell').click(function () {
191 that.notebook.undelete_cell();
190 that.notebook.undelete_cell();
192 });
191 });
193 this.element.find('#split_cell').click(function () {
192 this.element.find('#split_cell').click(function () {
194 that.notebook.split_cell();
193 that.notebook.split_cell();
195 });
194 });
196 this.element.find('#merge_cell_above').click(function () {
195 this.element.find('#merge_cell_above').click(function () {
197 that.notebook.merge_cell_above();
196 that.notebook.merge_cell_above();
198 });
197 });
199 this.element.find('#merge_cell_below').click(function () {
198 this.element.find('#merge_cell_below').click(function () {
200 that.notebook.merge_cell_below();
199 that.notebook.merge_cell_below();
201 });
200 });
202 this.element.find('#move_cell_up').click(function () {
201 this.element.find('#move_cell_up').click(function () {
203 that.notebook.move_cell_up();
202 that.notebook.move_cell_up();
204 });
203 });
205 this.element.find('#move_cell_down').click(function () {
204 this.element.find('#move_cell_down').click(function () {
206 that.notebook.move_cell_down();
205 that.notebook.move_cell_down();
207 });
206 });
208 this.element.find('#edit_nb_metadata').click(function () {
207 this.element.find('#edit_nb_metadata').click(function () {
209 that.notebook.edit_metadata({
208 that.notebook.edit_metadata({
210 notebook: that.notebook,
209 notebook: that.notebook,
211 keyboard_manager: that.notebook.keyboard_manager});
210 keyboard_manager: that.notebook.keyboard_manager});
212 });
211 });
213
212
214 // View
213 // View
215 this.element.find('#toggle_header').click(function () {
214 this.element.find('#toggle_header').click(function () {
216 $('div#header').toggle();
215 $('div#header').toggle();
217 that.layout_manager.do_resize();
216 that.layout_manager.do_resize();
218 });
217 });
219 this.element.find('#toggle_toolbar').click(function () {
218 this.element.find('#toggle_toolbar').click(function () {
220 $('div#maintoolbar').toggle();
219 $('div#maintoolbar').toggle();
221 that.layout_manager.do_resize();
220 that.layout_manager.do_resize();
222 });
221 });
223 // Insert
222 // Insert
224 this.element.find('#insert_cell_above').click(function () {
223 this.element.find('#insert_cell_above').click(function () {
225 that.notebook.insert_cell_above('code');
224 that.notebook.insert_cell_above('code');
226 that.notebook.select_prev();
225 that.notebook.select_prev();
227 });
226 });
228 this.element.find('#insert_cell_below').click(function () {
227 this.element.find('#insert_cell_below').click(function () {
229 that.notebook.insert_cell_below('code');
228 that.notebook.insert_cell_below('code');
230 that.notebook.select_next();
229 that.notebook.select_next();
231 });
230 });
232 // Cell
231 // Cell
233 this.element.find('#run_cell').click(function () {
232 this.element.find('#run_cell').click(function () {
234 that.notebook.execute_cell();
233 that.notebook.execute_cell();
235 });
234 });
236 this.element.find('#run_cell_select_below').click(function () {
235 this.element.find('#run_cell_select_below').click(function () {
237 that.notebook.execute_cell_and_select_below();
236 that.notebook.execute_cell_and_select_below();
238 });
237 });
239 this.element.find('#run_cell_insert_below').click(function () {
238 this.element.find('#run_cell_insert_below').click(function () {
240 that.notebook.execute_cell_and_insert_below();
239 that.notebook.execute_cell_and_insert_below();
241 });
240 });
242 this.element.find('#run_all_cells').click(function () {
241 this.element.find('#run_all_cells').click(function () {
243 that.notebook.execute_all_cells();
242 that.notebook.execute_all_cells();
244 });
243 });
245 this.element.find('#run_all_cells_above').click(function () {
244 this.element.find('#run_all_cells_above').click(function () {
246 that.notebook.execute_cells_above();
245 that.notebook.execute_cells_above();
247 });
246 });
248 this.element.find('#run_all_cells_below').click(function () {
247 this.element.find('#run_all_cells_below').click(function () {
249 that.notebook.execute_cells_below();
248 that.notebook.execute_cells_below();
250 });
249 });
251 this.element.find('#to_code').click(function () {
250 this.element.find('#to_code').click(function () {
252 that.notebook.to_code();
251 that.notebook.to_code();
253 });
252 });
254 this.element.find('#to_markdown').click(function () {
253 this.element.find('#to_markdown').click(function () {
255 that.notebook.to_markdown();
254 that.notebook.to_markdown();
256 });
255 });
257 this.element.find('#to_raw').click(function () {
256 this.element.find('#to_raw').click(function () {
258 that.notebook.to_raw();
257 that.notebook.to_raw();
259 });
258 });
260
259
261 this.element.find('#toggle_current_output').click(function () {
260 this.element.find('#toggle_current_output').click(function () {
262 that.notebook.toggle_output();
261 that.notebook.toggle_output();
263 });
262 });
264 this.element.find('#toggle_current_output_scroll').click(function () {
263 this.element.find('#toggle_current_output_scroll').click(function () {
265 that.notebook.toggle_output_scroll();
264 that.notebook.toggle_output_scroll();
266 });
265 });
267 this.element.find('#clear_current_output').click(function () {
266 this.element.find('#clear_current_output').click(function () {
268 that.notebook.clear_output();
267 that.notebook.clear_output();
269 });
268 });
270
269
271 this.element.find('#toggle_all_output').click(function () {
270 this.element.find('#toggle_all_output').click(function () {
272 that.notebook.toggle_all_output();
271 that.notebook.toggle_all_output();
273 });
272 });
274 this.element.find('#toggle_all_output_scroll').click(function () {
273 this.element.find('#toggle_all_output_scroll').click(function () {
275 that.notebook.toggle_all_output_scroll();
274 that.notebook.toggle_all_output_scroll();
276 });
275 });
277 this.element.find('#clear_all_output').click(function () {
276 this.element.find('#clear_all_output').click(function () {
278 that.notebook.clear_all_output();
277 that.notebook.clear_all_output();
279 });
278 });
280
279
281 // Kernel
280 // Kernel
282 this.element.find('#int_kernel').click(function () {
281 this.element.find('#int_kernel').click(function () {
283 that.notebook.kernel.interrupt();
282 that.notebook.kernel.interrupt();
284 });
283 });
285 this.element.find('#restart_kernel').click(function () {
284 this.element.find('#restart_kernel').click(function () {
286 that.notebook.restart_kernel();
285 that.notebook.restart_kernel();
287 });
286 });
288 this.element.find('#reconnect_kernel').click(function () {
287 this.element.find('#reconnect_kernel').click(function () {
289 that.notebook.kernel.reconnect();
288 that.notebook.kernel.reconnect();
290 });
289 });
291 // Help
290 // Help
292 if (this.tour) {
291 if (this.tour) {
293 this.element.find('#notebook_tour').click(function () {
292 this.element.find('#notebook_tour').click(function () {
294 that.tour.start();
293 that.tour.start();
295 });
294 });
296 } else {
295 } else {
297 this.element.find('#notebook_tour').addClass("disabled");
296 this.element.find('#notebook_tour').addClass("disabled");
298 }
297 }
299 this.element.find('#keyboard_shortcuts').click(function () {
298 this.element.find('#keyboard_shortcuts').click(function () {
300 that.quick_help.show_keyboard_shortcuts();
299 that.quick_help.show_keyboard_shortcuts();
301 });
300 });
302
301
303 this.update_restore_checkpoint(null);
302 this.update_restore_checkpoint(null);
304
303
305 this.events.on('checkpoints_listed.Notebook', function (event, data) {
304 this.events.on('checkpoints_listed.Notebook', function (event, data) {
306 that.update_restore_checkpoint(that.notebook.checkpoints);
305 that.update_restore_checkpoint(that.notebook.checkpoints);
307 });
306 });
308
307
309 this.events.on('checkpoint_created.Notebook', function (event, data) {
308 this.events.on('checkpoint_created.Notebook', function (event, data) {
310 that.update_restore_checkpoint(that.notebook.checkpoints);
309 that.update_restore_checkpoint(that.notebook.checkpoints);
311 });
310 });
312 };
311 };
313
312
314 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
313 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
315 var ul = this.element.find("#restore_checkpoint").find("ul");
314 var ul = this.element.find("#restore_checkpoint").find("ul");
316 ul.empty();
315 ul.empty();
317 if (!checkpoints || checkpoints.length === 0) {
316 if (!checkpoints || checkpoints.length === 0) {
318 ul.append(
317 ul.append(
319 $("<li/>")
318 $("<li/>")
320 .addClass("disabled")
319 .addClass("disabled")
321 .append(
320 .append(
322 $("<a/>")
321 $("<a/>")
323 .text("No checkpoints")
322 .text("No checkpoints")
324 )
323 )
325 );
324 );
326 return;
325 return;
327 }
326 }
328
327
329 var that = this;
328 var that = this;
330 checkpoints.map(function (checkpoint) {
329 checkpoints.map(function (checkpoint) {
331 var d = new Date(checkpoint.last_modified);
330 var d = new Date(checkpoint.last_modified);
332 ul.append(
331 ul.append(
333 $("<li/>").append(
332 $("<li/>").append(
334 $("<a/>")
333 $("<a/>")
335 .attr("href", "#")
334 .attr("href", "#")
336 .text(moment(d).format("LLLL"))
335 .text(moment(d).format("LLLL"))
337 .click(function () {
336 .click(function () {
338 that.notebook.restore_checkpoint_dialog(checkpoint);
337 that.notebook.restore_checkpoint_dialog(checkpoint);
339 })
338 })
340 )
339 )
341 );
340 );
342 });
341 });
343 };
342 };
344
343
345 // Backwards compatability.
344 // Backwards compatability.
346 IPython.MenuBar = MenuBar;
345 IPython.MenuBar = MenuBar;
347
346
348 return {'MenuBar': MenuBar};
347 return {'MenuBar': MenuBar};
349 });
348 });
@@ -1,2484 +1,2479 b''
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/session',
11 'services/sessions/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'highlight',
14 'highlight',
15 'notebook/js/mathjaxutils',
15 'notebook/js/mathjaxutils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'notebook/js/tooltip',
17 'notebook/js/tooltip',
18 'notebook/js/celltoolbarpresets/default',
18 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/rawcell',
19 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/slideshow',
20 'notebook/js/celltoolbarpresets/slideshow',
21 'notebook/js/scrollmanager'
21 'notebook/js/scrollmanager'
22 ], function (
22 ], function (
23 IPython,
23 IPython,
24 $,
24 $,
25 utils,
25 utils,
26 dialog,
26 dialog,
27 textcell,
27 textcell,
28 codecell,
28 codecell,
29 session,
29 session,
30 celltoolbar,
30 celltoolbar,
31 marked,
31 marked,
32 hljs,
32 hljs,
33 mathjaxutils,
33 mathjaxutils,
34 keyboard,
34 keyboard,
35 tooltip,
35 tooltip,
36 default_celltoolbar,
36 default_celltoolbar,
37 rawcell_celltoolbar,
37 rawcell_celltoolbar,
38 slideshow_celltoolbar,
38 slideshow_celltoolbar,
39 scrollmanager
39 scrollmanager
40 ) {
40 ) {
41
41
42 var Notebook = function (selector, options) {
42 var Notebook = function (selector, options) {
43 // Constructor
43 // Constructor
44 //
44 //
45 // A notebook contains and manages cells.
45 // A notebook contains and manages cells.
46 //
46 //
47 // Parameters:
47 // Parameters:
48 // selector: string
48 // selector: string
49 // options: dictionary
49 // options: dictionary
50 // Dictionary of keyword arguments.
50 // Dictionary of keyword arguments.
51 // events: $(Events) instance
51 // events: $(Events) instance
52 // keyboard_manager: KeyboardManager instance
52 // keyboard_manager: KeyboardManager instance
53 // contents: Contents instance
53 // contents: Contents instance
54 // save_widget: SaveWidget instance
54 // save_widget: SaveWidget instance
55 // config: dictionary
55 // config: dictionary
56 // base_url : string
56 // base_url : string
57 // notebook_path : string
57 // notebook_path : string
58 // notebook_name : string
58 // notebook_name : string
59 this.config = utils.mergeopt(Notebook, options.config);
59 this.config = utils.mergeopt(Notebook, options.config);
60 this.base_url = options.base_url;
60 this.base_url = options.base_url;
61 this.notebook_path = options.notebook_path;
61 this.notebook_path = options.notebook_path;
62 this.notebook_name = options.notebook_name;
62 this.notebook_name = options.notebook_name;
63 this.events = options.events;
63 this.events = options.events;
64 this.keyboard_manager = options.keyboard_manager;
64 this.keyboard_manager = options.keyboard_manager;
65 this.contents = options.contents;
65 this.contents = options.contents;
66 this.save_widget = options.save_widget;
66 this.save_widget = options.save_widget;
67 this.tooltip = new tooltip.Tooltip(this.events);
67 this.tooltip = new tooltip.Tooltip(this.events);
68 this.ws_url = options.ws_url;
68 this.ws_url = options.ws_url;
69 this._session_starting = false;
69 this._session_starting = false;
70 this.default_cell_type = this.config.default_cell_type || 'code';
70 this.default_cell_type = this.config.default_cell_type || 'code';
71
71
72 // Create default scroll manager.
72 // Create default scroll manager.
73 this.scroll_manager = new scrollmanager.ScrollManager(this);
73 this.scroll_manager = new scrollmanager.ScrollManager(this);
74
74
75 // TODO: This code smells (and the other `= this` line a couple lines down)
75 // TODO: This code smells (and the other `= this` line a couple lines down)
76 // We need a better way to deal with circular instance references.
76 // We need a better way to deal with circular instance references.
77 this.keyboard_manager.notebook = this;
77 this.keyboard_manager.notebook = this;
78 this.save_widget.notebook = this;
78 this.save_widget.notebook = this;
79
79
80 mathjaxutils.init();
80 mathjaxutils.init();
81
81
82 if (marked) {
82 if (marked) {
83 marked.setOptions({
83 marked.setOptions({
84 gfm : true,
84 gfm : true,
85 tables: true,
85 tables: true,
86 langPrefix: "language-",
86 langPrefix: "language-",
87 highlight: function(code, lang) {
87 highlight: function(code, lang) {
88 if (!lang) {
88 if (!lang) {
89 // no language, no highlight
89 // no language, no highlight
90 return code;
90 return code;
91 }
91 }
92 var highlighted;
92 var highlighted;
93 try {
93 try {
94 highlighted = hljs.highlight(lang, code, false);
94 highlighted = hljs.highlight(lang, code, false);
95 } catch(err) {
95 } catch(err) {
96 highlighted = hljs.highlightAuto(code);
96 highlighted = hljs.highlightAuto(code);
97 }
97 }
98 return highlighted.value;
98 return highlighted.value;
99 }
99 }
100 });
100 });
101 }
101 }
102
102
103 this.element = $(selector);
103 this.element = $(selector);
104 this.element.scroll();
104 this.element.scroll();
105 this.element.data("notebook", this);
105 this.element.data("notebook", this);
106 this.next_prompt_number = 1;
106 this.next_prompt_number = 1;
107 this.session = null;
107 this.session = null;
108 this.kernel = null;
108 this.kernel = null;
109 this.clipboard = null;
109 this.clipboard = null;
110 this.undelete_backup = null;
110 this.undelete_backup = null;
111 this.undelete_index = null;
111 this.undelete_index = null;
112 this.undelete_below = false;
112 this.undelete_below = false;
113 this.paste_enabled = false;
113 this.paste_enabled = false;
114 // It is important to start out in command mode to match the intial mode
114 // It is important to start out in command mode to match the intial mode
115 // of the KeyboardManager.
115 // of the KeyboardManager.
116 this.mode = 'command';
116 this.mode = 'command';
117 this.set_dirty(false);
117 this.set_dirty(false);
118 this.metadata = {};
118 this.metadata = {};
119 this._checkpoint_after_save = false;
119 this._checkpoint_after_save = false;
120 this.last_checkpoint = null;
120 this.last_checkpoint = null;
121 this.checkpoints = [];
121 this.checkpoints = [];
122 this.autosave_interval = 0;
122 this.autosave_interval = 0;
123 this.autosave_timer = null;
123 this.autosave_timer = null;
124 // autosave *at most* every two minutes
124 // autosave *at most* every two minutes
125 this.minimum_autosave_interval = 120000;
125 this.minimum_autosave_interval = 120000;
126 this.notebook_name_blacklist_re = /[\/\\:]/;
126 this.notebook_name_blacklist_re = /[\/\\:]/;
127 this.nbformat = 4; // Increment this when changing the nbformat
127 this.nbformat = 4; // Increment this when changing the nbformat
128 this.nbformat_minor = 0; // Increment this when changing the nbformat
128 this.nbformat_minor = 0; // Increment this when changing the nbformat
129 this.codemirror_mode = 'ipython';
129 this.codemirror_mode = 'ipython';
130 this.create_elements();
130 this.create_elements();
131 this.bind_events();
131 this.bind_events();
132 this.save_notebook = function() { // don't allow save until notebook_loaded
132 this.save_notebook = function() { // don't allow save until notebook_loaded
133 this.save_notebook_error(null, null, "Load failed, save is disabled");
133 this.save_notebook_error(null, null, "Load failed, save is disabled");
134 };
134 };
135
135
136 // Trigger cell toolbar registration.
136 // Trigger cell toolbar registration.
137 default_celltoolbar.register(this);
137 default_celltoolbar.register(this);
138 rawcell_celltoolbar.register(this);
138 rawcell_celltoolbar.register(this);
139 slideshow_celltoolbar.register(this);
139 slideshow_celltoolbar.register(this);
140 };
140 };
141
141
142 Notebook.options_default = {
142 Notebook.options_default = {
143 // can be any cell type, or the special values of
143 // can be any cell type, or the special values of
144 // 'above', 'below', or 'selected' to get the value from another cell.
144 // 'above', 'below', or 'selected' to get the value from another cell.
145 Notebook: {
145 Notebook: {
146 default_cell_type: 'code',
146 default_cell_type: 'code',
147 }
147 }
148 };
148 };
149
149
150
150
151 /**
151 /**
152 * Create an HTML and CSS representation of the notebook.
152 * Create an HTML and CSS representation of the notebook.
153 *
153 *
154 * @method create_elements
154 * @method create_elements
155 */
155 */
156 Notebook.prototype.create_elements = function () {
156 Notebook.prototype.create_elements = function () {
157 var that = this;
157 var that = this;
158 this.element.attr('tabindex','-1');
158 this.element.attr('tabindex','-1');
159 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
159 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
160 // We add this end_space div to the end of the notebook div to:
160 // We add this end_space div to the end of the notebook div to:
161 // i) provide a margin between the last cell and the end of the notebook
161 // i) provide a margin between the last cell and the end of the notebook
162 // ii) to prevent the div from scrolling up when the last cell is being
162 // ii) to prevent the div from scrolling up when the last cell is being
163 // edited, but is too low on the page, which browsers will do automatically.
163 // edited, but is too low on the page, which browsers will do automatically.
164 var end_space = $('<div/>').addClass('end_space');
164 var end_space = $('<div/>').addClass('end_space');
165 end_space.dblclick(function (e) {
165 end_space.dblclick(function (e) {
166 var ncells = that.ncells();
166 var ncells = that.ncells();
167 that.insert_cell_below('code',ncells-1);
167 that.insert_cell_below('code',ncells-1);
168 });
168 });
169 this.element.append(this.container);
169 this.element.append(this.container);
170 this.container.append(end_space);
170 this.container.append(end_space);
171 };
171 };
172
172
173 /**
173 /**
174 * Bind JavaScript events: key presses and custom IPython events.
174 * Bind JavaScript events: key presses and custom IPython events.
175 *
175 *
176 * @method bind_events
176 * @method bind_events
177 */
177 */
178 Notebook.prototype.bind_events = function () {
178 Notebook.prototype.bind_events = function () {
179 var that = this;
179 var that = this;
180
180
181 this.events.on('set_next_input.Notebook', function (event, data) {
181 this.events.on('set_next_input.Notebook', function (event, data) {
182 var index = that.find_cell_index(data.cell);
182 var index = that.find_cell_index(data.cell);
183 var new_cell = that.insert_cell_below('code',index);
183 var new_cell = that.insert_cell_below('code',index);
184 new_cell.set_text(data.text);
184 new_cell.set_text(data.text);
185 that.dirty = true;
185 that.dirty = true;
186 });
186 });
187
187
188 this.events.on('set_dirty.Notebook', function (event, data) {
188 this.events.on('set_dirty.Notebook', function (event, data) {
189 that.dirty = data.value;
189 that.dirty = data.value;
190 });
190 });
191
191
192 this.events.on('trust_changed.Notebook', function (event, trusted) {
192 this.events.on('trust_changed.Notebook', function (event, trusted) {
193 that.trusted = trusted;
193 that.trusted = trusted;
194 });
194 });
195
195
196 this.events.on('select.Cell', function (event, data) {
196 this.events.on('select.Cell', function (event, data) {
197 var index = that.find_cell_index(data.cell);
197 var index = that.find_cell_index(data.cell);
198 that.select(index);
198 that.select(index);
199 });
199 });
200
200
201 this.events.on('edit_mode.Cell', function (event, data) {
201 this.events.on('edit_mode.Cell', function (event, data) {
202 that.handle_edit_mode(data.cell);
202 that.handle_edit_mode(data.cell);
203 });
203 });
204
204
205 this.events.on('command_mode.Cell', function (event, data) {
205 this.events.on('command_mode.Cell', function (event, data) {
206 that.handle_command_mode(data.cell);
206 that.handle_command_mode(data.cell);
207 });
207 });
208
208
209 this.events.on('spec_changed.Kernel', function(event, data) {
209 this.events.on('spec_changed.Kernel', function(event, data) {
210 that.metadata.kernelspec =
210 that.metadata.kernelspec =
211 {name: data.name, display_name: data.display_name};
211 {name: data.name, display_name: data.display_name};
212 });
212 });
213
213
214 this.events.on('kernel_ready.Kernel', function(event, data) {
214 this.events.on('kernel_ready.Kernel', function(event, data) {
215 var kinfo = data.kernel.info_reply;
215 var kinfo = data.kernel.info_reply;
216 var langinfo = kinfo.language_info || {};
216 var langinfo = kinfo.language_info || {};
217 if (!langinfo.name) langinfo.name = kinfo.language;
217 if (!langinfo.name) langinfo.name = kinfo.language;
218
218
219 that.metadata.language_info = langinfo;
219 that.metadata.language_info = langinfo;
220 // Mode 'null' should be plain, unhighlighted text.
220 // Mode 'null' should be plain, unhighlighted text.
221 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
221 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
222 that.set_codemirror_mode(cm_mode);
222 that.set_codemirror_mode(cm_mode);
223 });
223 });
224
224
225 var collapse_time = function (time) {
225 var collapse_time = function (time) {
226 var app_height = $('#ipython-main-app').height(); // content height
226 var app_height = $('#ipython-main-app').height(); // content height
227 var splitter_height = $('div#pager_splitter').outerHeight(true);
227 var splitter_height = $('div#pager_splitter').outerHeight(true);
228 var new_height = app_height - splitter_height;
228 var new_height = app_height - splitter_height;
229 that.element.animate({height : new_height + 'px'}, time);
229 that.element.animate({height : new_height + 'px'}, time);
230 };
230 };
231
231
232 this.element.bind('collapse_pager', function (event, extrap) {
232 this.element.bind('collapse_pager', function (event, extrap) {
233 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
233 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
234 collapse_time(time);
234 collapse_time(time);
235 });
235 });
236
236
237 var expand_time = function (time) {
237 var expand_time = function (time) {
238 var app_height = $('#ipython-main-app').height(); // content height
238 var app_height = $('#ipython-main-app').height(); // content height
239 var splitter_height = $('div#pager_splitter').outerHeight(true);
239 var splitter_height = $('div#pager_splitter').outerHeight(true);
240 var pager_height = $('div#pager').outerHeight(true);
240 var pager_height = $('div#pager').outerHeight(true);
241 var new_height = app_height - pager_height - splitter_height;
241 var new_height = app_height - pager_height - splitter_height;
242 that.element.animate({height : new_height + 'px'}, time);
242 that.element.animate({height : new_height + 'px'}, time);
243 };
243 };
244
244
245 this.element.bind('expand_pager', function (event, extrap) {
245 this.element.bind('expand_pager', function (event, extrap) {
246 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
246 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
247 expand_time(time);
247 expand_time(time);
248 });
248 });
249
249
250 // Firefox 22 broke $(window).on("beforeunload")
250 // Firefox 22 broke $(window).on("beforeunload")
251 // I'm not sure why or how.
251 // I'm not sure why or how.
252 window.onbeforeunload = function (e) {
252 window.onbeforeunload = function (e) {
253 // TODO: Make killing the kernel configurable.
253 // TODO: Make killing the kernel configurable.
254 var kill_kernel = false;
254 var kill_kernel = false;
255 if (kill_kernel) {
255 if (kill_kernel) {
256 that.session.delete();
256 that.session.delete();
257 }
257 }
258 // if we are autosaving, trigger an autosave on nav-away.
258 // if we are autosaving, trigger an autosave on nav-away.
259 // still warn, because if we don't the autosave may fail.
259 // still warn, because if we don't the autosave may fail.
260 if (that.dirty) {
260 if (that.dirty) {
261 if ( that.autosave_interval ) {
261 if ( that.autosave_interval ) {
262 // schedule autosave in a timeout
262 // schedule autosave in a timeout
263 // this gives you a chance to forcefully discard changes
263 // this gives you a chance to forcefully discard changes
264 // by reloading the page if you *really* want to.
264 // by reloading the page if you *really* want to.
265 // the timer doesn't start until you *dismiss* the dialog.
265 // the timer doesn't start until you *dismiss* the dialog.
266 setTimeout(function () {
266 setTimeout(function () {
267 if (that.dirty) {
267 if (that.dirty) {
268 that.save_notebook();
268 that.save_notebook();
269 }
269 }
270 }, 1000);
270 }, 1000);
271 return "Autosave in progress, latest changes may be lost.";
271 return "Autosave in progress, latest changes may be lost.";
272 } else {
272 } else {
273 return "Unsaved changes will be lost.";
273 return "Unsaved changes will be lost.";
274 }
274 }
275 }
275 }
276 // Null is the *only* return value that will make the browser not
276 // Null is the *only* return value that will make the browser not
277 // pop up the "don't leave" dialog.
277 // pop up the "don't leave" dialog.
278 return null;
278 return null;
279 };
279 };
280 };
280 };
281
281
282 /**
282 /**
283 * Set the dirty flag, and trigger the set_dirty.Notebook event
283 * Set the dirty flag, and trigger the set_dirty.Notebook event
284 *
284 *
285 * @method set_dirty
285 * @method set_dirty
286 */
286 */
287 Notebook.prototype.set_dirty = function (value) {
287 Notebook.prototype.set_dirty = function (value) {
288 if (value === undefined) {
288 if (value === undefined) {
289 value = true;
289 value = true;
290 }
290 }
291 if (this.dirty == value) {
291 if (this.dirty == value) {
292 return;
292 return;
293 }
293 }
294 this.events.trigger('set_dirty.Notebook', {value: value});
294 this.events.trigger('set_dirty.Notebook', {value: value});
295 };
295 };
296
296
297 /**
297 /**
298 * Scroll the top of the page to a given cell.
298 * Scroll the top of the page to a given cell.
299 *
299 *
300 * @method scroll_to_cell
300 * @method scroll_to_cell
301 * @param {Number} cell_number An index of the cell to view
301 * @param {Number} cell_number An index of the cell to view
302 * @param {Number} time Animation time in milliseconds
302 * @param {Number} time Animation time in milliseconds
303 * @return {Number} Pixel offset from the top of the container
303 * @return {Number} Pixel offset from the top of the container
304 */
304 */
305 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
305 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
306 var cells = this.get_cells();
306 var cells = this.get_cells();
307 time = time || 0;
307 time = time || 0;
308 cell_number = Math.min(cells.length-1,cell_number);
308 cell_number = Math.min(cells.length-1,cell_number);
309 cell_number = Math.max(0 ,cell_number);
309 cell_number = Math.max(0 ,cell_number);
310 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
310 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
311 this.element.animate({scrollTop:scroll_value}, time);
311 this.element.animate({scrollTop:scroll_value}, time);
312 return scroll_value;
312 return scroll_value;
313 };
313 };
314
314
315 /**
315 /**
316 * Scroll to the bottom of the page.
316 * Scroll to the bottom of the page.
317 *
317 *
318 * @method scroll_to_bottom
318 * @method scroll_to_bottom
319 */
319 */
320 Notebook.prototype.scroll_to_bottom = function () {
320 Notebook.prototype.scroll_to_bottom = function () {
321 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
321 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
322 };
322 };
323
323
324 /**
324 /**
325 * Scroll to the top of the page.
325 * Scroll to the top of the page.
326 *
326 *
327 * @method scroll_to_top
327 * @method scroll_to_top
328 */
328 */
329 Notebook.prototype.scroll_to_top = function () {
329 Notebook.prototype.scroll_to_top = function () {
330 this.element.animate({scrollTop:0}, 0);
330 this.element.animate({scrollTop:0}, 0);
331 };
331 };
332
332
333 // Edit Notebook metadata
333 // Edit Notebook metadata
334
334
335 Notebook.prototype.edit_metadata = function () {
335 Notebook.prototype.edit_metadata = function () {
336 var that = this;
336 var that = this;
337 dialog.edit_metadata({
337 dialog.edit_metadata({
338 md: this.metadata,
338 md: this.metadata,
339 callback: function (md) {
339 callback: function (md) {
340 that.metadata = md;
340 that.metadata = md;
341 },
341 },
342 name: 'Notebook',
342 name: 'Notebook',
343 notebook: this,
343 notebook: this,
344 keyboard_manager: this.keyboard_manager});
344 keyboard_manager: this.keyboard_manager});
345 };
345 };
346
346
347 // Cell indexing, retrieval, etc.
347 // Cell indexing, retrieval, etc.
348
348
349 /**
349 /**
350 * Get all cell elements in the notebook.
350 * Get all cell elements in the notebook.
351 *
351 *
352 * @method get_cell_elements
352 * @method get_cell_elements
353 * @return {jQuery} A selector of all cell elements
353 * @return {jQuery} A selector of all cell elements
354 */
354 */
355 Notebook.prototype.get_cell_elements = function () {
355 Notebook.prototype.get_cell_elements = function () {
356 return this.container.children("div.cell");
356 return this.container.children("div.cell");
357 };
357 };
358
358
359 /**
359 /**
360 * Get a particular cell element.
360 * Get a particular cell element.
361 *
361 *
362 * @method get_cell_element
362 * @method get_cell_element
363 * @param {Number} index An index of a cell to select
363 * @param {Number} index An index of a cell to select
364 * @return {jQuery} A selector of the given cell.
364 * @return {jQuery} A selector of the given cell.
365 */
365 */
366 Notebook.prototype.get_cell_element = function (index) {
366 Notebook.prototype.get_cell_element = function (index) {
367 var result = null;
367 var result = null;
368 var e = this.get_cell_elements().eq(index);
368 var e = this.get_cell_elements().eq(index);
369 if (e.length !== 0) {
369 if (e.length !== 0) {
370 result = e;
370 result = e;
371 }
371 }
372 return result;
372 return result;
373 };
373 };
374
374
375 /**
375 /**
376 * Try to get a particular cell by msg_id.
376 * Try to get a particular cell by msg_id.
377 *
377 *
378 * @method get_msg_cell
378 * @method get_msg_cell
379 * @param {String} msg_id A message UUID
379 * @param {String} msg_id A message UUID
380 * @return {Cell} Cell or null if no cell was found.
380 * @return {Cell} Cell or null if no cell was found.
381 */
381 */
382 Notebook.prototype.get_msg_cell = function (msg_id) {
382 Notebook.prototype.get_msg_cell = function (msg_id) {
383 return codecell.CodeCell.msg_cells[msg_id] || null;
383 return codecell.CodeCell.msg_cells[msg_id] || null;
384 };
384 };
385
385
386 /**
386 /**
387 * Count the cells in this notebook.
387 * Count the cells in this notebook.
388 *
388 *
389 * @method ncells
389 * @method ncells
390 * @return {Number} The number of cells in this notebook
390 * @return {Number} The number of cells in this notebook
391 */
391 */
392 Notebook.prototype.ncells = function () {
392 Notebook.prototype.ncells = function () {
393 return this.get_cell_elements().length;
393 return this.get_cell_elements().length;
394 };
394 };
395
395
396 /**
396 /**
397 * Get all Cell objects in this notebook.
397 * Get all Cell objects in this notebook.
398 *
398 *
399 * @method get_cells
399 * @method get_cells
400 * @return {Array} This notebook's Cell objects
400 * @return {Array} This notebook's Cell objects
401 */
401 */
402 // TODO: we are often calling cells as cells()[i], which we should optimize
402 // TODO: we are often calling cells as cells()[i], which we should optimize
403 // to cells(i) or a new method.
403 // to cells(i) or a new method.
404 Notebook.prototype.get_cells = function () {
404 Notebook.prototype.get_cells = function () {
405 return this.get_cell_elements().toArray().map(function (e) {
405 return this.get_cell_elements().toArray().map(function (e) {
406 return $(e).data("cell");
406 return $(e).data("cell");
407 });
407 });
408 };
408 };
409
409
410 /**
410 /**
411 * Get a Cell object from this notebook.
411 * Get a Cell object from this notebook.
412 *
412 *
413 * @method get_cell
413 * @method get_cell
414 * @param {Number} index An index of a cell to retrieve
414 * @param {Number} index An index of a cell to retrieve
415 * @return {Cell} Cell or null if no cell was found.
415 * @return {Cell} Cell or null if no cell was found.
416 */
416 */
417 Notebook.prototype.get_cell = function (index) {
417 Notebook.prototype.get_cell = function (index) {
418 var result = null;
418 var result = null;
419 var ce = this.get_cell_element(index);
419 var ce = this.get_cell_element(index);
420 if (ce !== null) {
420 if (ce !== null) {
421 result = ce.data('cell');
421 result = ce.data('cell');
422 }
422 }
423 return result;
423 return result;
424 };
424 };
425
425
426 /**
426 /**
427 * Get the cell below a given cell.
427 * Get the cell below a given cell.
428 *
428 *
429 * @method get_next_cell
429 * @method get_next_cell
430 * @param {Cell} cell The provided cell
430 * @param {Cell} cell The provided cell
431 * @return {Cell} the next cell or null if no cell was found.
431 * @return {Cell} the next cell or null if no cell was found.
432 */
432 */
433 Notebook.prototype.get_next_cell = function (cell) {
433 Notebook.prototype.get_next_cell = function (cell) {
434 var result = null;
434 var result = null;
435 var index = this.find_cell_index(cell);
435 var index = this.find_cell_index(cell);
436 if (this.is_valid_cell_index(index+1)) {
436 if (this.is_valid_cell_index(index+1)) {
437 result = this.get_cell(index+1);
437 result = this.get_cell(index+1);
438 }
438 }
439 return result;
439 return result;
440 };
440 };
441
441
442 /**
442 /**
443 * Get the cell above a given cell.
443 * Get the cell above a given cell.
444 *
444 *
445 * @method get_prev_cell
445 * @method get_prev_cell
446 * @param {Cell} cell The provided cell
446 * @param {Cell} cell The provided cell
447 * @return {Cell} The previous cell or null if no cell was found.
447 * @return {Cell} The previous cell or null if no cell was found.
448 */
448 */
449 Notebook.prototype.get_prev_cell = function (cell) {
449 Notebook.prototype.get_prev_cell = function (cell) {
450 var result = null;
450 var result = null;
451 var index = this.find_cell_index(cell);
451 var index = this.find_cell_index(cell);
452 if (index !== null && index > 0) {
452 if (index !== null && index > 0) {
453 result = this.get_cell(index-1);
453 result = this.get_cell(index-1);
454 }
454 }
455 return result;
455 return result;
456 };
456 };
457
457
458 /**
458 /**
459 * Get the numeric index of a given cell.
459 * Get the numeric index of a given cell.
460 *
460 *
461 * @method find_cell_index
461 * @method find_cell_index
462 * @param {Cell} cell The provided cell
462 * @param {Cell} cell The provided cell
463 * @return {Number} The cell's numeric index or null if no cell was found.
463 * @return {Number} The cell's numeric index or null if no cell was found.
464 */
464 */
465 Notebook.prototype.find_cell_index = function (cell) {
465 Notebook.prototype.find_cell_index = function (cell) {
466 var result = null;
466 var result = null;
467 this.get_cell_elements().filter(function (index) {
467 this.get_cell_elements().filter(function (index) {
468 if ($(this).data("cell") === cell) {
468 if ($(this).data("cell") === cell) {
469 result = index;
469 result = index;
470 }
470 }
471 });
471 });
472 return result;
472 return result;
473 };
473 };
474
474
475 /**
475 /**
476 * Get a given index , or the selected index if none is provided.
476 * Get a given index , or the selected index if none is provided.
477 *
477 *
478 * @method index_or_selected
478 * @method index_or_selected
479 * @param {Number} index A cell's index
479 * @param {Number} index A cell's index
480 * @return {Number} The given index, or selected index if none is provided.
480 * @return {Number} The given index, or selected index if none is provided.
481 */
481 */
482 Notebook.prototype.index_or_selected = function (index) {
482 Notebook.prototype.index_or_selected = function (index) {
483 var i;
483 var i;
484 if (index === undefined || index === null) {
484 if (index === undefined || index === null) {
485 i = this.get_selected_index();
485 i = this.get_selected_index();
486 if (i === null) {
486 if (i === null) {
487 i = 0;
487 i = 0;
488 }
488 }
489 } else {
489 } else {
490 i = index;
490 i = index;
491 }
491 }
492 return i;
492 return i;
493 };
493 };
494
494
495 /**
495 /**
496 * Get the currently selected cell.
496 * Get the currently selected cell.
497 * @method get_selected_cell
497 * @method get_selected_cell
498 * @return {Cell} The selected cell
498 * @return {Cell} The selected cell
499 */
499 */
500 Notebook.prototype.get_selected_cell = function () {
500 Notebook.prototype.get_selected_cell = function () {
501 var index = this.get_selected_index();
501 var index = this.get_selected_index();
502 return this.get_cell(index);
502 return this.get_cell(index);
503 };
503 };
504
504
505 /**
505 /**
506 * Check whether a cell index is valid.
506 * Check whether a cell index is valid.
507 *
507 *
508 * @method is_valid_cell_index
508 * @method is_valid_cell_index
509 * @param {Number} index A cell index
509 * @param {Number} index A cell index
510 * @return True if the index is valid, false otherwise
510 * @return True if the index is valid, false otherwise
511 */
511 */
512 Notebook.prototype.is_valid_cell_index = function (index) {
512 Notebook.prototype.is_valid_cell_index = function (index) {
513 if (index !== null && index >= 0 && index < this.ncells()) {
513 if (index !== null && index >= 0 && index < this.ncells()) {
514 return true;
514 return true;
515 } else {
515 } else {
516 return false;
516 return false;
517 }
517 }
518 };
518 };
519
519
520 /**
520 /**
521 * Get the index of the currently selected cell.
521 * Get the index of the currently selected cell.
522
522
523 * @method get_selected_index
523 * @method get_selected_index
524 * @return {Number} The selected cell's numeric index
524 * @return {Number} The selected cell's numeric index
525 */
525 */
526 Notebook.prototype.get_selected_index = function () {
526 Notebook.prototype.get_selected_index = function () {
527 var result = null;
527 var result = null;
528 this.get_cell_elements().filter(function (index) {
528 this.get_cell_elements().filter(function (index) {
529 if ($(this).data("cell").selected === true) {
529 if ($(this).data("cell").selected === true) {
530 result = index;
530 result = index;
531 }
531 }
532 });
532 });
533 return result;
533 return result;
534 };
534 };
535
535
536
536
537 // Cell selection.
537 // Cell selection.
538
538
539 /**
539 /**
540 * Programmatically select a cell.
540 * Programmatically select a cell.
541 *
541 *
542 * @method select
542 * @method select
543 * @param {Number} index A cell's index
543 * @param {Number} index A cell's index
544 * @return {Notebook} This notebook
544 * @return {Notebook} This notebook
545 */
545 */
546 Notebook.prototype.select = function (index) {
546 Notebook.prototype.select = function (index) {
547 if (this.is_valid_cell_index(index)) {
547 if (this.is_valid_cell_index(index)) {
548 var sindex = this.get_selected_index();
548 var sindex = this.get_selected_index();
549 if (sindex !== null && index !== sindex) {
549 if (sindex !== null && index !== sindex) {
550 // If we are about to select a different cell, make sure we are
550 // If we are about to select a different cell, make sure we are
551 // first in command mode.
551 // first in command mode.
552 if (this.mode !== 'command') {
552 if (this.mode !== 'command') {
553 this.command_mode();
553 this.command_mode();
554 }
554 }
555 this.get_cell(sindex).unselect();
555 this.get_cell(sindex).unselect();
556 }
556 }
557 var cell = this.get_cell(index);
557 var cell = this.get_cell(index);
558 cell.select();
558 cell.select();
559 if (cell.cell_type === 'heading') {
559 if (cell.cell_type === 'heading') {
560 this.events.trigger('selected_cell_type_changed.Notebook',
560 this.events.trigger('selected_cell_type_changed.Notebook',
561 {'cell_type':cell.cell_type,level:cell.level}
561 {'cell_type':cell.cell_type,level:cell.level}
562 );
562 );
563 } else {
563 } else {
564 this.events.trigger('selected_cell_type_changed.Notebook',
564 this.events.trigger('selected_cell_type_changed.Notebook',
565 {'cell_type':cell.cell_type}
565 {'cell_type':cell.cell_type}
566 );
566 );
567 }
567 }
568 }
568 }
569 return this;
569 return this;
570 };
570 };
571
571
572 /**
572 /**
573 * Programmatically select the next cell.
573 * Programmatically select the next cell.
574 *
574 *
575 * @method select_next
575 * @method select_next
576 * @return {Notebook} This notebook
576 * @return {Notebook} This notebook
577 */
577 */
578 Notebook.prototype.select_next = function () {
578 Notebook.prototype.select_next = function () {
579 var index = this.get_selected_index();
579 var index = this.get_selected_index();
580 this.select(index+1);
580 this.select(index+1);
581 return this;
581 return this;
582 };
582 };
583
583
584 /**
584 /**
585 * Programmatically select the previous cell.
585 * Programmatically select the previous cell.
586 *
586 *
587 * @method select_prev
587 * @method select_prev
588 * @return {Notebook} This notebook
588 * @return {Notebook} This notebook
589 */
589 */
590 Notebook.prototype.select_prev = function () {
590 Notebook.prototype.select_prev = function () {
591 var index = this.get_selected_index();
591 var index = this.get_selected_index();
592 this.select(index-1);
592 this.select(index-1);
593 return this;
593 return this;
594 };
594 };
595
595
596
596
597 // Edit/Command mode
597 // Edit/Command mode
598
598
599 /**
599 /**
600 * Gets the index of the cell that is in edit mode.
600 * Gets the index of the cell that is in edit mode.
601 *
601 *
602 * @method get_edit_index
602 * @method get_edit_index
603 *
603 *
604 * @return index {int}
604 * @return index {int}
605 **/
605 **/
606 Notebook.prototype.get_edit_index = function () {
606 Notebook.prototype.get_edit_index = function () {
607 var result = null;
607 var result = null;
608 this.get_cell_elements().filter(function (index) {
608 this.get_cell_elements().filter(function (index) {
609 if ($(this).data("cell").mode === 'edit') {
609 if ($(this).data("cell").mode === 'edit') {
610 result = index;
610 result = index;
611 }
611 }
612 });
612 });
613 return result;
613 return result;
614 };
614 };
615
615
616 /**
616 /**
617 * Handle when a a cell blurs and the notebook should enter command mode.
617 * Handle when a a cell blurs and the notebook should enter command mode.
618 *
618 *
619 * @method handle_command_mode
619 * @method handle_command_mode
620 * @param [cell] {Cell} Cell to enter command mode on.
620 * @param [cell] {Cell} Cell to enter command mode on.
621 **/
621 **/
622 Notebook.prototype.handle_command_mode = function (cell) {
622 Notebook.prototype.handle_command_mode = function (cell) {
623 if (this.mode !== 'command') {
623 if (this.mode !== 'command') {
624 cell.command_mode();
624 cell.command_mode();
625 this.mode = 'command';
625 this.mode = 'command';
626 this.events.trigger('command_mode.Notebook');
626 this.events.trigger('command_mode.Notebook');
627 this.keyboard_manager.command_mode();
627 this.keyboard_manager.command_mode();
628 }
628 }
629 };
629 };
630
630
631 /**
631 /**
632 * Make the notebook enter command mode.
632 * Make the notebook enter command mode.
633 *
633 *
634 * @method command_mode
634 * @method command_mode
635 **/
635 **/
636 Notebook.prototype.command_mode = function () {
636 Notebook.prototype.command_mode = function () {
637 var cell = this.get_cell(this.get_edit_index());
637 var cell = this.get_cell(this.get_edit_index());
638 if (cell && this.mode !== 'command') {
638 if (cell && this.mode !== 'command') {
639 // We don't call cell.command_mode, but rather call cell.focus_cell()
639 // We don't call cell.command_mode, but rather call cell.focus_cell()
640 // which will blur and CM editor and trigger the call to
640 // which will blur and CM editor and trigger the call to
641 // handle_command_mode.
641 // handle_command_mode.
642 cell.focus_cell();
642 cell.focus_cell();
643 }
643 }
644 };
644 };
645
645
646 /**
646 /**
647 * Handle when a cell fires it's edit_mode event.
647 * Handle when a cell fires it's edit_mode event.
648 *
648 *
649 * @method handle_edit_mode
649 * @method handle_edit_mode
650 * @param [cell] {Cell} Cell to enter edit mode on.
650 * @param [cell] {Cell} Cell to enter edit mode on.
651 **/
651 **/
652 Notebook.prototype.handle_edit_mode = function (cell) {
652 Notebook.prototype.handle_edit_mode = function (cell) {
653 if (cell && this.mode !== 'edit') {
653 if (cell && this.mode !== 'edit') {
654 cell.edit_mode();
654 cell.edit_mode();
655 this.mode = 'edit';
655 this.mode = 'edit';
656 this.events.trigger('edit_mode.Notebook');
656 this.events.trigger('edit_mode.Notebook');
657 this.keyboard_manager.edit_mode();
657 this.keyboard_manager.edit_mode();
658 }
658 }
659 };
659 };
660
660
661 /**
661 /**
662 * Make a cell enter edit mode.
662 * Make a cell enter edit mode.
663 *
663 *
664 * @method edit_mode
664 * @method edit_mode
665 **/
665 **/
666 Notebook.prototype.edit_mode = function () {
666 Notebook.prototype.edit_mode = function () {
667 var cell = this.get_selected_cell();
667 var cell = this.get_selected_cell();
668 if (cell && this.mode !== 'edit') {
668 if (cell && this.mode !== 'edit') {
669 cell.unrender();
669 cell.unrender();
670 cell.focus_editor();
670 cell.focus_editor();
671 }
671 }
672 };
672 };
673
673
674 /**
674 /**
675 * Focus the currently selected cell.
675 * Focus the currently selected cell.
676 *
676 *
677 * @method focus_cell
677 * @method focus_cell
678 **/
678 **/
679 Notebook.prototype.focus_cell = function () {
679 Notebook.prototype.focus_cell = function () {
680 var cell = this.get_selected_cell();
680 var cell = this.get_selected_cell();
681 if (cell === null) {return;} // No cell is selected
681 if (cell === null) {return;} // No cell is selected
682 cell.focus_cell();
682 cell.focus_cell();
683 };
683 };
684
684
685 // Cell movement
685 // Cell movement
686
686
687 /**
687 /**
688 * Move given (or selected) cell up and select it.
688 * Move given (or selected) cell up and select it.
689 *
689 *
690 * @method move_cell_up
690 * @method move_cell_up
691 * @param [index] {integer} cell index
691 * @param [index] {integer} cell index
692 * @return {Notebook} This notebook
692 * @return {Notebook} This notebook
693 **/
693 **/
694 Notebook.prototype.move_cell_up = function (index) {
694 Notebook.prototype.move_cell_up = function (index) {
695 var i = this.index_or_selected(index);
695 var i = this.index_or_selected(index);
696 if (this.is_valid_cell_index(i) && i > 0) {
696 if (this.is_valid_cell_index(i) && i > 0) {
697 var pivot = this.get_cell_element(i-1);
697 var pivot = this.get_cell_element(i-1);
698 var tomove = this.get_cell_element(i);
698 var tomove = this.get_cell_element(i);
699 if (pivot !== null && tomove !== null) {
699 if (pivot !== null && tomove !== null) {
700 tomove.detach();
700 tomove.detach();
701 pivot.before(tomove);
701 pivot.before(tomove);
702 this.select(i-1);
702 this.select(i-1);
703 var cell = this.get_selected_cell();
703 var cell = this.get_selected_cell();
704 cell.focus_cell();
704 cell.focus_cell();
705 }
705 }
706 this.set_dirty(true);
706 this.set_dirty(true);
707 }
707 }
708 return this;
708 return this;
709 };
709 };
710
710
711
711
712 /**
712 /**
713 * Move given (or selected) cell down and select it
713 * Move given (or selected) cell down and select it
714 *
714 *
715 * @method move_cell_down
715 * @method move_cell_down
716 * @param [index] {integer} cell index
716 * @param [index] {integer} cell index
717 * @return {Notebook} This notebook
717 * @return {Notebook} This notebook
718 **/
718 **/
719 Notebook.prototype.move_cell_down = function (index) {
719 Notebook.prototype.move_cell_down = function (index) {
720 var i = this.index_or_selected(index);
720 var i = this.index_or_selected(index);
721 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
721 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
722 var pivot = this.get_cell_element(i+1);
722 var pivot = this.get_cell_element(i+1);
723 var tomove = this.get_cell_element(i);
723 var tomove = this.get_cell_element(i);
724 if (pivot !== null && tomove !== null) {
724 if (pivot !== null && tomove !== null) {
725 tomove.detach();
725 tomove.detach();
726 pivot.after(tomove);
726 pivot.after(tomove);
727 this.select(i+1);
727 this.select(i+1);
728 var cell = this.get_selected_cell();
728 var cell = this.get_selected_cell();
729 cell.focus_cell();
729 cell.focus_cell();
730 }
730 }
731 }
731 }
732 this.set_dirty();
732 this.set_dirty();
733 return this;
733 return this;
734 };
734 };
735
735
736
736
737 // Insertion, deletion.
737 // Insertion, deletion.
738
738
739 /**
739 /**
740 * Delete a cell from the notebook.
740 * Delete a cell from the notebook.
741 *
741 *
742 * @method delete_cell
742 * @method delete_cell
743 * @param [index] A cell's numeric index
743 * @param [index] A cell's numeric index
744 * @return {Notebook} This notebook
744 * @return {Notebook} This notebook
745 */
745 */
746 Notebook.prototype.delete_cell = function (index) {
746 Notebook.prototype.delete_cell = function (index) {
747 var i = this.index_or_selected(index);
747 var i = this.index_or_selected(index);
748 var cell = this.get_cell(i);
748 var cell = this.get_cell(i);
749 if (!cell.is_deletable()) {
749 if (!cell.is_deletable()) {
750 return this;
750 return this;
751 }
751 }
752
752
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', 'raw'], defaults to 'code'
829 * @param [type] {string} in ['code','markdown', 'raw'], 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.default_cell_type;
840 type = type || this.default_cell_type;
841 if (type === 'above') {
841 if (type === 'above') {
842 if (index > 0) {
842 if (index > 0) {
843 type = this.get_cell(index-1).cell_type;
843 type = this.get_cell(index-1).cell_type;
844 } else {
844 } else {
845 type = 'code';
845 type = 'code';
846 }
846 }
847 } else if (type === 'below') {
847 } else if (type === 'below') {
848 if (index < ncells) {
848 if (index < ncells) {
849 type = this.get_cell(index).cell_type;
849 type = this.get_cell(index).cell_type;
850 } else {
850 } else {
851 type = 'code';
851 type = 'code';
852 }
852 }
853 } else if (type === 'selected') {
853 } else if (type === 'selected') {
854 type = this.get_selected_cell().cell_type;
854 type = this.get_selected_cell().cell_type;
855 }
855 }
856
856
857 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
857 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
858 var cell_options = {
858 var cell_options = {
859 events: this.events,
859 events: this.events,
860 config: this.config,
860 config: this.config,
861 keyboard_manager: this.keyboard_manager,
861 keyboard_manager: this.keyboard_manager,
862 notebook: this,
862 notebook: this,
863 tooltip: this.tooltip,
863 tooltip: this.tooltip,
864 };
864 };
865 switch(type) {
865 switch(type) {
866 case 'code':
866 case 'code':
867 cell = new codecell.CodeCell(this.kernel, cell_options);
867 cell = new codecell.CodeCell(this.kernel, cell_options);
868 cell.set_input_prompt();
868 cell.set_input_prompt();
869 break;
869 break;
870 case 'markdown':
870 case 'markdown':
871 cell = new textcell.MarkdownCell(cell_options);
871 cell = new textcell.MarkdownCell(cell_options);
872 break;
872 break;
873 case 'raw':
873 case 'raw':
874 cell = new textcell.RawCell(cell_options);
874 cell = new textcell.RawCell(cell_options);
875 break;
875 break;
876 default:
876 default:
877 console.log("invalid cell type: ", type);
877 console.log("invalid cell type: ", type);
878 }
878 }
879
879
880 if(this._insert_element_at_index(cell.element,index)) {
880 if(this._insert_element_at_index(cell.element,index)) {
881 cell.render();
881 cell.render();
882 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
882 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
883 cell.refresh();
883 cell.refresh();
884 // We used to select the cell after we refresh it, but there
884 // We used to select the cell after we refresh it, but there
885 // are now cases were this method is called where select is
885 // are now cases were this method is called where select is
886 // not appropriate. The selection logic should be handled by the
886 // not appropriate. The selection logic should be handled by the
887 // caller of the the top level insert_cell methods.
887 // caller of the the top level insert_cell methods.
888 this.set_dirty(true);
888 this.set_dirty(true);
889 }
889 }
890 }
890 }
891 return cell;
891 return cell;
892
892
893 };
893 };
894
894
895 /**
895 /**
896 * Insert an element at given cell index.
896 * Insert an element at given cell index.
897 *
897 *
898 * @method _insert_element_at_index
898 * @method _insert_element_at_index
899 * @param element {dom_element} a cell element
899 * @param element {dom_element} a cell element
900 * @param [index] {int} a valid index where to inser cell
900 * @param [index] {int} a valid index where to inser cell
901 * @private
901 * @private
902 *
902 *
903 * return true if everything whent fine.
903 * return true if everything whent fine.
904 **/
904 **/
905 Notebook.prototype._insert_element_at_index = function(element, index){
905 Notebook.prototype._insert_element_at_index = function(element, index){
906 if (element === undefined){
906 if (element === undefined){
907 return false;
907 return false;
908 }
908 }
909
909
910 var ncells = this.ncells();
910 var ncells = this.ncells();
911
911
912 if (ncells === 0) {
912 if (ncells === 0) {
913 // special case append if empty
913 // special case append if empty
914 this.element.find('div.end_space').before(element);
914 this.element.find('div.end_space').before(element);
915 } else if ( ncells === index ) {
915 } else if ( ncells === index ) {
916 // special case append it the end, but not empty
916 // special case append it the end, but not empty
917 this.get_cell_element(index-1).after(element);
917 this.get_cell_element(index-1).after(element);
918 } else if (this.is_valid_cell_index(index)) {
918 } else if (this.is_valid_cell_index(index)) {
919 // otherwise always somewhere to append to
919 // otherwise always somewhere to append to
920 this.get_cell_element(index).before(element);
920 this.get_cell_element(index).before(element);
921 } else {
921 } else {
922 return false;
922 return false;
923 }
923 }
924
924
925 if (this.undelete_index !== null && index <= this.undelete_index) {
925 if (this.undelete_index !== null && index <= this.undelete_index) {
926 this.undelete_index = this.undelete_index + 1;
926 this.undelete_index = this.undelete_index + 1;
927 this.set_dirty(true);
927 this.set_dirty(true);
928 }
928 }
929 return true;
929 return true;
930 };
930 };
931
931
932 /**
932 /**
933 * Insert a cell of given type above given index, or at top
933 * Insert a cell of given type above given index, or at top
934 * of notebook if index smaller than 0.
934 * of notebook if index smaller than 0.
935 *
935 *
936 * default index value is the one of currently selected cell
936 * default index value is the one of currently selected cell
937 *
937 *
938 * @method insert_cell_above
938 * @method insert_cell_above
939 * @param [type] {string} cell type
939 * @param [type] {string} cell type
940 * @param [index] {integer}
940 * @param [index] {integer}
941 *
941 *
942 * @return handle to created cell or null
942 * @return handle to created cell or null
943 **/
943 **/
944 Notebook.prototype.insert_cell_above = function (type, index) {
944 Notebook.prototype.insert_cell_above = function (type, index) {
945 index = this.index_or_selected(index);
945 index = this.index_or_selected(index);
946 return this.insert_cell_at_index(type, index);
946 return this.insert_cell_at_index(type, index);
947 };
947 };
948
948
949 /**
949 /**
950 * Insert a cell of given type below given index, or at bottom
950 * Insert a cell of given type below given index, or at bottom
951 * of notebook if index greater than number of cells
951 * of notebook if index greater than number of cells
952 *
952 *
953 * default index value is the one of currently selected cell
953 * default index value is the one of currently selected cell
954 *
954 *
955 * @method insert_cell_below
955 * @method insert_cell_below
956 * @param [type] {string} cell type
956 * @param [type] {string} cell type
957 * @param [index] {integer}
957 * @param [index] {integer}
958 *
958 *
959 * @return handle to created cell or null
959 * @return handle to created cell or null
960 *
960 *
961 **/
961 **/
962 Notebook.prototype.insert_cell_below = function (type, index) {
962 Notebook.prototype.insert_cell_below = function (type, index) {
963 index = this.index_or_selected(index);
963 index = this.index_or_selected(index);
964 return this.insert_cell_at_index(type, index+1);
964 return this.insert_cell_at_index(type, index+1);
965 };
965 };
966
966
967
967
968 /**
968 /**
969 * Insert cell at end of notebook
969 * Insert cell at end of notebook
970 *
970 *
971 * @method insert_cell_at_bottom
971 * @method insert_cell_at_bottom
972 * @param {String} type cell type
972 * @param {String} type cell type
973 *
973 *
974 * @return the added cell; or null
974 * @return the added cell; or null
975 **/
975 **/
976 Notebook.prototype.insert_cell_at_bottom = function (type){
976 Notebook.prototype.insert_cell_at_bottom = function (type){
977 var len = this.ncells();
977 var len = this.ncells();
978 return this.insert_cell_below(type,len-1);
978 return this.insert_cell_below(type,len-1);
979 };
979 };
980
980
981 /**
981 /**
982 * Turn a cell into a code cell.
982 * Turn a cell into a code cell.
983 *
983 *
984 * @method to_code
984 * @method to_code
985 * @param {Number} [index] A cell's index
985 * @param {Number} [index] A cell's index
986 */
986 */
987 Notebook.prototype.to_code = function (index) {
987 Notebook.prototype.to_code = function (index) {
988 var i = this.index_or_selected(index);
988 var i = this.index_or_selected(index);
989 if (this.is_valid_cell_index(i)) {
989 if (this.is_valid_cell_index(i)) {
990 var source_cell = this.get_cell(i);
990 var source_cell = this.get_cell(i);
991 if (!(source_cell instanceof codecell.CodeCell)) {
991 if (!(source_cell instanceof codecell.CodeCell)) {
992 var target_cell = this.insert_cell_below('code',i);
992 var target_cell = this.insert_cell_below('code',i);
993 var text = source_cell.get_text();
993 var text = source_cell.get_text();
994 if (text === source_cell.placeholder) {
994 if (text === source_cell.placeholder) {
995 text = '';
995 text = '';
996 }
996 }
997 //metadata
997 //metadata
998 target_cell.metadata = source_cell.metadata;
998 target_cell.metadata = source_cell.metadata;
999
999
1000 target_cell.set_text(text);
1000 target_cell.set_text(text);
1001 // make this value the starting point, so that we can only undo
1001 // make this value the starting point, so that we can only undo
1002 // to this state, instead of a blank cell
1002 // to this state, instead of a blank cell
1003 target_cell.code_mirror.clearHistory();
1003 target_cell.code_mirror.clearHistory();
1004 source_cell.element.remove();
1004 source_cell.element.remove();
1005 this.select(i);
1005 this.select(i);
1006 var cursor = source_cell.code_mirror.getCursor();
1006 var cursor = source_cell.code_mirror.getCursor();
1007 target_cell.code_mirror.setCursor(cursor);
1007 target_cell.code_mirror.setCursor(cursor);
1008 this.set_dirty(true);
1008 this.set_dirty(true);
1009 }
1009 }
1010 }
1010 }
1011 };
1011 };
1012
1012
1013 /**
1013 /**
1014 * Turn a cell into a Markdown cell.
1014 * Turn a cell into a Markdown cell.
1015 *
1015 *
1016 * @method to_markdown
1016 * @method to_markdown
1017 * @param {Number} [index] A cell's index
1017 * @param {Number} [index] A cell's index
1018 */
1018 */
1019 Notebook.prototype.to_markdown = function (index) {
1019 Notebook.prototype.to_markdown = function (index) {
1020 var i = this.index_or_selected(index);
1020 var i = this.index_or_selected(index);
1021 if (this.is_valid_cell_index(i)) {
1021 if (this.is_valid_cell_index(i)) {
1022 var source_cell = this.get_cell(i);
1022 var source_cell = this.get_cell(i);
1023
1023
1024 if (!(source_cell instanceof textcell.MarkdownCell)) {
1024 if (!(source_cell instanceof textcell.MarkdownCell)) {
1025 var target_cell = this.insert_cell_below('markdown',i);
1025 var target_cell = this.insert_cell_below('markdown',i);
1026 var text = source_cell.get_text();
1026 var text = source_cell.get_text();
1027
1027
1028 if (text === source_cell.placeholder) {
1028 if (text === source_cell.placeholder) {
1029 text = '';
1029 text = '';
1030 }
1030 }
1031 // metadata
1031 // metadata
1032 target_cell.metadata = source_cell.metadata;
1032 target_cell.metadata = source_cell.metadata;
1033 // We must show the editor before setting its contents
1033 // We must show the editor before setting its contents
1034 target_cell.unrender();
1034 target_cell.unrender();
1035 target_cell.set_text(text);
1035 target_cell.set_text(text);
1036 // make this value the starting point, so that we can only undo
1036 // make this value the starting point, so that we can only undo
1037 // to this state, instead of a blank cell
1037 // to this state, instead of a blank cell
1038 target_cell.code_mirror.clearHistory();
1038 target_cell.code_mirror.clearHistory();
1039 source_cell.element.remove();
1039 source_cell.element.remove();
1040 this.select(i);
1040 this.select(i);
1041 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1041 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1042 target_cell.render();
1042 target_cell.render();
1043 }
1043 }
1044 var cursor = source_cell.code_mirror.getCursor();
1044 var cursor = source_cell.code_mirror.getCursor();
1045 target_cell.code_mirror.setCursor(cursor);
1045 target_cell.code_mirror.setCursor(cursor);
1046 this.set_dirty(true);
1046 this.set_dirty(true);
1047 }
1047 }
1048 }
1048 }
1049 };
1049 };
1050
1050
1051 /**
1051 /**
1052 * Turn a cell into a raw text cell.
1052 * Turn a cell into a raw text cell.
1053 *
1053 *
1054 * @method to_raw
1054 * @method to_raw
1055 * @param {Number} [index] A cell's index
1055 * @param {Number} [index] A cell's index
1056 */
1056 */
1057 Notebook.prototype.to_raw = function (index) {
1057 Notebook.prototype.to_raw = function (index) {
1058 var i = this.index_or_selected(index);
1058 var i = this.index_or_selected(index);
1059 if (this.is_valid_cell_index(i)) {
1059 if (this.is_valid_cell_index(i)) {
1060 var target_cell = null;
1060 var target_cell = null;
1061 var source_cell = this.get_cell(i);
1061 var source_cell = this.get_cell(i);
1062
1062
1063 if (!(source_cell instanceof textcell.RawCell)) {
1063 if (!(source_cell instanceof textcell.RawCell)) {
1064 target_cell = this.insert_cell_below('raw',i);
1064 target_cell = this.insert_cell_below('raw',i);
1065 var text = source_cell.get_text();
1065 var text = source_cell.get_text();
1066 if (text === source_cell.placeholder) {
1066 if (text === source_cell.placeholder) {
1067 text = '';
1067 text = '';
1068 }
1068 }
1069 //metadata
1069 //metadata
1070 target_cell.metadata = source_cell.metadata;
1070 target_cell.metadata = source_cell.metadata;
1071 // We must show the editor before setting its contents
1071 // We must show the editor before setting its contents
1072 target_cell.unrender();
1072 target_cell.unrender();
1073 target_cell.set_text(text);
1073 target_cell.set_text(text);
1074 // make this value the starting point, so that we can only undo
1074 // make this value the starting point, so that we can only undo
1075 // to this state, instead of a blank cell
1075 // to this state, instead of a blank cell
1076 target_cell.code_mirror.clearHistory();
1076 target_cell.code_mirror.clearHistory();
1077 source_cell.element.remove();
1077 source_cell.element.remove();
1078 this.select(i);
1078 this.select(i);
1079 var cursor = source_cell.code_mirror.getCursor();
1079 var cursor = source_cell.code_mirror.getCursor();
1080 target_cell.code_mirror.setCursor(cursor);
1080 target_cell.code_mirror.setCursor(cursor);
1081 this.set_dirty(true);
1081 this.set_dirty(true);
1082 }
1082 }
1083 }
1083 }
1084 };
1084 };
1085
1085
1086 Notebook.prototype._warn_heading = function () {
1086 Notebook.prototype._warn_heading = function () {
1087 // warn about heading cells being removed
1087 // warn about heading cells being removed
1088 dialog.modal({
1088 dialog.modal({
1089 notebook: this,
1089 notebook: this,
1090 keyboard_manager: this.keyboard_manager,
1090 keyboard_manager: this.keyboard_manager,
1091 title : "Use markdown headings",
1091 title : "Use markdown headings",
1092 body : $("<p/>").text(
1092 body : $("<p/>").text(
1093 'IPython no longer uses special heading cells. ' +
1093 'IPython no longer uses special heading cells. ' +
1094 'Instead, write your headings in Markdown cells using # characters:'
1094 'Instead, write your headings in Markdown cells using # characters:'
1095 ).append($('<pre/>').text(
1095 ).append($('<pre/>').text(
1096 '## This is a level 2 heading'
1096 '## This is a level 2 heading'
1097 )),
1097 )),
1098 buttons : {
1098 buttons : {
1099 "OK" : {},
1099 "OK" : {},
1100 }
1100 }
1101 });
1101 });
1102 };
1102 };
1103
1103
1104 /**
1104 /**
1105 * Turn a cell into a markdown cell with a heading.
1105 * Turn a cell into a markdown cell with a heading.
1106 *
1106 *
1107 * @method to_heading
1107 * @method to_heading
1108 * @param {Number} [index] A cell's index
1108 * @param {Number} [index] A cell's index
1109 * @param {Number} [level] A heading level (e.g., 1 for h1)
1109 * @param {Number} [level] A heading level (e.g., 1 for h1)
1110 */
1110 */
1111 Notebook.prototype.to_heading = function (index, level) {
1111 Notebook.prototype.to_heading = function (index, level) {
1112 this.to_markdown(index);
1112 this.to_markdown(index);
1113 level = level || 1;
1113 level = level || 1;
1114 var i = this.index_or_selected(index);
1114 var i = this.index_or_selected(index);
1115 if (this.is_valid_cell_index(i)) {
1115 if (this.is_valid_cell_index(i)) {
1116 var cell = this.get_cell(i);
1116 var cell = this.get_cell(i);
1117 cell.set_heading_level(level);
1117 cell.set_heading_level(level);
1118 this.set_dirty(true);
1118 this.set_dirty(true);
1119 }
1119 }
1120 };
1120 };
1121
1121
1122
1122
1123 // Cut/Copy/Paste
1123 // Cut/Copy/Paste
1124
1124
1125 /**
1125 /**
1126 * Enable UI elements for pasting cells.
1126 * Enable UI elements for pasting cells.
1127 *
1127 *
1128 * @method enable_paste
1128 * @method enable_paste
1129 */
1129 */
1130 Notebook.prototype.enable_paste = function () {
1130 Notebook.prototype.enable_paste = function () {
1131 var that = this;
1131 var that = this;
1132 if (!this.paste_enabled) {
1132 if (!this.paste_enabled) {
1133 $('#paste_cell_replace').removeClass('disabled')
1133 $('#paste_cell_replace').removeClass('disabled')
1134 .on('click', function () {that.paste_cell_replace();});
1134 .on('click', function () {that.paste_cell_replace();});
1135 $('#paste_cell_above').removeClass('disabled')
1135 $('#paste_cell_above').removeClass('disabled')
1136 .on('click', function () {that.paste_cell_above();});
1136 .on('click', function () {that.paste_cell_above();});
1137 $('#paste_cell_below').removeClass('disabled')
1137 $('#paste_cell_below').removeClass('disabled')
1138 .on('click', function () {that.paste_cell_below();});
1138 .on('click', function () {that.paste_cell_below();});
1139 this.paste_enabled = true;
1139 this.paste_enabled = true;
1140 }
1140 }
1141 };
1141 };
1142
1142
1143 /**
1143 /**
1144 * Disable UI elements for pasting cells.
1144 * Disable UI elements for pasting cells.
1145 *
1145 *
1146 * @method disable_paste
1146 * @method disable_paste
1147 */
1147 */
1148 Notebook.prototype.disable_paste = function () {
1148 Notebook.prototype.disable_paste = function () {
1149 if (this.paste_enabled) {
1149 if (this.paste_enabled) {
1150 $('#paste_cell_replace').addClass('disabled').off('click');
1150 $('#paste_cell_replace').addClass('disabled').off('click');
1151 $('#paste_cell_above').addClass('disabled').off('click');
1151 $('#paste_cell_above').addClass('disabled').off('click');
1152 $('#paste_cell_below').addClass('disabled').off('click');
1152 $('#paste_cell_below').addClass('disabled').off('click');
1153 this.paste_enabled = false;
1153 this.paste_enabled = false;
1154 }
1154 }
1155 };
1155 };
1156
1156
1157 /**
1157 /**
1158 * Cut a cell.
1158 * Cut a cell.
1159 *
1159 *
1160 * @method cut_cell
1160 * @method cut_cell
1161 */
1161 */
1162 Notebook.prototype.cut_cell = function () {
1162 Notebook.prototype.cut_cell = function () {
1163 this.copy_cell();
1163 this.copy_cell();
1164 this.delete_cell();
1164 this.delete_cell();
1165 };
1165 };
1166
1166
1167 /**
1167 /**
1168 * Copy a cell.
1168 * Copy a cell.
1169 *
1169 *
1170 * @method copy_cell
1170 * @method copy_cell
1171 */
1171 */
1172 Notebook.prototype.copy_cell = function () {
1172 Notebook.prototype.copy_cell = function () {
1173 var cell = this.get_selected_cell();
1173 var cell = this.get_selected_cell();
1174 this.clipboard = cell.toJSON();
1174 this.clipboard = cell.toJSON();
1175 // remove undeletable status from the copied cell
1175 // remove undeletable status from the copied cell
1176 if (this.clipboard.metadata.deletable !== undefined) {
1176 if (this.clipboard.metadata.deletable !== undefined) {
1177 delete this.clipboard.metadata.deletable;
1177 delete this.clipboard.metadata.deletable;
1178 }
1178 }
1179 this.enable_paste();
1179 this.enable_paste();
1180 };
1180 };
1181
1181
1182 /**
1182 /**
1183 * Replace the selected cell with a cell in the clipboard.
1183 * Replace the selected cell with a cell in the clipboard.
1184 *
1184 *
1185 * @method paste_cell_replace
1185 * @method paste_cell_replace
1186 */
1186 */
1187 Notebook.prototype.paste_cell_replace = function () {
1187 Notebook.prototype.paste_cell_replace = function () {
1188 if (this.clipboard !== null && this.paste_enabled) {
1188 if (this.clipboard !== null && this.paste_enabled) {
1189 var cell_data = this.clipboard;
1189 var cell_data = this.clipboard;
1190 var new_cell = this.insert_cell_above(cell_data.cell_type);
1190 var new_cell = this.insert_cell_above(cell_data.cell_type);
1191 new_cell.fromJSON(cell_data);
1191 new_cell.fromJSON(cell_data);
1192 var old_cell = this.get_next_cell(new_cell);
1192 var old_cell = this.get_next_cell(new_cell);
1193 this.delete_cell(this.find_cell_index(old_cell));
1193 this.delete_cell(this.find_cell_index(old_cell));
1194 this.select(this.find_cell_index(new_cell));
1194 this.select(this.find_cell_index(new_cell));
1195 }
1195 }
1196 };
1196 };
1197
1197
1198 /**
1198 /**
1199 * Paste a cell from the clipboard above the selected cell.
1199 * Paste a cell from the clipboard above the selected cell.
1200 *
1200 *
1201 * @method paste_cell_above
1201 * @method paste_cell_above
1202 */
1202 */
1203 Notebook.prototype.paste_cell_above = function () {
1203 Notebook.prototype.paste_cell_above = function () {
1204 if (this.clipboard !== null && this.paste_enabled) {
1204 if (this.clipboard !== null && this.paste_enabled) {
1205 var cell_data = this.clipboard;
1205 var cell_data = this.clipboard;
1206 var new_cell = this.insert_cell_above(cell_data.cell_type);
1206 var new_cell = this.insert_cell_above(cell_data.cell_type);
1207 new_cell.fromJSON(cell_data);
1207 new_cell.fromJSON(cell_data);
1208 new_cell.focus_cell();
1208 new_cell.focus_cell();
1209 }
1209 }
1210 };
1210 };
1211
1211
1212 /**
1212 /**
1213 * Paste a cell from the clipboard below the selected cell.
1213 * Paste a cell from the clipboard below the selected cell.
1214 *
1214 *
1215 * @method paste_cell_below
1215 * @method paste_cell_below
1216 */
1216 */
1217 Notebook.prototype.paste_cell_below = function () {
1217 Notebook.prototype.paste_cell_below = function () {
1218 if (this.clipboard !== null && this.paste_enabled) {
1218 if (this.clipboard !== null && this.paste_enabled) {
1219 var cell_data = this.clipboard;
1219 var cell_data = this.clipboard;
1220 var new_cell = this.insert_cell_below(cell_data.cell_type);
1220 var new_cell = this.insert_cell_below(cell_data.cell_type);
1221 new_cell.fromJSON(cell_data);
1221 new_cell.fromJSON(cell_data);
1222 new_cell.focus_cell();
1222 new_cell.focus_cell();
1223 }
1223 }
1224 };
1224 };
1225
1225
1226 // Split/merge
1226 // Split/merge
1227
1227
1228 /**
1228 /**
1229 * Split the selected cell into two, at the cursor.
1229 * Split the selected cell into two, at the cursor.
1230 *
1230 *
1231 * @method split_cell
1231 * @method split_cell
1232 */
1232 */
1233 Notebook.prototype.split_cell = function () {
1233 Notebook.prototype.split_cell = function () {
1234 var cell = this.get_selected_cell();
1234 var cell = this.get_selected_cell();
1235 if (cell.is_splittable()) {
1235 if (cell.is_splittable()) {
1236 var texta = cell.get_pre_cursor();
1236 var texta = cell.get_pre_cursor();
1237 var textb = cell.get_post_cursor();
1237 var textb = cell.get_post_cursor();
1238 cell.set_text(textb);
1238 cell.set_text(textb);
1239 var new_cell = this.insert_cell_above(cell.cell_type);
1239 var new_cell = this.insert_cell_above(cell.cell_type);
1240 // Unrender the new cell so we can call set_text.
1240 // Unrender the new cell so we can call set_text.
1241 new_cell.unrender();
1241 new_cell.unrender();
1242 new_cell.set_text(texta);
1242 new_cell.set_text(texta);
1243 }
1243 }
1244 };
1244 };
1245
1245
1246 /**
1246 /**
1247 * Combine the selected cell into the cell above it.
1247 * Combine the selected cell into the cell above it.
1248 *
1248 *
1249 * @method merge_cell_above
1249 * @method merge_cell_above
1250 */
1250 */
1251 Notebook.prototype.merge_cell_above = function () {
1251 Notebook.prototype.merge_cell_above = function () {
1252 var index = this.get_selected_index();
1252 var index = this.get_selected_index();
1253 var cell = this.get_cell(index);
1253 var cell = this.get_cell(index);
1254 var render = cell.rendered;
1254 var render = cell.rendered;
1255 if (!cell.is_mergeable()) {
1255 if (!cell.is_mergeable()) {
1256 return;
1256 return;
1257 }
1257 }
1258 if (index > 0) {
1258 if (index > 0) {
1259 var upper_cell = this.get_cell(index-1);
1259 var upper_cell = this.get_cell(index-1);
1260 if (!upper_cell.is_mergeable()) {
1260 if (!upper_cell.is_mergeable()) {
1261 return;
1261 return;
1262 }
1262 }
1263 var upper_text = upper_cell.get_text();
1263 var upper_text = upper_cell.get_text();
1264 var text = cell.get_text();
1264 var text = cell.get_text();
1265 if (cell instanceof codecell.CodeCell) {
1265 if (cell instanceof codecell.CodeCell) {
1266 cell.set_text(upper_text+'\n'+text);
1266 cell.set_text(upper_text+'\n'+text);
1267 } else {
1267 } else {
1268 cell.unrender(); // Must unrender before we set_text.
1268 cell.unrender(); // Must unrender before we set_text.
1269 cell.set_text(upper_text+'\n\n'+text);
1269 cell.set_text(upper_text+'\n\n'+text);
1270 if (render) {
1270 if (render) {
1271 // The rendered state of the final cell should match
1271 // The rendered state of the final cell should match
1272 // that of the original selected cell;
1272 // that of the original selected cell;
1273 cell.render();
1273 cell.render();
1274 }
1274 }
1275 }
1275 }
1276 this.delete_cell(index-1);
1276 this.delete_cell(index-1);
1277 this.select(this.find_cell_index(cell));
1277 this.select(this.find_cell_index(cell));
1278 }
1278 }
1279 };
1279 };
1280
1280
1281 /**
1281 /**
1282 * Combine the selected cell into the cell below it.
1282 * Combine the selected cell into the cell below it.
1283 *
1283 *
1284 * @method merge_cell_below
1284 * @method merge_cell_below
1285 */
1285 */
1286 Notebook.prototype.merge_cell_below = function () {
1286 Notebook.prototype.merge_cell_below = function () {
1287 var index = this.get_selected_index();
1287 var index = this.get_selected_index();
1288 var cell = this.get_cell(index);
1288 var cell = this.get_cell(index);
1289 var render = cell.rendered;
1289 var render = cell.rendered;
1290 if (!cell.is_mergeable()) {
1290 if (!cell.is_mergeable()) {
1291 return;
1291 return;
1292 }
1292 }
1293 if (index < this.ncells()-1) {
1293 if (index < this.ncells()-1) {
1294 var lower_cell = this.get_cell(index+1);
1294 var lower_cell = this.get_cell(index+1);
1295 if (!lower_cell.is_mergeable()) {
1295 if (!lower_cell.is_mergeable()) {
1296 return;
1296 return;
1297 }
1297 }
1298 var lower_text = lower_cell.get_text();
1298 var lower_text = lower_cell.get_text();
1299 var text = cell.get_text();
1299 var text = cell.get_text();
1300 if (cell instanceof codecell.CodeCell) {
1300 if (cell instanceof codecell.CodeCell) {
1301 cell.set_text(text+'\n'+lower_text);
1301 cell.set_text(text+'\n'+lower_text);
1302 } else {
1302 } else {
1303 cell.unrender(); // Must unrender before we set_text.
1303 cell.unrender(); // Must unrender before we set_text.
1304 cell.set_text(text+'\n\n'+lower_text);
1304 cell.set_text(text+'\n\n'+lower_text);
1305 if (render) {
1305 if (render) {
1306 // The rendered state of the final cell should match
1306 // The rendered state of the final cell should match
1307 // that of the original selected cell;
1307 // that of the original selected cell;
1308 cell.render();
1308 cell.render();
1309 }
1309 }
1310 }
1310 }
1311 this.delete_cell(index+1);
1311 this.delete_cell(index+1);
1312 this.select(this.find_cell_index(cell));
1312 this.select(this.find_cell_index(cell));
1313 }
1313 }
1314 };
1314 };
1315
1315
1316
1316
1317 // Cell collapsing and output clearing
1317 // Cell collapsing and output clearing
1318
1318
1319 /**
1319 /**
1320 * Hide a cell's output.
1320 * Hide a cell's output.
1321 *
1321 *
1322 * @method collapse_output
1322 * @method collapse_output
1323 * @param {Number} index A cell's numeric index
1323 * @param {Number} index A cell's numeric index
1324 */
1324 */
1325 Notebook.prototype.collapse_output = function (index) {
1325 Notebook.prototype.collapse_output = function (index) {
1326 var i = this.index_or_selected(index);
1326 var i = this.index_or_selected(index);
1327 var cell = this.get_cell(i);
1327 var cell = this.get_cell(i);
1328 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1328 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1329 cell.collapse_output();
1329 cell.collapse_output();
1330 this.set_dirty(true);
1330 this.set_dirty(true);
1331 }
1331 }
1332 };
1332 };
1333
1333
1334 /**
1334 /**
1335 * Hide each code cell's output area.
1335 * Hide each code cell's output area.
1336 *
1336 *
1337 * @method collapse_all_output
1337 * @method collapse_all_output
1338 */
1338 */
1339 Notebook.prototype.collapse_all_output = function () {
1339 Notebook.prototype.collapse_all_output = function () {
1340 this.get_cells().map(function (cell, i) {
1340 this.get_cells().map(function (cell, i) {
1341 if (cell instanceof codecell.CodeCell) {
1341 if (cell instanceof codecell.CodeCell) {
1342 cell.collapse_output();
1342 cell.collapse_output();
1343 }
1343 }
1344 });
1344 });
1345 // this should not be set if the `collapse` key is removed from nbformat
1345 // this should not be set if the `collapse` key is removed from nbformat
1346 this.set_dirty(true);
1346 this.set_dirty(true);
1347 };
1347 };
1348
1348
1349 /**
1349 /**
1350 * Show a cell's output.
1350 * Show a cell's output.
1351 *
1351 *
1352 * @method expand_output
1352 * @method expand_output
1353 * @param {Number} index A cell's numeric index
1353 * @param {Number} index A cell's numeric index
1354 */
1354 */
1355 Notebook.prototype.expand_output = function (index) {
1355 Notebook.prototype.expand_output = function (index) {
1356 var i = this.index_or_selected(index);
1356 var i = this.index_or_selected(index);
1357 var cell = this.get_cell(i);
1357 var cell = this.get_cell(i);
1358 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1358 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1359 cell.expand_output();
1359 cell.expand_output();
1360 this.set_dirty(true);
1360 this.set_dirty(true);
1361 }
1361 }
1362 };
1362 };
1363
1363
1364 /**
1364 /**
1365 * Expand each code cell's output area, and remove scrollbars.
1365 * Expand each code cell's output area, and remove scrollbars.
1366 *
1366 *
1367 * @method expand_all_output
1367 * @method expand_all_output
1368 */
1368 */
1369 Notebook.prototype.expand_all_output = function () {
1369 Notebook.prototype.expand_all_output = function () {
1370 this.get_cells().map(function (cell, i) {
1370 this.get_cells().map(function (cell, i) {
1371 if (cell instanceof codecell.CodeCell) {
1371 if (cell instanceof codecell.CodeCell) {
1372 cell.expand_output();
1372 cell.expand_output();
1373 }
1373 }
1374 });
1374 });
1375 // this should not be set if the `collapse` key is removed from nbformat
1375 // this should not be set if the `collapse` key is removed from nbformat
1376 this.set_dirty(true);
1376 this.set_dirty(true);
1377 };
1377 };
1378
1378
1379 /**
1379 /**
1380 * Clear the selected CodeCell's output area.
1380 * Clear the selected CodeCell's output area.
1381 *
1381 *
1382 * @method clear_output
1382 * @method clear_output
1383 * @param {Number} index A cell's numeric index
1383 * @param {Number} index A cell's numeric index
1384 */
1384 */
1385 Notebook.prototype.clear_output = function (index) {
1385 Notebook.prototype.clear_output = function (index) {
1386 var i = this.index_or_selected(index);
1386 var i = this.index_or_selected(index);
1387 var cell = this.get_cell(i);
1387 var cell = this.get_cell(i);
1388 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1388 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1389 cell.clear_output();
1389 cell.clear_output();
1390 this.set_dirty(true);
1390 this.set_dirty(true);
1391 }
1391 }
1392 };
1392 };
1393
1393
1394 /**
1394 /**
1395 * Clear each code cell's output area.
1395 * Clear each code cell's output area.
1396 *
1396 *
1397 * @method clear_all_output
1397 * @method clear_all_output
1398 */
1398 */
1399 Notebook.prototype.clear_all_output = function () {
1399 Notebook.prototype.clear_all_output = function () {
1400 this.get_cells().map(function (cell, i) {
1400 this.get_cells().map(function (cell, i) {
1401 if (cell instanceof codecell.CodeCell) {
1401 if (cell instanceof codecell.CodeCell) {
1402 cell.clear_output();
1402 cell.clear_output();
1403 }
1403 }
1404 });
1404 });
1405 this.set_dirty(true);
1405 this.set_dirty(true);
1406 };
1406 };
1407
1407
1408 /**
1408 /**
1409 * Scroll the selected CodeCell's output area.
1409 * Scroll the selected CodeCell's output area.
1410 *
1410 *
1411 * @method scroll_output
1411 * @method scroll_output
1412 * @param {Number} index A cell's numeric index
1412 * @param {Number} index A cell's numeric index
1413 */
1413 */
1414 Notebook.prototype.scroll_output = function (index) {
1414 Notebook.prototype.scroll_output = function (index) {
1415 var i = this.index_or_selected(index);
1415 var i = this.index_or_selected(index);
1416 var cell = this.get_cell(i);
1416 var cell = this.get_cell(i);
1417 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1417 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1418 cell.scroll_output();
1418 cell.scroll_output();
1419 this.set_dirty(true);
1419 this.set_dirty(true);
1420 }
1420 }
1421 };
1421 };
1422
1422
1423 /**
1423 /**
1424 * Expand each code cell's output area, and add a scrollbar for long output.
1424 * Expand each code cell's output area, and add a scrollbar for long output.
1425 *
1425 *
1426 * @method scroll_all_output
1426 * @method scroll_all_output
1427 */
1427 */
1428 Notebook.prototype.scroll_all_output = function () {
1428 Notebook.prototype.scroll_all_output = function () {
1429 this.get_cells().map(function (cell, i) {
1429 this.get_cells().map(function (cell, i) {
1430 if (cell instanceof codecell.CodeCell) {
1430 if (cell instanceof codecell.CodeCell) {
1431 cell.scroll_output();
1431 cell.scroll_output();
1432 }
1432 }
1433 });
1433 });
1434 // this should not be set if the `collapse` key is removed from nbformat
1434 // this should not be set if the `collapse` key is removed from nbformat
1435 this.set_dirty(true);
1435 this.set_dirty(true);
1436 };
1436 };
1437
1437
1438 /** Toggle whether a cell's output is collapsed or expanded.
1438 /** Toggle whether a cell's output is collapsed or expanded.
1439 *
1439 *
1440 * @method toggle_output
1440 * @method toggle_output
1441 * @param {Number} index A cell's numeric index
1441 * @param {Number} index A cell's numeric index
1442 */
1442 */
1443 Notebook.prototype.toggle_output = function (index) {
1443 Notebook.prototype.toggle_output = function (index) {
1444 var i = this.index_or_selected(index);
1444 var i = this.index_or_selected(index);
1445 var cell = this.get_cell(i);
1445 var cell = this.get_cell(i);
1446 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1446 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1447 cell.toggle_output();
1447 cell.toggle_output();
1448 this.set_dirty(true);
1448 this.set_dirty(true);
1449 }
1449 }
1450 };
1450 };
1451
1451
1452 /**
1452 /**
1453 * Hide/show the output of all cells.
1453 * Hide/show the output of all cells.
1454 *
1454 *
1455 * @method toggle_all_output
1455 * @method toggle_all_output
1456 */
1456 */
1457 Notebook.prototype.toggle_all_output = function () {
1457 Notebook.prototype.toggle_all_output = function () {
1458 this.get_cells().map(function (cell, i) {
1458 this.get_cells().map(function (cell, i) {
1459 if (cell instanceof codecell.CodeCell) {
1459 if (cell instanceof codecell.CodeCell) {
1460 cell.toggle_output();
1460 cell.toggle_output();
1461 }
1461 }
1462 });
1462 });
1463 // this should not be set if the `collapse` key is removed from nbformat
1463 // this should not be set if the `collapse` key is removed from nbformat
1464 this.set_dirty(true);
1464 this.set_dirty(true);
1465 };
1465 };
1466
1466
1467 /**
1467 /**
1468 * Toggle a scrollbar for long cell outputs.
1468 * Toggle a scrollbar for long cell outputs.
1469 *
1469 *
1470 * @method toggle_output_scroll
1470 * @method toggle_output_scroll
1471 * @param {Number} index A cell's numeric index
1471 * @param {Number} index A cell's numeric index
1472 */
1472 */
1473 Notebook.prototype.toggle_output_scroll = function (index) {
1473 Notebook.prototype.toggle_output_scroll = function (index) {
1474 var i = this.index_or_selected(index);
1474 var i = this.index_or_selected(index);
1475 var cell = this.get_cell(i);
1475 var cell = this.get_cell(i);
1476 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1476 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1477 cell.toggle_output_scroll();
1477 cell.toggle_output_scroll();
1478 this.set_dirty(true);
1478 this.set_dirty(true);
1479 }
1479 }
1480 };
1480 };
1481
1481
1482 /**
1482 /**
1483 * Toggle the scrolling of long output on all cells.
1483 * Toggle the scrolling of long output on all cells.
1484 *
1484 *
1485 * @method toggle_all_output_scrolling
1485 * @method toggle_all_output_scrolling
1486 */
1486 */
1487 Notebook.prototype.toggle_all_output_scroll = function () {
1487 Notebook.prototype.toggle_all_output_scroll = function () {
1488 this.get_cells().map(function (cell, i) {
1488 this.get_cells().map(function (cell, i) {
1489 if (cell instanceof codecell.CodeCell) {
1489 if (cell instanceof codecell.CodeCell) {
1490 cell.toggle_output_scroll();
1490 cell.toggle_output_scroll();
1491 }
1491 }
1492 });
1492 });
1493 // this should not be set if the `collapse` key is removed from nbformat
1493 // this should not be set if the `collapse` key is removed from nbformat
1494 this.set_dirty(true);
1494 this.set_dirty(true);
1495 };
1495 };
1496
1496
1497 // Other cell functions: line numbers, ...
1497 // Other cell functions: line numbers, ...
1498
1498
1499 /**
1499 /**
1500 * Toggle line numbers in the selected cell's input area.
1500 * Toggle line numbers in the selected cell's input area.
1501 *
1501 *
1502 * @method cell_toggle_line_numbers
1502 * @method cell_toggle_line_numbers
1503 */
1503 */
1504 Notebook.prototype.cell_toggle_line_numbers = function() {
1504 Notebook.prototype.cell_toggle_line_numbers = function() {
1505 this.get_selected_cell().toggle_line_numbers();
1505 this.get_selected_cell().toggle_line_numbers();
1506 };
1506 };
1507
1507
1508 /**
1508 /**
1509 * Set the codemirror mode for all code cells, including the default for
1509 * Set the codemirror mode for all code cells, including the default for
1510 * new code cells.
1510 * new code cells.
1511 *
1511 *
1512 * @method set_codemirror_mode
1512 * @method set_codemirror_mode
1513 */
1513 */
1514 Notebook.prototype.set_codemirror_mode = function(newmode){
1514 Notebook.prototype.set_codemirror_mode = function(newmode){
1515 if (newmode === this.codemirror_mode) {
1515 if (newmode === this.codemirror_mode) {
1516 return;
1516 return;
1517 }
1517 }
1518 this.codemirror_mode = newmode;
1518 this.codemirror_mode = newmode;
1519 codecell.CodeCell.options_default.cm_config.mode = newmode;
1519 codecell.CodeCell.options_default.cm_config.mode = newmode;
1520 var modename = newmode.mode || newmode.name || newmode;
1520 var modename = newmode.mode || newmode.name || newmode;
1521
1521
1522 var that = this;
1522 var that = this;
1523 utils.requireCodeMirrorMode(modename, function () {
1523 utils.requireCodeMirrorMode(modename, function () {
1524 that.get_cells().map(function(cell, i) {
1524 that.get_cells().map(function(cell, i) {
1525 if (cell.cell_type === 'code'){
1525 if (cell.cell_type === 'code'){
1526 cell.code_mirror.setOption('mode', newmode);
1526 cell.code_mirror.setOption('mode', newmode);
1527 // This is currently redundant, because cm_config ends up as
1527 // This is currently redundant, because cm_config ends up as
1528 // codemirror's own .options object, but I don't want to
1528 // codemirror's own .options object, but I don't want to
1529 // rely on that.
1529 // rely on that.
1530 cell.cm_config.mode = newmode;
1530 cell.cm_config.mode = newmode;
1531 }
1531 }
1532 });
1532 });
1533 });
1533 });
1534 };
1534 };
1535
1535
1536 // Session related things
1536 // Session related things
1537
1537
1538 /**
1538 /**
1539 * Start a new session and set it on each code cell.
1539 * Start a new session and set it on each code cell.
1540 *
1540 *
1541 * @method start_session
1541 * @method start_session
1542 */
1542 */
1543 Notebook.prototype.start_session = function (kernel_name) {
1543 Notebook.prototype.start_session = function (kernel_name) {
1544 if (this._session_starting) {
1544 if (this._session_starting) {
1545 throw new session.SessionAlreadyStarting();
1545 throw new session.SessionAlreadyStarting();
1546 }
1546 }
1547 this._session_starting = true;
1547 this._session_starting = true;
1548
1548
1549 var options = {
1549 var options = {
1550 base_url: this.base_url,
1550 base_url: this.base_url,
1551 ws_url: this.ws_url,
1551 ws_url: this.ws_url,
1552 notebook_path: this.notebook_path,
1552 notebook_path: this.notebook_path,
1553 notebook_name: this.notebook_name,
1553 notebook_name: this.notebook_name,
1554 kernel_name: kernel_name,
1554 kernel_name: kernel_name,
1555 notebook: this
1555 notebook: this
1556 };
1556 };
1557
1557
1558 var success = $.proxy(this._session_started, this);
1558 var success = $.proxy(this._session_started, this);
1559 var failure = $.proxy(this._session_start_failed, this);
1559 var failure = $.proxy(this._session_start_failed, this);
1560
1560
1561 if (this.session !== null) {
1561 if (this.session !== null) {
1562 this.session.restart(options, success, failure);
1562 this.session.restart(options, success, failure);
1563 } else {
1563 } else {
1564 this.session = new session.Session(options);
1564 this.session = new session.Session(options);
1565 this.session.start(success, failure);
1565 this.session.start(success, failure);
1566 }
1566 }
1567 };
1567 };
1568
1568
1569
1569
1570 /**
1570 /**
1571 * Once a session is started, link the code cells to the kernel and pass the
1571 * Once a session is started, link the code cells to the kernel and pass the
1572 * comm manager to the widget manager
1572 * comm manager to the widget manager
1573 *
1573 *
1574 */
1574 */
1575 Notebook.prototype._session_started = function (){
1575 Notebook.prototype._session_started = function (){
1576 this._session_starting = false;
1576 this._session_starting = false;
1577 this.kernel = this.session.kernel;
1577 this.kernel = this.session.kernel;
1578 var ncells = this.ncells();
1578 var ncells = this.ncells();
1579 for (var i=0; i<ncells; i++) {
1579 for (var i=0; i<ncells; i++) {
1580 var cell = this.get_cell(i);
1580 var cell = this.get_cell(i);
1581 if (cell instanceof codecell.CodeCell) {
1581 if (cell instanceof codecell.CodeCell) {
1582 cell.set_kernel(this.session.kernel);
1582 cell.set_kernel(this.session.kernel);
1583 }
1583 }
1584 }
1584 }
1585 };
1585 };
1586 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1586 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1587 this._session_starting = false;
1587 this._session_starting = false;
1588 utils.log_ajax_error(jqxhr, status, error);
1588 utils.log_ajax_error(jqxhr, status, error);
1589 };
1589 };
1590
1590
1591 /**
1591 /**
1592 * Prompt the user to restart the IPython kernel.
1592 * Prompt the user to restart the IPython kernel.
1593 *
1593 *
1594 * @method restart_kernel
1594 * @method restart_kernel
1595 */
1595 */
1596 Notebook.prototype.restart_kernel = function () {
1596 Notebook.prototype.restart_kernel = function () {
1597 var that = this;
1597 var that = this;
1598 dialog.modal({
1598 dialog.modal({
1599 notebook: this,
1599 notebook: this,
1600 keyboard_manager: this.keyboard_manager,
1600 keyboard_manager: this.keyboard_manager,
1601 title : "Restart kernel or continue running?",
1601 title : "Restart kernel or continue running?",
1602 body : $("<p/>").text(
1602 body : $("<p/>").text(
1603 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1603 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1604 ),
1604 ),
1605 buttons : {
1605 buttons : {
1606 "Continue running" : {},
1606 "Continue running" : {},
1607 "Restart" : {
1607 "Restart" : {
1608 "class" : "btn-danger",
1608 "class" : "btn-danger",
1609 "click" : function() {
1609 "click" : function() {
1610 that.kernel.restart();
1610 that.kernel.restart();
1611 }
1611 }
1612 }
1612 }
1613 }
1613 }
1614 });
1614 });
1615 };
1615 };
1616
1616
1617 /**
1617 /**
1618 * Execute or render cell outputs and go into command mode.
1618 * Execute or render cell outputs and go into command mode.
1619 *
1619 *
1620 * @method execute_cell
1620 * @method execute_cell
1621 */
1621 */
1622 Notebook.prototype.execute_cell = function () {
1622 Notebook.prototype.execute_cell = function () {
1623 // mode = shift, ctrl, alt
1623 // mode = shift, ctrl, alt
1624 var cell = this.get_selected_cell();
1624 var cell = this.get_selected_cell();
1625
1625
1626 cell.execute();
1626 cell.execute();
1627 this.command_mode();
1627 this.command_mode();
1628 this.set_dirty(true);
1628 this.set_dirty(true);
1629 };
1629 };
1630
1630
1631 /**
1631 /**
1632 * Execute or render cell outputs and insert a new cell below.
1632 * Execute or render cell outputs and insert a new cell below.
1633 *
1633 *
1634 * @method execute_cell_and_insert_below
1634 * @method execute_cell_and_insert_below
1635 */
1635 */
1636 Notebook.prototype.execute_cell_and_insert_below = function () {
1636 Notebook.prototype.execute_cell_and_insert_below = function () {
1637 var cell = this.get_selected_cell();
1637 var cell = this.get_selected_cell();
1638 var cell_index = this.find_cell_index(cell);
1638 var cell_index = this.find_cell_index(cell);
1639
1639
1640 cell.execute();
1640 cell.execute();
1641
1641
1642 // If we are at the end always insert a new cell and return
1642 // If we are at the end always insert a new cell and return
1643 if (cell_index === (this.ncells()-1)) {
1643 if (cell_index === (this.ncells()-1)) {
1644 this.command_mode();
1644 this.command_mode();
1645 this.insert_cell_below();
1645 this.insert_cell_below();
1646 this.select(cell_index+1);
1646 this.select(cell_index+1);
1647 this.edit_mode();
1647 this.edit_mode();
1648 this.scroll_to_bottom();
1648 this.scroll_to_bottom();
1649 this.set_dirty(true);
1649 this.set_dirty(true);
1650 return;
1650 return;
1651 }
1651 }
1652
1652
1653 this.command_mode();
1653 this.command_mode();
1654 this.insert_cell_below();
1654 this.insert_cell_below();
1655 this.select(cell_index+1);
1655 this.select(cell_index+1);
1656 this.edit_mode();
1656 this.edit_mode();
1657 this.set_dirty(true);
1657 this.set_dirty(true);
1658 };
1658 };
1659
1659
1660 /**
1660 /**
1661 * Execute or render cell outputs and select the next cell.
1661 * Execute or render cell outputs and select the next cell.
1662 *
1662 *
1663 * @method execute_cell_and_select_below
1663 * @method execute_cell_and_select_below
1664 */
1664 */
1665 Notebook.prototype.execute_cell_and_select_below = function () {
1665 Notebook.prototype.execute_cell_and_select_below = function () {
1666
1666
1667 var cell = this.get_selected_cell();
1667 var cell = this.get_selected_cell();
1668 var cell_index = this.find_cell_index(cell);
1668 var cell_index = this.find_cell_index(cell);
1669
1669
1670 cell.execute();
1670 cell.execute();
1671
1671
1672 // If we are at the end always insert a new cell and return
1672 // If we are at the end always insert a new cell and return
1673 if (cell_index === (this.ncells()-1)) {
1673 if (cell_index === (this.ncells()-1)) {
1674 this.command_mode();
1674 this.command_mode();
1675 this.insert_cell_below();
1675 this.insert_cell_below();
1676 this.select(cell_index+1);
1676 this.select(cell_index+1);
1677 this.edit_mode();
1677 this.edit_mode();
1678 this.scroll_to_bottom();
1678 this.scroll_to_bottom();
1679 this.set_dirty(true);
1679 this.set_dirty(true);
1680 return;
1680 return;
1681 }
1681 }
1682
1682
1683 this.command_mode();
1683 this.command_mode();
1684 this.select(cell_index+1);
1684 this.select(cell_index+1);
1685 this.focus_cell();
1685 this.focus_cell();
1686 this.set_dirty(true);
1686 this.set_dirty(true);
1687 };
1687 };
1688
1688
1689 /**
1689 /**
1690 * Execute all cells below the selected cell.
1690 * Execute all cells below the selected cell.
1691 *
1691 *
1692 * @method execute_cells_below
1692 * @method execute_cells_below
1693 */
1693 */
1694 Notebook.prototype.execute_cells_below = function () {
1694 Notebook.prototype.execute_cells_below = function () {
1695 this.execute_cell_range(this.get_selected_index(), this.ncells());
1695 this.execute_cell_range(this.get_selected_index(), this.ncells());
1696 this.scroll_to_bottom();
1696 this.scroll_to_bottom();
1697 };
1697 };
1698
1698
1699 /**
1699 /**
1700 * Execute all cells above the selected cell.
1700 * Execute all cells above the selected cell.
1701 *
1701 *
1702 * @method execute_cells_above
1702 * @method execute_cells_above
1703 */
1703 */
1704 Notebook.prototype.execute_cells_above = function () {
1704 Notebook.prototype.execute_cells_above = function () {
1705 this.execute_cell_range(0, this.get_selected_index());
1705 this.execute_cell_range(0, this.get_selected_index());
1706 };
1706 };
1707
1707
1708 /**
1708 /**
1709 * Execute all cells.
1709 * Execute all cells.
1710 *
1710 *
1711 * @method execute_all_cells
1711 * @method execute_all_cells
1712 */
1712 */
1713 Notebook.prototype.execute_all_cells = function () {
1713 Notebook.prototype.execute_all_cells = function () {
1714 this.execute_cell_range(0, this.ncells());
1714 this.execute_cell_range(0, this.ncells());
1715 this.scroll_to_bottom();
1715 this.scroll_to_bottom();
1716 };
1716 };
1717
1717
1718 /**
1718 /**
1719 * Execute a contiguous range of cells.
1719 * Execute a contiguous range of cells.
1720 *
1720 *
1721 * @method execute_cell_range
1721 * @method execute_cell_range
1722 * @param {Number} start Index of the first cell to execute (inclusive)
1722 * @param {Number} start Index of the first cell to execute (inclusive)
1723 * @param {Number} end Index of the last cell to execute (exclusive)
1723 * @param {Number} end Index of the last cell to execute (exclusive)
1724 */
1724 */
1725 Notebook.prototype.execute_cell_range = function (start, end) {
1725 Notebook.prototype.execute_cell_range = function (start, end) {
1726 this.command_mode();
1726 this.command_mode();
1727 for (var i=start; i<end; i++) {
1727 for (var i=start; i<end; i++) {
1728 this.select(i);
1728 this.select(i);
1729 this.execute_cell();
1729 this.execute_cell();
1730 }
1730 }
1731 };
1731 };
1732
1732
1733 // Persistance and loading
1733 // Persistance and loading
1734
1734
1735 /**
1735 /**
1736 * Getter method for this notebook's name.
1736 * Getter method for this notebook's name.
1737 *
1737 *
1738 * @method get_notebook_name
1738 * @method get_notebook_name
1739 * @return {String} This notebook's name (excluding file extension)
1739 * @return {String} This notebook's name (excluding file extension)
1740 */
1740 */
1741 Notebook.prototype.get_notebook_name = function () {
1741 Notebook.prototype.get_notebook_name = function () {
1742 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1742 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1743 return nbname;
1743 return nbname;
1744 };
1744 };
1745
1745
1746 /**
1746 /**
1747 * Setter method for this notebook's name.
1747 * Setter method for this notebook's name.
1748 *
1748 *
1749 * @method set_notebook_name
1749 * @method set_notebook_name
1750 * @param {String} name A new name for this notebook
1750 * @param {String} name A new name for this notebook
1751 */
1751 */
1752 Notebook.prototype.set_notebook_name = function (name) {
1752 Notebook.prototype.set_notebook_name = function (name) {
1753 var parent = utils.url_path_split(this.notebook_path)[0];
1753 var parent = utils.url_path_split(this.notebook_path)[0];
1754 this.notebook_name = name;
1754 this.notebook_name = name;
1755 this.notebook_path = utils.url_path_join(parent, name);
1755 this.notebook_path = utils.url_path_join(parent, name);
1756 };
1756 };
1757
1757
1758 /**
1758 /**
1759 * Check that a notebook's name is valid.
1759 * Check that a notebook's name is valid.
1760 *
1760 *
1761 * @method test_notebook_name
1761 * @method test_notebook_name
1762 * @param {String} nbname A name for this notebook
1762 * @param {String} nbname A name for this notebook
1763 * @return {Boolean} True if the name is valid, false if invalid
1763 * @return {Boolean} True if the name is valid, false if invalid
1764 */
1764 */
1765 Notebook.prototype.test_notebook_name = function (nbname) {
1765 Notebook.prototype.test_notebook_name = function (nbname) {
1766 nbname = nbname || '';
1766 nbname = nbname || '';
1767 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1767 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1768 return true;
1768 return true;
1769 } else {
1769 } else {
1770 return false;
1770 return false;
1771 }
1771 }
1772 };
1772 };
1773
1773
1774 /**
1774 /**
1775 * Load a notebook from JSON (.ipynb).
1775 * Load a notebook from JSON (.ipynb).
1776 *
1776 *
1777 * @method fromJSON
1777 * @method fromJSON
1778 * @param {Object} data JSON representation of a notebook
1778 * @param {Object} data JSON representation of a notebook
1779 */
1779 */
1780 Notebook.prototype.fromJSON = function (data) {
1780 Notebook.prototype.fromJSON = function (data) {
1781
1781
1782 var content = data.content;
1782 var content = data.content;
1783 var ncells = this.ncells();
1783 var ncells = this.ncells();
1784 var i;
1784 var i;
1785 for (i=0; i<ncells; i++) {
1785 for (i=0; i<ncells; i++) {
1786 // Always delete cell 0 as they get renumbered as they are deleted.
1786 // Always delete cell 0 as they get renumbered as they are deleted.
1787 this.delete_cell(0);
1787 this.delete_cell(0);
1788 }
1788 }
1789 // Save the metadata and name.
1789 // Save the metadata and name.
1790 this.metadata = content.metadata;
1790 this.metadata = content.metadata;
1791 this.notebook_name = data.name;
1791 this.notebook_name = data.name;
1792 this.notebook_path = data.path;
1792 this.notebook_path = data.path;
1793 var trusted = true;
1793 var trusted = true;
1794
1794
1795 // Trigger an event changing the kernel spec - this will set the default
1795 // Trigger an event changing the kernel spec - this will set the default
1796 // codemirror mode
1796 // codemirror mode
1797 if (this.metadata.kernelspec !== undefined) {
1797 if (this.metadata.kernelspec !== undefined) {
1798 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1798 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1799 }
1799 }
1800
1800
1801 // Set the codemirror mode from language_info metadata
1801 // Set the codemirror mode from language_info metadata
1802 if (this.metadata.language_info !== undefined) {
1802 if (this.metadata.language_info !== undefined) {
1803 var langinfo = this.metadata.language_info;
1803 var langinfo = this.metadata.language_info;
1804 // Mode 'null' should be plain, unhighlighted text.
1804 // Mode 'null' should be plain, unhighlighted text.
1805 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
1805 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
1806 this.set_codemirror_mode(cm_mode);
1806 this.set_codemirror_mode(cm_mode);
1807 }
1807 }
1808
1808
1809 var new_cells = content.cells;
1809 var new_cells = content.cells;
1810 ncells = new_cells.length;
1810 ncells = new_cells.length;
1811 var cell_data = null;
1811 var cell_data = null;
1812 var new_cell = null;
1812 var new_cell = null;
1813 for (i=0; i<ncells; i++) {
1813 for (i=0; i<ncells; i++) {
1814 cell_data = new_cells[i];
1814 cell_data = new_cells[i];
1815 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1815 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1816 new_cell.fromJSON(cell_data);
1816 new_cell.fromJSON(cell_data);
1817 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1817 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1818 trusted = false;
1818 trusted = false;
1819 }
1819 }
1820 }
1820 }
1821 if (trusted !== this.trusted) {
1821 if (trusted !== this.trusted) {
1822 this.trusted = trusted;
1822 this.trusted = trusted;
1823 this.events.trigger("trust_changed.Notebook", trusted);
1823 this.events.trigger("trust_changed.Notebook", trusted);
1824 }
1824 }
1825 };
1825 };
1826
1826
1827 /**
1827 /**
1828 * Dump this notebook into a JSON-friendly object.
1828 * Dump this notebook into a JSON-friendly object.
1829 *
1829 *
1830 * @method toJSON
1830 * @method toJSON
1831 * @return {Object} A JSON-friendly representation of this notebook.
1831 * @return {Object} A JSON-friendly representation of this notebook.
1832 */
1832 */
1833 Notebook.prototype.toJSON = function () {
1833 Notebook.prototype.toJSON = function () {
1834 // remove the conversion indicator, which only belongs in-memory
1834 // remove the conversion indicator, which only belongs in-memory
1835 delete this.metadata.orig_nbformat;
1835 delete this.metadata.orig_nbformat;
1836 delete this.metadata.orig_nbformat_minor;
1836 delete this.metadata.orig_nbformat_minor;
1837
1837
1838 var cells = this.get_cells();
1838 var cells = this.get_cells();
1839 var ncells = cells.length;
1839 var ncells = cells.length;
1840 var cell_array = new Array(ncells);
1840 var cell_array = new Array(ncells);
1841 var trusted = true;
1841 var trusted = true;
1842 for (var i=0; i<ncells; i++) {
1842 for (var i=0; i<ncells; i++) {
1843 var cell = cells[i];
1843 var cell = cells[i];
1844 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1844 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1845 trusted = false;
1845 trusted = false;
1846 }
1846 }
1847 cell_array[i] = cell.toJSON();
1847 cell_array[i] = cell.toJSON();
1848 }
1848 }
1849 var data = {
1849 var data = {
1850 cells: cell_array,
1850 cells: cell_array,
1851 metadata: this.metadata,
1851 metadata: this.metadata,
1852 nbformat: this.nbformat,
1852 nbformat: this.nbformat,
1853 nbformat_minor: this.nbformat_minor
1853 nbformat_minor: this.nbformat_minor
1854 };
1854 };
1855 if (trusted != this.trusted) {
1855 if (trusted != this.trusted) {
1856 this.trusted = trusted;
1856 this.trusted = trusted;
1857 this.events.trigger("trust_changed.Notebook", trusted);
1857 this.events.trigger("trust_changed.Notebook", trusted);
1858 }
1858 }
1859 return data;
1859 return data;
1860 };
1860 };
1861
1861
1862 /**
1862 /**
1863 * Start an autosave timer, for periodically saving the notebook.
1863 * Start an autosave timer, for periodically saving the notebook.
1864 *
1864 *
1865 * @method set_autosave_interval
1865 * @method set_autosave_interval
1866 * @param {Integer} interval the autosave interval in milliseconds
1866 * @param {Integer} interval the autosave interval in milliseconds
1867 */
1867 */
1868 Notebook.prototype.set_autosave_interval = function (interval) {
1868 Notebook.prototype.set_autosave_interval = function (interval) {
1869 var that = this;
1869 var that = this;
1870 // clear previous interval, so we don't get simultaneous timers
1870 // clear previous interval, so we don't get simultaneous timers
1871 if (this.autosave_timer) {
1871 if (this.autosave_timer) {
1872 clearInterval(this.autosave_timer);
1872 clearInterval(this.autosave_timer);
1873 }
1873 }
1874
1874
1875 this.autosave_interval = this.minimum_autosave_interval = interval;
1875 this.autosave_interval = this.minimum_autosave_interval = interval;
1876 if (interval) {
1876 if (interval) {
1877 this.autosave_timer = setInterval(function() {
1877 this.autosave_timer = setInterval(function() {
1878 if (that.dirty) {
1878 if (that.dirty) {
1879 that.save_notebook();
1879 that.save_notebook();
1880 }
1880 }
1881 }, interval);
1881 }, interval);
1882 this.events.trigger("autosave_enabled.Notebook", interval);
1882 this.events.trigger("autosave_enabled.Notebook", interval);
1883 } else {
1883 } else {
1884 this.autosave_timer = null;
1884 this.autosave_timer = null;
1885 this.events.trigger("autosave_disabled.Notebook");
1885 this.events.trigger("autosave_disabled.Notebook");
1886 }
1886 }
1887 };
1887 };
1888
1888
1889 /**
1889 /**
1890 * Save this notebook on the server. This becomes a notebook instance's
1890 * Save this notebook on the server. This becomes a notebook instance's
1891 * .save_notebook method *after* the entire notebook has been loaded.
1891 * .save_notebook method *after* the entire notebook has been loaded.
1892 *
1892 *
1893 * @method save_notebook
1893 * @method save_notebook
1894 */
1894 */
1895 Notebook.prototype.save_notebook = function () {
1895 Notebook.prototype.save_notebook = function () {
1896 // Create a JSON model to be sent to the server.
1896 // Create a JSON model to be sent to the server.
1897 var model = {
1897 var model = {
1898 type : "notebook",
1898 type : "notebook",
1899 content : this.toJSON()
1899 content : this.toJSON()
1900 };
1900 };
1901 // time the ajax call for autosave tuning purposes.
1901 // time the ajax call for autosave tuning purposes.
1902 var start = new Date().getTime();
1902 var start = new Date().getTime();
1903
1903
1904 var that = this;
1904 var that = this;
1905 this.contents.save(this.notebook_path, model, {
1905 this.contents.save(this.notebook_path, model).then(
1906 success: $.proxy(this.save_notebook_success, this, start),
1906 $.proxy(this.save_notebook_success, this, start),
1907 error: function (error) {
1907 function (error) {
1908 that.events.trigger('notebook_save_failed.Notebook', error);
1908 that.events.trigger('notebook_save_failed.Notebook', error);
1909 }
1909 }
1910 });
1910 );
1911 };
1911 };
1912
1912
1913 /**
1913 /**
1914 * Success callback for saving a notebook.
1914 * Success callback for saving a notebook.
1915 *
1915 *
1916 * @method save_notebook_success
1916 * @method save_notebook_success
1917 * @param {Integer} start Time when the save request start
1917 * @param {Integer} start Time when the save request start
1918 * @param {Object} data JSON representation of a notebook
1918 * @param {Object} data JSON representation of a notebook
1919 */
1919 */
1920 Notebook.prototype.save_notebook_success = function (start, data) {
1920 Notebook.prototype.save_notebook_success = function (start, data) {
1921 this.set_dirty(false);
1921 this.set_dirty(false);
1922 if (data.message) {
1922 if (data.message) {
1923 // save succeeded, but validation failed.
1923 // save succeeded, but validation failed.
1924 var body = $("<div>");
1924 var body = $("<div>");
1925 var title = "Notebook validation failed";
1925 var title = "Notebook validation failed";
1926
1926
1927 body.append($("<p>").text(
1927 body.append($("<p>").text(
1928 "The save operation succeeded," +
1928 "The save operation succeeded," +
1929 " but the notebook does not appear to be valid." +
1929 " but the notebook does not appear to be valid." +
1930 " The validation error was:"
1930 " The validation error was:"
1931 )).append($("<div>").addClass("validation-error").append(
1931 )).append($("<div>").addClass("validation-error").append(
1932 $("<pre>").text(data.message)
1932 $("<pre>").text(data.message)
1933 ));
1933 ));
1934 dialog.modal({
1934 dialog.modal({
1935 notebook: this,
1935 notebook: this,
1936 keyboard_manager: this.keyboard_manager,
1936 keyboard_manager: this.keyboard_manager,
1937 title: title,
1937 title: title,
1938 body: body,
1938 body: body,
1939 buttons : {
1939 buttons : {
1940 OK : {
1940 OK : {
1941 "class" : "btn-primary"
1941 "class" : "btn-primary"
1942 }
1942 }
1943 }
1943 }
1944 });
1944 });
1945 }
1945 }
1946 this.events.trigger('notebook_saved.Notebook');
1946 this.events.trigger('notebook_saved.Notebook');
1947 this._update_autosave_interval(start);
1947 this._update_autosave_interval(start);
1948 if (this._checkpoint_after_save) {
1948 if (this._checkpoint_after_save) {
1949 this.create_checkpoint();
1949 this.create_checkpoint();
1950 this._checkpoint_after_save = false;
1950 this._checkpoint_after_save = false;
1951 }
1951 }
1952 };
1952 };
1953
1953
1954 /**
1954 /**
1955 * update the autosave interval based on how long the last save took
1955 * update the autosave interval based on how long the last save took
1956 *
1956 *
1957 * @method _update_autosave_interval
1957 * @method _update_autosave_interval
1958 * @param {Integer} timestamp when the save request started
1958 * @param {Integer} timestamp when the save request started
1959 */
1959 */
1960 Notebook.prototype._update_autosave_interval = function (start) {
1960 Notebook.prototype._update_autosave_interval = function (start) {
1961 var duration = (new Date().getTime() - start);
1961 var duration = (new Date().getTime() - start);
1962 if (this.autosave_interval) {
1962 if (this.autosave_interval) {
1963 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1963 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1964 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1964 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1965 // round to 10 seconds, otherwise we will be setting a new interval too often
1965 // round to 10 seconds, otherwise we will be setting a new interval too often
1966 interval = 10000 * Math.round(interval / 10000);
1966 interval = 10000 * Math.round(interval / 10000);
1967 // set new interval, if it's changed
1967 // set new interval, if it's changed
1968 if (interval != this.autosave_interval) {
1968 if (interval != this.autosave_interval) {
1969 this.set_autosave_interval(interval);
1969 this.set_autosave_interval(interval);
1970 }
1970 }
1971 }
1971 }
1972 };
1972 };
1973
1973
1974 /**
1974 /**
1975 * Explicitly trust the output of this notebook.
1975 * Explicitly trust the output of this notebook.
1976 *
1976 *
1977 * @method trust_notebook
1977 * @method trust_notebook
1978 */
1978 */
1979 Notebook.prototype.trust_notebook = function () {
1979 Notebook.prototype.trust_notebook = function () {
1980 var body = $("<div>").append($("<p>")
1980 var body = $("<div>").append($("<p>")
1981 .text("A trusted IPython notebook may execute hidden malicious code ")
1981 .text("A trusted IPython notebook may execute hidden malicious code ")
1982 .append($("<strong>")
1982 .append($("<strong>")
1983 .append(
1983 .append(
1984 $("<em>").text("when you open it")
1984 $("<em>").text("when you open it")
1985 )
1985 )
1986 ).append(".").append(
1986 ).append(".").append(
1987 " Selecting trust will immediately reload this notebook in a trusted state."
1987 " Selecting trust will immediately reload this notebook in a trusted state."
1988 ).append(
1988 ).append(
1989 " For more information, see the "
1989 " For more information, see the "
1990 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1990 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1991 .text("IPython security documentation")
1991 .text("IPython security documentation")
1992 ).append(".")
1992 ).append(".")
1993 );
1993 );
1994
1994
1995 var nb = this;
1995 var nb = this;
1996 dialog.modal({
1996 dialog.modal({
1997 notebook: this,
1997 notebook: this,
1998 keyboard_manager: this.keyboard_manager,
1998 keyboard_manager: this.keyboard_manager,
1999 title: "Trust this notebook?",
1999 title: "Trust this notebook?",
2000 body: body,
2000 body: body,
2001
2001
2002 buttons: {
2002 buttons: {
2003 Cancel : {},
2003 Cancel : {},
2004 Trust : {
2004 Trust : {
2005 class : "btn-danger",
2005 class : "btn-danger",
2006 click : function () {
2006 click : function () {
2007 var cells = nb.get_cells();
2007 var cells = nb.get_cells();
2008 for (var i = 0; i < cells.length; i++) {
2008 for (var i = 0; i < cells.length; i++) {
2009 var cell = cells[i];
2009 var cell = cells[i];
2010 if (cell.cell_type == 'code') {
2010 if (cell.cell_type == 'code') {
2011 cell.output_area.trusted = true;
2011 cell.output_area.trusted = true;
2012 }
2012 }
2013 }
2013 }
2014 nb.events.on('notebook_saved.Notebook', function () {
2014 nb.events.on('notebook_saved.Notebook', function () {
2015 window.location.reload();
2015 window.location.reload();
2016 });
2016 });
2017 nb.save_notebook();
2017 nb.save_notebook();
2018 }
2018 }
2019 }
2019 }
2020 }
2020 }
2021 });
2021 });
2022 };
2022 };
2023
2023
2024 Notebook.prototype.copy_notebook = function(){
2024 Notebook.prototype.copy_notebook = function(){
2025 var base_url = this.base_url;
2025 var base_url = this.base_url;
2026 var w = window.open();
2026 var w = window.open();
2027 var parent = utils.url_path_split(this.notebook_path)[0];
2027 var parent = utils.url_path_split(this.notebook_path)[0];
2028 this.contents.copy(this.notebook_path, parent, {
2028 this.contents.copy(this.notebook_path, parent).then(
2029 success: function (data) {
2029 function (data) {
2030 w.location = utils.url_join_encode(
2030 w.location = utils.url_join_encode(
2031 base_url, 'notebooks', data.path
2031 base_url, 'notebooks', data.path
2032 );
2032 );
2033 },
2033 },
2034 error : function(error) {
2034 function(error) {
2035 w.close();
2035 w.close();
2036 console.log(error);
2036 console.log(error);
2037 },
2037 }
2038 });
2038 );
2039 };
2039 };
2040
2040
2041 Notebook.prototype.rename = function (new_name) {
2041 Notebook.prototype.rename = function (new_name) {
2042 if (!new_name.match(/\.ipynb$/)) {
2042 if (!new_name.match(/\.ipynb$/)) {
2043 new_name = new_name + ".ipynb";
2043 new_name = new_name + ".ipynb";
2044 }
2044 }
2045
2045
2046 var that = this;
2046 var that = this;
2047 var parent = utils.url_path_split(this.notebook_path)[0];
2047 var parent = utils.url_path_split(this.notebook_path)[0];
2048 var new_path = utils.url_path_join(parent, new_name);
2048 var new_path = utils.url_path_join(parent, new_name);
2049 this.contents.rename(this.notebook_path, new_path, {
2049 this.contents.rename(this.notebook_path, new_path).then(
2050 success: function (json) {
2050 function (json) {
2051 that.notebook_name = json.name;
2051 that.notebook_name = json.name;
2052 that.notebook_path = json.path;
2052 that.notebook_path = json.path;
2053 that.session.rename_notebook(json.path);
2053 that.session.rename_notebook(json.path);
2054 that.events.trigger('notebook_renamed.Notebook', json);
2054 that.events.trigger('notebook_renamed.Notebook', json);
2055 },
2055 },
2056 error: $.proxy(this.rename_error, this)
2056 $.proxy(this.rename_error, this)
2057 });
2057 );
2058 };
2058 };
2059
2059
2060 Notebook.prototype.delete = function () {
2060 Notebook.prototype.delete = function () {
2061 this.contents.delete(this.notebook_path);
2061 this.contents.delete(this.notebook_path);
2062 };
2062 };
2063
2063
2064 Notebook.prototype.rename_error = function (error) {
2064 Notebook.prototype.rename_error = function (error) {
2065 var that = this;
2065 var that = this;
2066 var dialog_body = $('<div/>').append(
2066 var dialog_body = $('<div/>').append(
2067 $("<p/>").text('This notebook name already exists.')
2067 $("<p/>").text('This notebook name already exists.')
2068 );
2068 );
2069 this.events.trigger('notebook_rename_failed.Notebook', error);
2069 this.events.trigger('notebook_rename_failed.Notebook', error);
2070 dialog.modal({
2070 dialog.modal({
2071 notebook: this,
2071 notebook: this,
2072 keyboard_manager: this.keyboard_manager,
2072 keyboard_manager: this.keyboard_manager,
2073 title: "Notebook Rename Error!",
2073 title: "Notebook Rename Error!",
2074 body: dialog_body,
2074 body: dialog_body,
2075 buttons : {
2075 buttons : {
2076 "Cancel": {},
2076 "Cancel": {},
2077 "OK": {
2077 "OK": {
2078 class: "btn-primary",
2078 class: "btn-primary",
2079 click: function () {
2079 click: function () {
2080 that.save_widget.rename_notebook({notebook:that});
2080 that.save_widget.rename_notebook({notebook:that});
2081 }}
2081 }}
2082 },
2082 },
2083 open : function (event, ui) {
2083 open : function (event, ui) {
2084 var that = $(this);
2084 var that = $(this);
2085 // Upon ENTER, click the OK button.
2085 // Upon ENTER, click the OK button.
2086 that.find('input[type="text"]').keydown(function (event, ui) {
2086 that.find('input[type="text"]').keydown(function (event, ui) {
2087 if (event.which === this.keyboard.keycodes.enter) {
2087 if (event.which === this.keyboard.keycodes.enter) {
2088 that.find('.btn-primary').first().click();
2088 that.find('.btn-primary').first().click();
2089 }
2089 }
2090 });
2090 });
2091 that.find('input[type="text"]').focus();
2091 that.find('input[type="text"]').focus();
2092 }
2092 }
2093 });
2093 });
2094 };
2094 };
2095
2095
2096 /**
2096 /**
2097 * Request a notebook's data from the server.
2097 * Request a notebook's data from the server.
2098 *
2098 *
2099 * @method load_notebook
2099 * @method load_notebook
2100 * @param {String} notebook_path A notebook to load
2100 * @param {String} notebook_path A notebook to load
2101 */
2101 */
2102 Notebook.prototype.load_notebook = function (notebook_path) {
2102 Notebook.prototype.load_notebook = function (notebook_path) {
2103 this.notebook_path = notebook_path;
2103 this.notebook_path = notebook_path;
2104 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2104 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2105 this.events.trigger('notebook_loading.Notebook');
2105 this.events.trigger('notebook_loading.Notebook');
2106 this.contents.get(notebook_path, {
2106 this.contents.get(notebook_path, {type: 'notebook'}).then(
2107 type: 'notebook',
2107 $.proxy(this.load_notebook_success, this),
2108 success: $.proxy(this.load_notebook_success, this),
2108 $.proxy(this.load_notebook_error, this)
2109 error: $.proxy(this.load_notebook_error, this)
2109 );
2110 });
2111 };
2110 };
2112
2111
2113 /**
2112 /**
2114 * Success callback for loading a notebook from the server.
2113 * Success callback for loading a notebook from the server.
2115 *
2114 *
2116 * Load notebook data from the JSON response.
2115 * Load notebook data from the JSON response.
2117 *
2116 *
2118 * @method load_notebook_success
2117 * @method load_notebook_success
2119 * @param {Object} data JSON representation of a notebook
2118 * @param {Object} data JSON representation of a notebook
2120 */
2119 */
2121 Notebook.prototype.load_notebook_success = function (data) {
2120 Notebook.prototype.load_notebook_success = function (data) {
2122 var failed, msg;
2121 var failed, msg;
2123 try {
2122 try {
2124 this.fromJSON(data);
2123 this.fromJSON(data);
2125 } catch (e) {
2124 } catch (e) {
2126 failed = e;
2125 failed = e;
2127 console.log("Notebook failed to load from JSON:", e);
2126 console.log("Notebook failed to load from JSON:", e);
2128 }
2127 }
2129 if (failed || data.message) {
2128 if (failed || data.message) {
2130 // *either* fromJSON failed or validation failed
2129 // *either* fromJSON failed or validation failed
2131 var body = $("<div>");
2130 var body = $("<div>");
2132 var title;
2131 var title;
2133 if (failed) {
2132 if (failed) {
2134 title = "Notebook failed to load";
2133 title = "Notebook failed to load";
2135 body.append($("<p>").text(
2134 body.append($("<p>").text(
2136 "The error was: "
2135 "The error was: "
2137 )).append($("<div>").addClass("js-error").text(
2136 )).append($("<div>").addClass("js-error").text(
2138 failed.toString()
2137 failed.toString()
2139 )).append($("<p>").text(
2138 )).append($("<p>").text(
2140 "See the error console for details."
2139 "See the error console for details."
2141 ));
2140 ));
2142 } else {
2141 } else {
2143 title = "Notebook validation failed";
2142 title = "Notebook validation failed";
2144 }
2143 }
2145
2144
2146 if (data.message) {
2145 if (data.message) {
2147 if (failed) {
2146 if (failed) {
2148 msg = "The notebook also failed validation:";
2147 msg = "The notebook also failed validation:";
2149 } else {
2148 } else {
2150 msg = "An invalid notebook may not function properly." +
2149 msg = "An invalid notebook may not function properly." +
2151 " The validation error was:";
2150 " The validation error was:";
2152 }
2151 }
2153 body.append($("<p>").text(
2152 body.append($("<p>").text(
2154 msg
2153 msg
2155 )).append($("<div>").addClass("validation-error").append(
2154 )).append($("<div>").addClass("validation-error").append(
2156 $("<pre>").text(data.message)
2155 $("<pre>").text(data.message)
2157 ));
2156 ));
2158 }
2157 }
2159
2158
2160 dialog.modal({
2159 dialog.modal({
2161 notebook: this,
2160 notebook: this,
2162 keyboard_manager: this.keyboard_manager,
2161 keyboard_manager: this.keyboard_manager,
2163 title: title,
2162 title: title,
2164 body: body,
2163 body: body,
2165 buttons : {
2164 buttons : {
2166 OK : {
2165 OK : {
2167 "class" : "btn-primary"
2166 "class" : "btn-primary"
2168 }
2167 }
2169 }
2168 }
2170 });
2169 });
2171 }
2170 }
2172 if (this.ncells() === 0) {
2171 if (this.ncells() === 0) {
2173 this.insert_cell_below('code');
2172 this.insert_cell_below('code');
2174 this.edit_mode(0);
2173 this.edit_mode(0);
2175 } else {
2174 } else {
2176 this.select(0);
2175 this.select(0);
2177 this.handle_command_mode(this.get_cell(0));
2176 this.handle_command_mode(this.get_cell(0));
2178 }
2177 }
2179 this.set_dirty(false);
2178 this.set_dirty(false);
2180 this.scroll_to_top();
2179 this.scroll_to_top();
2181 var nbmodel = data.content;
2180 var nbmodel = data.content;
2182 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2181 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2183 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2182 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2184 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2183 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2185 var src;
2184 var src;
2186 if (nbmodel.nbformat > orig_nbformat) {
2185 if (nbmodel.nbformat > orig_nbformat) {
2187 src = " an older notebook format ";
2186 src = " an older notebook format ";
2188 } else {
2187 } else {
2189 src = " a newer notebook format ";
2188 src = " a newer notebook format ";
2190 }
2189 }
2191
2190
2192 msg = "This notebook has been converted from" + src +
2191 msg = "This notebook has been converted from" + src +
2193 "(v"+orig_nbformat+") to the current notebook " +
2192 "(v"+orig_nbformat+") to the current notebook " +
2194 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2193 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2195 "current notebook format will be used.";
2194 "current notebook format will be used.";
2196
2195
2197 if (nbmodel.nbformat > orig_nbformat) {
2196 if (nbmodel.nbformat > orig_nbformat) {
2198 msg += " Older versions of IPython may not be able to read the new format.";
2197 msg += " Older versions of IPython may not be able to read the new format.";
2199 } else {
2198 } else {
2200 msg += " Some features of the original notebook may not be available.";
2199 msg += " Some features of the original notebook may not be available.";
2201 }
2200 }
2202 msg += " To preserve the original version, close the " +
2201 msg += " To preserve the original version, close the " +
2203 "notebook without saving it.";
2202 "notebook without saving it.";
2204 dialog.modal({
2203 dialog.modal({
2205 notebook: this,
2204 notebook: this,
2206 keyboard_manager: this.keyboard_manager,
2205 keyboard_manager: this.keyboard_manager,
2207 title : "Notebook converted",
2206 title : "Notebook converted",
2208 body : msg,
2207 body : msg,
2209 buttons : {
2208 buttons : {
2210 OK : {
2209 OK : {
2211 class : "btn-primary"
2210 class : "btn-primary"
2212 }
2211 }
2213 }
2212 }
2214 });
2213 });
2215 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor < orig_nbformat_minor) {
2214 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor < orig_nbformat_minor) {
2216 var that = this;
2215 var that = this;
2217 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2216 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2218 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2217 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2219 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2218 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2220 this_vs + ". You can still work with this notebook, but some features " +
2219 this_vs + ". You can still work with this notebook, but some features " +
2221 "introduced in later notebook versions may not be available.";
2220 "introduced in later notebook versions may not be available.";
2222
2221
2223 dialog.modal({
2222 dialog.modal({
2224 notebook: this,
2223 notebook: this,
2225 keyboard_manager: this.keyboard_manager,
2224 keyboard_manager: this.keyboard_manager,
2226 title : "Newer Notebook",
2225 title : "Newer Notebook",
2227 body : msg,
2226 body : msg,
2228 buttons : {
2227 buttons : {
2229 OK : {
2228 OK : {
2230 class : "btn-danger"
2229 class : "btn-danger"
2231 }
2230 }
2232 }
2231 }
2233 });
2232 });
2234
2233
2235 }
2234 }
2236
2235
2237 // Create the session after the notebook is completely loaded to prevent
2236 // Create the session after the notebook is completely loaded to prevent
2238 // code execution upon loading, which is a security risk.
2237 // code execution upon loading, which is a security risk.
2239 if (this.session === null) {
2238 if (this.session === null) {
2240 var kernelspec = this.metadata.kernelspec || {};
2239 var kernelspec = this.metadata.kernelspec || {};
2241 var kernel_name = kernelspec.name;
2240 var kernel_name = kernelspec.name;
2242
2241
2243 this.start_session(kernel_name);
2242 this.start_session(kernel_name);
2244 }
2243 }
2245 // load our checkpoint list
2244 // load our checkpoint list
2246 this.list_checkpoints();
2245 this.list_checkpoints();
2247
2246
2248 // load toolbar state
2247 // load toolbar state
2249 if (this.metadata.celltoolbar) {
2248 if (this.metadata.celltoolbar) {
2250 celltoolbar.CellToolbar.global_show();
2249 celltoolbar.CellToolbar.global_show();
2251 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2250 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2252 } else {
2251 } else {
2253 celltoolbar.CellToolbar.global_hide();
2252 celltoolbar.CellToolbar.global_hide();
2254 }
2253 }
2255
2254
2256 // now that we're fully loaded, it is safe to restore save functionality
2255 // now that we're fully loaded, it is safe to restore save functionality
2257 delete(this.save_notebook);
2256 delete(this.save_notebook);
2258 this.events.trigger('notebook_loaded.Notebook');
2257 this.events.trigger('notebook_loaded.Notebook');
2259 };
2258 };
2260
2259
2261 /**
2260 /**
2262 * Failure callback for loading a notebook from the server.
2261 * Failure callback for loading a notebook from the server.
2263 *
2262 *
2264 * @method load_notebook_error
2263 * @method load_notebook_error
2265 * @param {Error} error
2264 * @param {Error} error
2266 */
2265 */
2267 Notebook.prototype.load_notebook_error = function (error) {
2266 Notebook.prototype.load_notebook_error = function (error) {
2268 this.events.trigger('notebook_load_failed.Notebook', error);
2267 this.events.trigger('notebook_load_failed.Notebook', error);
2269 var msg;
2268 var msg;
2270 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2269 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2271 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2270 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2272 msg = "An unknown error occurred while loading this notebook. " +
2271 msg = "An unknown error occurred while loading this notebook. " +
2273 "This version can load notebook formats " +
2272 "This version can load notebook formats " +
2274 "v" + this.nbformat + " or earlier. See the server log for details.";
2273 "v" + this.nbformat + " or earlier. See the server log for details.";
2275 } else {
2274 } else {
2276 msg = error.message;
2275 msg = error.message;
2277 }
2276 }
2278 dialog.modal({
2277 dialog.modal({
2279 notebook: this,
2278 notebook: this,
2280 keyboard_manager: this.keyboard_manager,
2279 keyboard_manager: this.keyboard_manager,
2281 title: "Error loading notebook",
2280 title: "Error loading notebook",
2282 body : msg,
2281 body : msg,
2283 buttons : {
2282 buttons : {
2284 "OK": {}
2283 "OK": {}
2285 }
2284 }
2286 });
2285 });
2287 };
2286 };
2288
2287
2289 /********************* checkpoint-related *********************/
2288 /********************* checkpoint-related *********************/
2290
2289
2291 /**
2290 /**
2292 * Save the notebook then immediately create a checkpoint.
2291 * Save the notebook then immediately create a checkpoint.
2293 *
2292 *
2294 * @method save_checkpoint
2293 * @method save_checkpoint
2295 */
2294 */
2296 Notebook.prototype.save_checkpoint = function () {
2295 Notebook.prototype.save_checkpoint = function () {
2297 this._checkpoint_after_save = true;
2296 this._checkpoint_after_save = true;
2298 this.save_notebook();
2297 this.save_notebook();
2299 };
2298 };
2300
2299
2301 /**
2300 /**
2302 * Add a checkpoint for this notebook.
2301 * Add a checkpoint for this notebook.
2303 * for use as a callback from checkpoint creation.
2302 * for use as a callback from checkpoint creation.
2304 *
2303 *
2305 * @method add_checkpoint
2304 * @method add_checkpoint
2306 */
2305 */
2307 Notebook.prototype.add_checkpoint = function (checkpoint) {
2306 Notebook.prototype.add_checkpoint = function (checkpoint) {
2308 var found = false;
2307 var found = false;
2309 for (var i = 0; i < this.checkpoints.length; i++) {
2308 for (var i = 0; i < this.checkpoints.length; i++) {
2310 var existing = this.checkpoints[i];
2309 var existing = this.checkpoints[i];
2311 if (existing.id == checkpoint.id) {
2310 if (existing.id == checkpoint.id) {
2312 found = true;
2311 found = true;
2313 this.checkpoints[i] = checkpoint;
2312 this.checkpoints[i] = checkpoint;
2314 break;
2313 break;
2315 }
2314 }
2316 }
2315 }
2317 if (!found) {
2316 if (!found) {
2318 this.checkpoints.push(checkpoint);
2317 this.checkpoints.push(checkpoint);
2319 }
2318 }
2320 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2319 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2321 };
2320 };
2322
2321
2323 /**
2322 /**
2324 * List checkpoints for this notebook.
2323 * List checkpoints for this notebook.
2325 *
2324 *
2326 * @method list_checkpoints
2325 * @method list_checkpoints
2327 */
2326 */
2328 Notebook.prototype.list_checkpoints = function () {
2327 Notebook.prototype.list_checkpoints = function () {
2329 var that = this;
2328 var that = this;
2330 this.contents.list_checkpoints(this.notebook_path, {
2329 this.contents.list_checkpoints(this.notebook_path).then(
2331 success: $.proxy(this.list_checkpoints_success, this),
2330 $.proxy(this.list_checkpoints_success, this),
2332 error: function(error) {
2331 function(error) {
2333 that.events.trigger('list_checkpoints_failed.Notebook', error);
2332 that.events.trigger('list_checkpoints_failed.Notebook', error);
2334 }
2333 }
2335 });
2334 );
2336 };
2335 };
2337
2336
2338 /**
2337 /**
2339 * Success callback for listing checkpoints.
2338 * Success callback for listing checkpoints.
2340 *
2339 *
2341 * @method list_checkpoint_success
2340 * @method list_checkpoint_success
2342 * @param {Object} data JSON representation of a checkpoint
2341 * @param {Object} data JSON representation of a checkpoint
2343 */
2342 */
2344 Notebook.prototype.list_checkpoints_success = function (data) {
2343 Notebook.prototype.list_checkpoints_success = function (data) {
2345 data = $.parseJSON(data);
2346 this.checkpoints = data;
2344 this.checkpoints = data;
2347 if (data.length) {
2345 if (data.length) {
2348 this.last_checkpoint = data[data.length - 1];
2346 this.last_checkpoint = data[data.length - 1];
2349 } else {
2347 } else {
2350 this.last_checkpoint = null;
2348 this.last_checkpoint = null;
2351 }
2349 }
2352 this.events.trigger('checkpoints_listed.Notebook', [data]);
2350 this.events.trigger('checkpoints_listed.Notebook', [data]);
2353 };
2351 };
2354
2352
2355 /**
2353 /**
2356 * Create a checkpoint of this notebook on the server from the most recent save.
2354 * Create a checkpoint of this notebook on the server from the most recent save.
2357 *
2355 *
2358 * @method create_checkpoint
2356 * @method create_checkpoint
2359 */
2357 */
2360 Notebook.prototype.create_checkpoint = function () {
2358 Notebook.prototype.create_checkpoint = function () {
2361 var that = this;
2359 var that = this;
2362 this.contents.create_checkpoint(this.notebook_path, {
2360 this.contents.create_checkpoint(this.notebook_path).then(
2363 success: $.proxy(this.create_checkpoint_success, this),
2361 $.proxy(this.create_checkpoint_success, this),
2364 error: function (error) {
2362 function (error) {
2365 that.events.trigger('checkpoint_failed.Notebook', error);
2363 that.events.trigger('checkpoint_failed.Notebook', error);
2366 }
2364 }
2367 });
2365 );
2368 };
2366 };
2369
2367
2370 /**
2368 /**
2371 * Success callback for creating a checkpoint.
2369 * Success callback for creating a checkpoint.
2372 *
2370 *
2373 * @method create_checkpoint_success
2371 * @method create_checkpoint_success
2374 * @param {Object} data JSON representation of a checkpoint
2372 * @param {Object} data JSON representation of a checkpoint
2375 */
2373 */
2376 Notebook.prototype.create_checkpoint_success = function (data) {
2374 Notebook.prototype.create_checkpoint_success = function (data) {
2377 data = $.parseJSON(data);
2378 this.add_checkpoint(data);
2375 this.add_checkpoint(data);
2379 this.events.trigger('checkpoint_created.Notebook', data);
2376 this.events.trigger('checkpoint_created.Notebook', data);
2380 };
2377 };
2381
2378
2382 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2379 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2383 var that = this;
2380 var that = this;
2384 checkpoint = checkpoint || this.last_checkpoint;
2381 checkpoint = checkpoint || this.last_checkpoint;
2385 if ( ! checkpoint ) {
2382 if ( ! checkpoint ) {
2386 console.log("restore dialog, but no checkpoint to restore to!");
2383 console.log("restore dialog, but no checkpoint to restore to!");
2387 return;
2384 return;
2388 }
2385 }
2389 var body = $('<div/>').append(
2386 var body = $('<div/>').append(
2390 $('<p/>').addClass("p-space").text(
2387 $('<p/>').addClass("p-space").text(
2391 "Are you sure you want to revert the notebook to " +
2388 "Are you sure you want to revert the notebook to " +
2392 "the latest checkpoint?"
2389 "the latest checkpoint?"
2393 ).append(
2390 ).append(
2394 $("<strong/>").text(
2391 $("<strong/>").text(
2395 " This cannot be undone."
2392 " This cannot be undone."
2396 )
2393 )
2397 )
2394 )
2398 ).append(
2395 ).append(
2399 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2396 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2400 ).append(
2397 ).append(
2401 $('<p/>').addClass("p-space").text(
2398 $('<p/>').addClass("p-space").text(
2402 Date(checkpoint.last_modified)
2399 Date(checkpoint.last_modified)
2403 ).css("text-align", "center")
2400 ).css("text-align", "center")
2404 );
2401 );
2405
2402
2406 dialog.modal({
2403 dialog.modal({
2407 notebook: this,
2404 notebook: this,
2408 keyboard_manager: this.keyboard_manager,
2405 keyboard_manager: this.keyboard_manager,
2409 title : "Revert notebook to checkpoint",
2406 title : "Revert notebook to checkpoint",
2410 body : body,
2407 body : body,
2411 buttons : {
2408 buttons : {
2412 Revert : {
2409 Revert : {
2413 class : "btn-danger",
2410 class : "btn-danger",
2414 click : function () {
2411 click : function () {
2415 that.restore_checkpoint(checkpoint.id);
2412 that.restore_checkpoint(checkpoint.id);
2416 }
2413 }
2417 },
2414 },
2418 Cancel : {}
2415 Cancel : {}
2419 }
2416 }
2420 });
2417 });
2421 };
2418 };
2422
2419
2423 /**
2420 /**
2424 * Restore the notebook to a checkpoint state.
2421 * Restore the notebook to a checkpoint state.
2425 *
2422 *
2426 * @method restore_checkpoint
2423 * @method restore_checkpoint
2427 * @param {String} checkpoint ID
2424 * @param {String} checkpoint ID
2428 */
2425 */
2429 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2426 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2430 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2427 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2431 var that = this;
2428 var that = this;
2432 this.contents.restore_checkpoint(this.notebook_path,
2429 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2433 checkpoint, {
2430 $.proxy(this.restore_checkpoint_success, this),
2434 success: $.proxy(this.restore_checkpoint_success, this),
2431 function (error) {
2435 error: function (error) {
2436 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2432 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2437 }
2433 }
2438 });
2434 );
2439 };
2435 };
2440
2436
2441 /**
2437 /**
2442 * Success callback for restoring a notebook to a checkpoint.
2438 * Success callback for restoring a notebook to a checkpoint.
2443 *
2439 *
2444 * @method restore_checkpoint_success
2440 * @method restore_checkpoint_success
2445 */
2441 */
2446 Notebook.prototype.restore_checkpoint_success = function () {
2442 Notebook.prototype.restore_checkpoint_success = function () {
2447 this.events.trigger('checkpoint_restored.Notebook');
2443 this.events.trigger('checkpoint_restored.Notebook');
2448 this.load_notebook(this.notebook_path);
2444 this.load_notebook(this.notebook_path);
2449 };
2445 };
2450
2446
2451 /**
2447 /**
2452 * Delete a notebook checkpoint.
2448 * Delete a notebook checkpoint.
2453 *
2449 *
2454 * @method delete_checkpoint
2450 * @method delete_checkpoint
2455 * @param {String} checkpoint ID
2451 * @param {String} checkpoint ID
2456 */
2452 */
2457 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2453 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2458 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2454 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2459 var that = this;
2455 var that = this;
2460 this.contents.delete_checkpoint(this.notebook_path,
2456 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2461 checkpoint, {
2457 $.proxy(this.delete_checkpoint_success, this),
2462 success: $.proxy(this.delete_checkpoint_success, this),
2458 function (error) {
2463 error: function (error) {
2464 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2459 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2465 }
2460 }
2466 });
2461 );
2467 };
2462 };
2468
2463
2469 /**
2464 /**
2470 * Success callback for deleting a notebook checkpoint
2465 * Success callback for deleting a notebook checkpoint
2471 *
2466 *
2472 * @method delete_checkpoint_success
2467 * @method delete_checkpoint_success
2473 */
2468 */
2474 Notebook.prototype.delete_checkpoint_success = function () {
2469 Notebook.prototype.delete_checkpoint_success = function () {
2475 this.events.trigger('checkpoint_deleted.Notebook');
2470 this.events.trigger('checkpoint_deleted.Notebook');
2476 this.load_notebook(this.notebook_path);
2471 this.load_notebook(this.notebook_path);
2477 };
2472 };
2478
2473
2479
2474
2480 // For backwards compatability.
2475 // For backwards compatability.
2481 IPython.Notebook = Notebook;
2476 IPython.Notebook = Notebook;
2482
2477
2483 return {'Notebook': Notebook};
2478 return {'Notebook': Notebook};
2484 });
2479 });
@@ -1,258 +1,242 b''
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 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 var Contents = function(options) {
9 var Contents = function(options) {
10 // Constructor
10 // Constructor
11 //
11 //
12 // A contents handles passing file operations
12 // A contents handles passing file operations
13 // to the back-end. This includes checkpointing
13 // to the back-end. This includes checkpointing
14 // with the normal file operations.
14 // with the normal file operations.
15 //
15 //
16 // Parameters:
16 // Parameters:
17 // options: dictionary
17 // options: dictionary
18 // Dictionary of keyword arguments.
18 // Dictionary of keyword arguments.
19 // base_url: string
19 // base_url: string
20 this.base_url = options.base_url;
20 this.base_url = options.base_url;
21 };
21 };
22
22
23 /** Error type */
23 /** Error type */
24 Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';
24 Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';
25
25
26 Contents.DirectoryNotEmptyError = function() {
26 Contents.DirectoryNotEmptyError = function() {
27 // Constructor
27 // Constructor
28 //
28 //
29 // An error representing the result of attempting to delete a non-empty
29 // An error representing the result of attempting to delete a non-empty
30 // directory.
30 // directory.
31 this.message = 'A directory must be empty before being deleted.';
31 this.message = 'A directory must be empty before being deleted.';
32 };
32 };
33
33
34 Contents.DirectoryNotEmptyError.prototype = Object.create(Error.prototype);
34 Contents.DirectoryNotEmptyError.prototype = Object.create(Error.prototype);
35 Contents.DirectoryNotEmptyError.prototype.name =
35 Contents.DirectoryNotEmptyError.prototype.name =
36 Contents.DIRECTORY_NOT_EMPTY_ERROR;
36 Contents.DIRECTORY_NOT_EMPTY_ERROR;
37
37
38
38
39 Contents.prototype.api_url = function() {
39 Contents.prototype.api_url = function() {
40 var url_parts = [this.base_url, 'api/contents'].concat(
40 var url_parts = [this.base_url, 'api/contents'].concat(
41 Array.prototype.slice.apply(arguments));
41 Array.prototype.slice.apply(arguments));
42 return utils.url_join_encode.apply(null, url_parts);
42 return utils.url_join_encode.apply(null, url_parts);
43 };
43 };
44
44
45 /**
45 /**
46 * Creates a basic error handler that wraps a jqXHR error as an Error.
46 * Creates a basic error handler that wraps a jqXHR error as an Error.
47 *
47 *
48 * Takes a callback that accepts an Error, and returns a callback that can
48 * Takes a callback that accepts an Error, and returns a callback that can
49 * be passed directly to $.ajax, which will wrap the error from jQuery
49 * be passed directly to $.ajax, which will wrap the error from jQuery
50 * as an Error, and pass that to the original callback.
50 * as an Error, and pass that to the original callback.
51 *
51 *
52 * @method create_basic_error_handler
52 * @method create_basic_error_handler
53 * @param{Function} callback
53 * @param{Function} callback
54 * @return{Function}
54 * @return{Function}
55 */
55 */
56 Contents.prototype.create_basic_error_handler = function(callback) {
56 Contents.prototype.create_basic_error_handler = function(callback) {
57 if (!callback) {
57 if (!callback) {
58 return utils.log_ajax_error;
58 return utils.log_ajax_error;
59 }
59 }
60 return function(xhr, status, error) {
60 return function(xhr, status, error) {
61 callback(utils.wrap_ajax_error(xhr, status, error));
61 callback(utils.wrap_ajax_error(xhr, status, error));
62 };
62 };
63 };
63 };
64
64
65 /**
65 /**
66 * File Functions (including notebook operations)
66 * File Functions (including notebook operations)
67 */
67 */
68
68
69 /**
69 /**
70 * Get a file.
70 * Get a file.
71 *
71 *
72 * Calls success with file JSON model, or error with error.
72 * Calls success with file JSON model, or error with error.
73 *
73 *
74 * @method get
74 * @method get
75 * @param {String} path
75 * @param {String} path
76 * @param {Function} success
76 * @param {Object} options
77 * @param {Function} error
77 * type : 'notebook', 'file', or 'directory'
78 * format: 'text' or 'base64'; only relevant for type: 'file'
78 */
79 */
79 Contents.prototype.get = function (path, options) {
80 Contents.prototype.get = function (path, options) {
80 // We do the call with settings so we can set cache to false.
81 // We do the call with settings so we can set cache to false.
81 var settings = {
82 var settings = {
82 processData : false,
83 processData : false,
83 cache : false,
84 cache : false,
84 type : "GET",
85 type : "GET",
85 dataType : "json",
86 dataType : "json",
86 success : options.success,
87 error : this.create_basic_error_handler(options.error)
88 };
87 };
89 var url = this.api_url(path);
88 var url = this.api_url(path);
90 params = {};
89 params = {};
91 if (options.type) { params.type = options.type; }
90 if (options.type) { params.type = options.type; }
92 if (options.format) { params.format = options.format; }
91 if (options.format) { params.format = options.format; }
93 $.ajax(url + '?' + $.param(params), settings);
92 return utils.promising_ajax(url + '?' + $.param(params), settings);
94 };
93 };
95
94
96
95
97 /**
96 /**
98 * Creates a new untitled file or directory in the specified directory path.
97 * Creates a new untitled file or directory in the specified directory path.
99 *
98 *
100 * @method new
99 * @method new
101 * @param {String} path: the directory in which to create the new file/directory
100 * @param {String} path: the directory in which to create the new file/directory
102 * @param {Object} options:
101 * @param {Object} options:
103 * ext: file extension to use
102 * ext: file extension to use
104 * type: model type to create ('notebook', 'file', or 'directory')
103 * type: model type to create ('notebook', 'file', or 'directory')
105 */
104 */
106 Contents.prototype.new_untitled = function(path, options) {
105 Contents.prototype.new_untitled = function(path, options) {
107 var data = JSON.stringify({
106 var data = JSON.stringify({
108 ext: options.ext,
107 ext: options.ext,
109 type: options.type
108 type: options.type
110 });
109 });
111
110
112 var settings = {
111 var settings = {
113 processData : false,
112 processData : false,
114 type : "POST",
113 type : "POST",
115 data: data,
114 data: data,
116 dataType : "json",
115 dataType : "json",
117 success : options.success || function() {},
118 error : this.create_basic_error_handler(options.error)
119 };
116 };
120 $.ajax(this.api_url(path), settings);
117 return utils.promising_ajax(this.api_url(path), settings);
121 };
118 };
122
119
123 Contents.prototype.delete = function(path, options) {
120 Contents.prototype.delete = function(path) {
124 var error_callback = options.error || function() {};
125 var settings = {
121 var settings = {
126 processData : false,
122 processData : false,
127 type : "DELETE",
123 type : "DELETE",
128 dataType : "json",
124 dataType : "json",
129 success : options.success || function() {},
125 };
130 error : function(xhr, status, error) {
126 var url = this.api_url(path);
127 return utils.promising_ajax(url, settings).catch(
128 // Translate certain errors to more specific ones.
129 function(error) {
131 // TODO: update IPEP27 to specify errors more precisely, so
130 // TODO: update IPEP27 to specify errors more precisely, so
132 // that error types can be detected here with certainty.
131 // that error types can be detected here with certainty.
133 if (xhr.status === 400) {
132 if (error.xhr.status === 400) {
134 error_callback(new Contents.DirectoryNotEmptyError());
133 throw new Contents.DirectoryNotEmptyError();
135 }
134 }
136 error_callback(utils.wrap_ajax_error(xhr, status, error));
135 throw error;
137 }
136 }
138 };
137 );
139 var url = this.api_url(path);
140 $.ajax(url, settings);
141 };
138 };
142
139
143 Contents.prototype.rename = function(path, new_path, options) {
140 Contents.prototype.rename = function(path, new_path) {
144 var data = {path: new_path};
141 var data = {path: new_path};
145 var settings = {
142 var settings = {
146 processData : false,
143 processData : false,
147 type : "PATCH",
144 type : "PATCH",
148 data : JSON.stringify(data),
145 data : JSON.stringify(data),
149 dataType: "json",
146 dataType: "json",
150 contentType: 'application/json',
147 contentType: 'application/json',
151 success : options.success || function() {},
152 error : this.create_basic_error_handler(options.error)
153 };
148 };
154 var url = this.api_url(path);
149 var url = this.api_url(path);
155 $.ajax(url, settings);
150 return utils.promising_ajax(url, settings);
156 };
151 };
157
152
158 Contents.prototype.save = function(path, model, options) {
153 Contents.prototype.save = function(path, model) {
159 // We do the call with settings so we can set cache to false.
154 // We do the call with settings so we can set cache to false.
160 var settings = {
155 var settings = {
161 processData : false,
156 processData : false,
162 type : "PUT",
157 type : "PUT",
163 data : JSON.stringify(model),
158 data : JSON.stringify(model),
164 contentType: 'application/json',
159 contentType: 'application/json',
165 success : options.success || function() {},
166 error : this.create_basic_error_handler(options.error)
167 };
160 };
168 var url = this.api_url(path);
161 var url = this.api_url(path);
169 $.ajax(url, settings);
162 return utils.promising_ajax(url, settings);
170 };
163 };
171
164
172 Contents.prototype.copy = function(from_file, to_dir, options) {
165 Contents.prototype.copy = function(from_file, to_dir) {
173 // Copy a file into a given directory via POST
166 // Copy a file into a given directory via POST
174 // The server will select the name of the copied file
167 // The server will select the name of the copied file
175 var url = this.api_url(to_dir);
168 var url = this.api_url(to_dir);
176
169
177 var settings = {
170 var settings = {
178 processData : false,
171 processData : false,
179 type: "POST",
172 type: "POST",
180 data: JSON.stringify({copy_from: from_file}),
173 data: JSON.stringify({copy_from: from_file}),
181 dataType : "json",
174 dataType : "json",
182 success: options.success || function() {},
183 error: this.create_basic_error_handler(options.error)
184 };
175 };
185 $.ajax(url, settings);
176 return utils.promising_ajax(url, settings);
186 };
177 };
187
178
188 /**
179 /**
189 * Checkpointing Functions
180 * Checkpointing Functions
190 */
181 */
191
182
192 Contents.prototype.create_checkpoint = function(path, options) {
183 Contents.prototype.create_checkpoint = function(path) {
193 var url = this.api_url(path, 'checkpoints');
184 var url = this.api_url(path, 'checkpoints');
194 var settings = {
185 var settings = {
195 type : "POST",
186 type : "POST",
196 success: options.success || function() {},
187 dataType : "json",
197 error : this.create_basic_error_handler(options.error)
198 };
188 };
199 $.ajax(url, settings);
189 return utils.promising_ajax(url, settings);
200 };
190 };
201
191
202 Contents.prototype.list_checkpoints = function(path, options) {
192 Contents.prototype.list_checkpoints = function(path) {
203 var url = this.api_url(path, 'checkpoints');
193 var url = this.api_url(path, 'checkpoints');
204 var settings = {
194 var settings = {
205 type : "GET",
195 type : "GET",
206 success: options.success,
196 cache: false,
207 error : this.create_basic_error_handler(options.error)
197 dataType: "json",
208 };
198 };
209 $.ajax(url, settings);
199 return utils.promising_ajax(url, settings);
210 };
200 };
211
201
212 Contents.prototype.restore_checkpoint = function(path, checkpoint_id, options) {
202 Contents.prototype.restore_checkpoint = function(path, checkpoint_id) {
213 var url = this.api_url(path, 'checkpoints', checkpoint_id);
203 var url = this.api_url(path, 'checkpoints', checkpoint_id);
214 var settings = {
204 var settings = {
215 type : "POST",
205 type : "POST",
216 success: options.success || function() {},
217 error : this.create_basic_error_handler(options.error)
218 };
206 };
219 $.ajax(url, settings);
207 return utils.promising_ajax(url, settings);
220 };
208 };
221
209
222 Contents.prototype.delete_checkpoint = function(path, checkpoint_id, options) {
210 Contents.prototype.delete_checkpoint = function(path, checkpoint_id) {
223 var url = this.api_url(path, 'checkpoints', checkpoint_id);
211 var url = this.api_url(path, 'checkpoints', checkpoint_id);
224 var settings = {
212 var settings = {
225 type : "DELETE",
213 type : "DELETE",
226 success: options.success || function() {},
227 error : this.create_basic_error_handler(options.error)
228 };
214 };
229 $.ajax(url, settings);
215 return utils.promising_ajax(url, settings);
230 };
216 };
231
217
232 /**
218 /**
233 * File management functions
219 * File management functions
234 */
220 */
235
221
236 /**
222 /**
237 * List notebooks and directories at a given path
223 * List notebooks and directories at a given path
238 *
224 *
239 * On success, load_callback is called with an array of dictionaries
225 * On success, load_callback is called with an array of dictionaries
240 * representing individual files or directories. Each dictionary has
226 * representing individual files or directories. Each dictionary has
241 * the keys:
227 * the keys:
242 * type: "notebook" or "directory"
228 * type: "notebook" or "directory"
243 * created: created date
229 * created: created date
244 * last_modified: last modified dat
230 * last_modified: last modified dat
245 * @method list_notebooks
231 * @method list_notebooks
246 * @param {String} path The path to list notebooks in
232 * @param {String} path The path to list notebooks in
247 * @param {Object} options including success and error callbacks
248 */
233 */
249 Contents.prototype.list_contents = function(path, options) {
234 Contents.prototype.list_contents = function(path) {
250 options.type = 'directory';
235 return this.get(path, {type: 'directory'});
251 this.get(path, options);
252 };
236 };
253
237
254
238
255 IPython.Contents = Contents;
239 IPython.Contents = Contents;
256
240
257 return {'Contents': Contents};
241 return {'Contents': Contents};
258 });
242 });
@@ -1,147 +1,146 b''
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 require([
4 require([
5 'jquery',
5 'jquery',
6 'base/js/namespace',
6 'base/js/namespace',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/events',
8 'base/js/events',
9 'base/js/page',
9 'base/js/page',
10 'base/js/utils',
10 'base/js/utils',
11 'contents',
11 'contents',
12 'tree/js/notebooklist',
12 'tree/js/notebooklist',
13 'tree/js/clusterlist',
13 'tree/js/clusterlist',
14 'tree/js/sessionlist',
14 'tree/js/sessionlist',
15 'tree/js/kernellist',
15 'tree/js/kernellist',
16 'tree/js/terminallist',
16 'tree/js/terminallist',
17 'auth/js/loginwidget',
17 'auth/js/loginwidget',
18 // only loaded, not used:
18 // only loaded, not used:
19 'jqueryui',
19 'jqueryui',
20 'bootstrap',
20 'bootstrap',
21 'custom/custom',
21 'custom/custom',
22 ], function(
22 ], function(
23 $,
23 $,
24 IPython,
24 IPython,
25 dialog,
25 dialog,
26 events,
26 events,
27 page,
27 page,
28 utils,
28 utils,
29 contents_service,
29 contents_service,
30 notebooklist,
30 notebooklist,
31 clusterlist,
31 clusterlist,
32 sesssionlist,
32 sesssionlist,
33 kernellist,
33 kernellist,
34 terminallist,
34 terminallist,
35 loginwidget){
35 loginwidget){
36 "use strict";
36 "use strict";
37
37
38 page = new page.Page();
38 page = new page.Page();
39
39
40 var common_options = {
40 var common_options = {
41 base_url: utils.get_body_data("baseUrl"),
41 base_url: utils.get_body_data("baseUrl"),
42 notebook_path: utils.get_body_data("notebookPath"),
42 notebook_path: utils.get_body_data("notebookPath"),
43 };
43 };
44 var session_list = new sesssionlist.SesssionList($.extend({
44 var session_list = new sesssionlist.SesssionList($.extend({
45 events: events},
45 events: events},
46 common_options));
46 common_options));
47 var contents = new contents_service.Contents($.extend({
47 var contents = new contents_service.Contents($.extend({
48 events: events},
48 events: events},
49 common_options));
49 common_options));
50 var notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({
50 var notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({
51 contents: contents,
51 contents: contents,
52 session_list: session_list},
52 session_list: session_list},
53 common_options));
53 common_options));
54 var cluster_list = new clusterlist.ClusterList('#cluster_list', common_options);
54 var cluster_list = new clusterlist.ClusterList('#cluster_list', common_options);
55 var kernel_list = new kernellist.KernelList('#running_list', $.extend({
55 var kernel_list = new kernellist.KernelList('#running_list', $.extend({
56 session_list: session_list},
56 session_list: session_list},
57 common_options));
57 common_options));
58
58
59 var terminal_list;
59 var terminal_list;
60 if (utils.get_body_data("terminalsAvailable") === "True") {
60 if (utils.get_body_data("terminalsAvailable") === "True") {
61 terminal_list = new terminallist.TerminalList('#terminal_list', common_options);
61 terminal_list = new terminallist.TerminalList('#terminal_list', common_options);
62 }
62 }
63
63
64 var login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
64 var login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
65
65
66 $('#new_notebook').click(function (e) {
66 $('#new_notebook').click(function (e) {
67 var w = window.open();
67 var w = window.open();
68 contents.new_untitled(common_options.notebook_path, {
68 contents.new_untitled(common_options.notebook_path, {type: "notebook"}).then(
69 type: "notebook",
69 function (data) {
70 success: function (data) {
71 w.location = utils.url_join_encode(
70 w.location = utils.url_join_encode(
72 common_options.base_url, 'notebooks', data.path
71 common_options.base_url, 'notebooks', data.path
73 );
72 );
74 },
73 },
75 error: function(error) {
74 function(error) {
76 w.close();
75 w.close();
77 dialog.modal({
76 dialog.modal({
78 title : 'Creating Notebook Failed',
77 title : 'Creating Notebook Failed',
79 body : "The error was: " + error.message,
78 body : "The error was: " + error.message,
80 buttons : {'OK' : {'class' : 'btn-primary'}}
79 buttons : {'OK' : {'class' : 'btn-primary'}}
81 });
80 });
82 }
81 }
83 });
82 );
84 });
83 });
85
84
86 var interval_id=0;
85 var interval_id=0;
87 // auto refresh every xx secondes, no need to be fast,
86 // auto refresh every xx secondes, no need to be fast,
88 // update is done at least when page get focus
87 // update is done at least when page get focus
89 var time_refresh = 60; // in sec
88 var time_refresh = 60; // in sec
90
89
91 var enable_autorefresh = function(){
90 var enable_autorefresh = function(){
92 //refresh immediately , then start interval
91 //refresh immediately , then start interval
93 session_list.load_sessions();
92 session_list.load_sessions();
94 cluster_list.load_list();
93 cluster_list.load_list();
95 if (!interval_id){
94 if (!interval_id){
96 interval_id = setInterval(function(){
95 interval_id = setInterval(function(){
97 session_list.load_sessions();
96 session_list.load_sessions();
98 cluster_list.load_list();
97 cluster_list.load_list();
99 }, time_refresh*1000);
98 }, time_refresh*1000);
100 }
99 }
101 };
100 };
102
101
103 var disable_autorefresh = function(){
102 var disable_autorefresh = function(){
104 clearInterval(interval_id);
103 clearInterval(interval_id);
105 interval_id = 0;
104 interval_id = 0;
106 };
105 };
107
106
108 // stop autorefresh when page lose focus
107 // stop autorefresh when page lose focus
109 $(window).blur(function() {
108 $(window).blur(function() {
110 disable_autorefresh();
109 disable_autorefresh();
111 });
110 });
112
111
113 //re-enable when page get focus back
112 //re-enable when page get focus back
114 $(window).focus(function() {
113 $(window).focus(function() {
115 enable_autorefresh();
114 enable_autorefresh();
116 });
115 });
117
116
118 // finally start it, it will refresh immediately
117 // finally start it, it will refresh immediately
119 enable_autorefresh();
118 enable_autorefresh();
120
119
121 page.show();
120 page.show();
122
121
123 // For backwards compatability.
122 // For backwards compatability.
124 IPython.page = page;
123 IPython.page = page;
125 IPython.notebook_list = notebook_list;
124 IPython.notebook_list = notebook_list;
126 IPython.cluster_list = cluster_list;
125 IPython.cluster_list = cluster_list;
127 IPython.session_list = session_list;
126 IPython.session_list = session_list;
128 IPython.kernel_list = kernel_list;
127 IPython.kernel_list = kernel_list;
129 IPython.login_widget = login_widget;
128 IPython.login_widget = login_widget;
130
129
131 events.trigger('app_initialized.DashboardApp');
130 events.trigger('app_initialized.DashboardApp');
132
131
133 // bound the upload method to the on change of the file select list
132 // bound the upload method to the on change of the file select list
134 $("#alternate_upload").change(function (event){
133 $("#alternate_upload").change(function (event){
135 notebook_list.handleFilesUpload(event,'form');
134 notebook_list.handleFilesUpload(event,'form');
136 });
135 });
137
136
138 // set hash on tab click
137 // set hash on tab click
139 $("#tabs").find("a").click(function() {
138 $("#tabs").find("a").click(function() {
140 window.location.hash = $(this).attr("href");
139 window.location.hash = $(this).attr("href");
141 });
140 });
142
141
143 // load tab if url hash
142 // load tab if url hash
144 if (window.location.hash) {
143 if (window.location.hash) {
145 $("#tabs").find("a[href=" + window.location.hash + "]").click();
144 $("#tabs").find("a[href=" + window.location.hash + "]").click();
146 }
145 }
147 });
146 });
@@ -1,469 +1,467 b''
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 ], function(IPython, $, utils, dialog) {
9 ], function(IPython, $, utils, dialog) {
10 "use strict";
10 "use strict";
11
11
12 var NotebookList = function (selector, options) {
12 var NotebookList = function (selector, options) {
13 // Constructor
13 // Constructor
14 //
14 //
15 // Parameters:
15 // Parameters:
16 // selector: string
16 // selector: string
17 // options: dictionary
17 // options: dictionary
18 // Dictionary of keyword arguments.
18 // Dictionary of keyword arguments.
19 // session_list: SessionList instance
19 // session_list: SessionList instance
20 // element_name: string
20 // element_name: string
21 // base_url: string
21 // base_url: string
22 // notebook_path: string
22 // notebook_path: string
23 // contents: Contents instance
23 // contents: Contents instance
24 var that = this;
24 var that = this;
25 this.session_list = options.session_list;
25 this.session_list = options.session_list;
26 // allow code re-use by just changing element_name in kernellist.js
26 // allow code re-use by just changing element_name in kernellist.js
27 this.element_name = options.element_name || 'notebook';
27 this.element_name = options.element_name || 'notebook';
28 this.selector = selector;
28 this.selector = selector;
29 if (this.selector !== undefined) {
29 if (this.selector !== undefined) {
30 this.element = $(selector);
30 this.element = $(selector);
31 this.style();
31 this.style();
32 this.bind_events();
32 this.bind_events();
33 }
33 }
34 this.notebooks_list = [];
34 this.notebooks_list = [];
35 this.sessions = {};
35 this.sessions = {};
36 this.base_url = options.base_url || utils.get_body_data("baseUrl");
36 this.base_url = options.base_url || utils.get_body_data("baseUrl");
37 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
37 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
38 this.contents = options.contents;
38 this.contents = options.contents;
39 if (this.session_list && this.session_list.events) {
39 if (this.session_list && this.session_list.events) {
40 this.session_list.events.on('sessions_loaded.Dashboard',
40 this.session_list.events.on('sessions_loaded.Dashboard',
41 function(e, d) { that.sessions_loaded(d); });
41 function(e, d) { that.sessions_loaded(d); });
42 }
42 }
43 };
43 };
44
44
45 NotebookList.prototype.style = function () {
45 NotebookList.prototype.style = function () {
46 var prefix = '#' + this.element_name;
46 var prefix = '#' + this.element_name;
47 $(prefix + '_toolbar').addClass('list_toolbar');
47 $(prefix + '_toolbar').addClass('list_toolbar');
48 $(prefix + '_list_info').addClass('toolbar_info');
48 $(prefix + '_list_info').addClass('toolbar_info');
49 $(prefix + '_buttons').addClass('toolbar_buttons');
49 $(prefix + '_buttons').addClass('toolbar_buttons');
50 $(prefix + '_list_header').addClass('list_header');
50 $(prefix + '_list_header').addClass('list_header');
51 this.element.addClass("list_container");
51 this.element.addClass("list_container");
52 };
52 };
53
53
54
54
55 NotebookList.prototype.bind_events = function () {
55 NotebookList.prototype.bind_events = function () {
56 var that = this;
56 var that = this;
57 $('#refresh_' + this.element_name + '_list').click(function () {
57 $('#refresh_' + this.element_name + '_list').click(function () {
58 that.load_sessions();
58 that.load_sessions();
59 });
59 });
60 this.element.bind('dragover', function () {
60 this.element.bind('dragover', function () {
61 return false;
61 return false;
62 });
62 });
63 this.element.bind('drop', function(event){
63 this.element.bind('drop', function(event){
64 that.handleFilesUpload(event,'drop');
64 that.handleFilesUpload(event,'drop');
65 return false;
65 return false;
66 });
66 });
67 };
67 };
68
68
69 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
69 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
70 var that = this;
70 var that = this;
71 var files;
71 var files;
72 if(dropOrForm =='drop'){
72 if(dropOrForm =='drop'){
73 files = event.originalEvent.dataTransfer.files;
73 files = event.originalEvent.dataTransfer.files;
74 } else
74 } else
75 {
75 {
76 files = event.originalEvent.target.files;
76 files = event.originalEvent.target.files;
77 }
77 }
78 for (var i = 0; i < files.length; i++) {
78 for (var i = 0; i < files.length; i++) {
79 var f = files[i];
79 var f = files[i];
80 var name_and_ext = utils.splitext(f.name);
80 var name_and_ext = utils.splitext(f.name);
81 var file_ext = name_and_ext[1];
81 var file_ext = name_and_ext[1];
82
82
83 var reader = new FileReader();
83 var reader = new FileReader();
84 if (file_ext === '.ipynb') {
84 if (file_ext === '.ipynb') {
85 reader.readAsText(f);
85 reader.readAsText(f);
86 } else {
86 } else {
87 // read non-notebook files as binary
87 // read non-notebook files as binary
88 reader.readAsArrayBuffer(f);
88 reader.readAsArrayBuffer(f);
89 }
89 }
90 var item = that.new_item(0);
90 var item = that.new_item(0);
91 item.addClass('new-file');
91 item.addClass('new-file');
92 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
92 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
93 // Store the list item in the reader so we can use it later
93 // Store the list item in the reader so we can use it later
94 // to know which item it belongs to.
94 // to know which item it belongs to.
95 $(reader).data('item', item);
95 $(reader).data('item', item);
96 reader.onload = function (event) {
96 reader.onload = function (event) {
97 var item = $(event.target).data('item');
97 var item = $(event.target).data('item');
98 that.add_file_data(event.target.result, item);
98 that.add_file_data(event.target.result, item);
99 that.add_upload_button(item);
99 that.add_upload_button(item);
100 };
100 };
101 reader.onerror = function (event) {
101 reader.onerror = function (event) {
102 var item = $(event.target).data('item');
102 var item = $(event.target).data('item');
103 var name = item.data('name');
103 var name = item.data('name');
104 item.remove();
104 item.remove();
105 dialog.modal({
105 dialog.modal({
106 title : 'Failed to read file',
106 title : 'Failed to read file',
107 body : "Failed to read file '" + name + "'",
107 body : "Failed to read file '" + name + "'",
108 buttons : {'OK' : { 'class' : 'btn-primary' }}
108 buttons : {'OK' : { 'class' : 'btn-primary' }}
109 });
109 });
110 };
110 };
111 }
111 }
112 // Replace the file input form wth a clone of itself. This is required to
112 // Replace the file input form wth a clone of itself. This is required to
113 // reset the form. Otherwise, if you upload a file, delete it and try to
113 // reset the form. Otherwise, if you upload a file, delete it and try to
114 // upload it again, the changed event won't fire.
114 // upload it again, the changed event won't fire.
115 var form = $('input.fileinput');
115 var form = $('input.fileinput');
116 form.replaceWith(form.clone(true));
116 form.replaceWith(form.clone(true));
117 return false;
117 return false;
118 };
118 };
119
119
120 NotebookList.prototype.clear_list = function (remove_uploads) {
120 NotebookList.prototype.clear_list = function (remove_uploads) {
121 // Clears the navigation tree.
121 // Clears the navigation tree.
122 //
122 //
123 // Parameters
123 // Parameters
124 // remove_uploads: bool=False
124 // remove_uploads: bool=False
125 // Should upload prompts also be removed from the tree.
125 // Should upload prompts also be removed from the tree.
126 if (remove_uploads) {
126 if (remove_uploads) {
127 this.element.children('.list_item').remove();
127 this.element.children('.list_item').remove();
128 } else {
128 } else {
129 this.element.children('.list_item:not(.new-file)').remove();
129 this.element.children('.list_item:not(.new-file)').remove();
130 }
130 }
131 };
131 };
132
132
133 NotebookList.prototype.load_sessions = function(){
133 NotebookList.prototype.load_sessions = function(){
134 this.session_list.load_sessions();
134 this.session_list.load_sessions();
135 };
135 };
136
136
137
137
138 NotebookList.prototype.sessions_loaded = function(data){
138 NotebookList.prototype.sessions_loaded = function(data){
139 this.sessions = data;
139 this.sessions = data;
140 this.load_list();
140 this.load_list();
141 };
141 };
142
142
143 NotebookList.prototype.load_list = function () {
143 NotebookList.prototype.load_list = function () {
144 var that = this;
144 var that = this;
145 this.contents.list_contents(that.notebook_path, {
145 this.contents.list_contents(that.notebook_path).then(
146 success: $.proxy(this.draw_notebook_list, this),
146 $.proxy(this.draw_notebook_list, this),
147 error: function(error) {
147 function(error) {
148 that.draw_notebook_list({content: []}, "Server error: " + error.message);
148 that.draw_notebook_list({content: []}, "Server error: " + error.message);
149 }
149 }
150 });
150 );
151 };
151 };
152
152
153 /**
153 /**
154 * Draw the list of notebooks
154 * Draw the list of notebooks
155 * @method draw_notebook_list
155 * @method draw_notebook_list
156 * @param {Array} list An array of dictionaries representing files or
156 * @param {Array} list An array of dictionaries representing files or
157 * directories.
157 * directories.
158 * @param {String} error_msg An error message
158 * @param {String} error_msg An error message
159 */
159 */
160 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
160 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
161 var message = error_msg || 'Notebook list empty.';
161 var message = error_msg || 'Notebook list empty.';
162 var item = null;
162 var item = null;
163 var model = null;
163 var model = null;
164 var len = list.content.length;
164 var len = list.content.length;
165 this.clear_list();
165 this.clear_list();
166 var n_uploads = this.element.children('.list_item').length;
166 var n_uploads = this.element.children('.list_item').length;
167 if (len === 0) {
167 if (len === 0) {
168 item = this.new_item(0);
168 item = this.new_item(0);
169 var span12 = item.children().first();
169 var span12 = item.children().first();
170 span12.empty();
170 span12.empty();
171 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
171 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
172 }
172 }
173 var path = this.notebook_path;
173 var path = this.notebook_path;
174 var offset = n_uploads;
174 var offset = n_uploads;
175 if (path !== '') {
175 if (path !== '') {
176 item = this.new_item(offset);
176 item = this.new_item(offset);
177 model = {
177 model = {
178 type: 'directory',
178 type: 'directory',
179 name: '..',
179 name: '..',
180 path: utils.url_path_split(path)[0],
180 path: utils.url_path_split(path)[0],
181 };
181 };
182 this.add_link(model, item);
182 this.add_link(model, item);
183 offset += 1;
183 offset += 1;
184 }
184 }
185 for (var i=0; i<len; i++) {
185 for (var i=0; i<len; i++) {
186 model = list.content[i];
186 model = list.content[i];
187 item = this.new_item(i+offset);
187 item = this.new_item(i+offset);
188 this.add_link(model, item);
188 this.add_link(model, item);
189 }
189 }
190 };
190 };
191
191
192
192
193 NotebookList.prototype.new_item = function (index) {
193 NotebookList.prototype.new_item = function (index) {
194 var item = $('<div/>').addClass("list_item").addClass("row");
194 var item = $('<div/>').addClass("list_item").addClass("row");
195 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
195 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
196 // item.css('border-top-style','none');
196 // item.css('border-top-style','none');
197 item.append($("<div/>").addClass("col-md-12").append(
197 item.append($("<div/>").addClass("col-md-12").append(
198 $('<i/>').addClass('item_icon')
198 $('<i/>').addClass('item_icon')
199 ).append(
199 ).append(
200 $("<a/>").addClass("item_link").append(
200 $("<a/>").addClass("item_link").append(
201 $("<span/>").addClass("item_name")
201 $("<span/>").addClass("item_name")
202 )
202 )
203 ).append(
203 ).append(
204 $('<div/>').addClass("item_buttons btn-group pull-right")
204 $('<div/>').addClass("item_buttons btn-group pull-right")
205 ));
205 ));
206
206
207 if (index === -1) {
207 if (index === -1) {
208 this.element.append(item);
208 this.element.append(item);
209 } else {
209 } else {
210 this.element.children().eq(index).after(item);
210 this.element.children().eq(index).after(item);
211 }
211 }
212 return item;
212 return item;
213 };
213 };
214
214
215
215
216 NotebookList.icons = {
216 NotebookList.icons = {
217 directory: 'folder_icon',
217 directory: 'folder_icon',
218 notebook: 'notebook_icon',
218 notebook: 'notebook_icon',
219 file: 'file_icon',
219 file: 'file_icon',
220 };
220 };
221
221
222 NotebookList.uri_prefixes = {
222 NotebookList.uri_prefixes = {
223 directory: 'tree',
223 directory: 'tree',
224 notebook: 'notebooks',
224 notebook: 'notebooks',
225 file: 'files',
225 file: 'files',
226 };
226 };
227
227
228
228
229 NotebookList.prototype.add_link = function (model, item) {
229 NotebookList.prototype.add_link = function (model, item) {
230 var path = model.path,
230 var path = model.path,
231 name = model.name;
231 name = model.name;
232 item.data('name', name);
232 item.data('name', name);
233 item.data('path', path);
233 item.data('path', path);
234 item.find(".item_name").text(name);
234 item.find(".item_name").text(name);
235 var icon = NotebookList.icons[model.type];
235 var icon = NotebookList.icons[model.type];
236 var uri_prefix = NotebookList.uri_prefixes[model.type];
236 var uri_prefix = NotebookList.uri_prefixes[model.type];
237 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
237 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
238 var link = item.find("a.item_link")
238 var link = item.find("a.item_link")
239 .attr('href',
239 .attr('href',
240 utils.url_join_encode(
240 utils.url_join_encode(
241 this.base_url,
241 this.base_url,
242 uri_prefix,
242 uri_prefix,
243 path
243 path
244 )
244 )
245 );
245 );
246 // directory nav doesn't open new tabs
246 // directory nav doesn't open new tabs
247 // files, notebooks do
247 // files, notebooks do
248 if (model.type !== "directory") {
248 if (model.type !== "directory") {
249 link.attr('target','_blank');
249 link.attr('target','_blank');
250 }
250 }
251 var path_name = utils.url_path_join(path, name);
251 var path_name = utils.url_path_join(path, name);
252 if (model.type == 'file') {
252 if (model.type == 'file') {
253 this.add_delete_button(item);
253 this.add_delete_button(item);
254 } else if (model.type == 'notebook') {
254 } else if (model.type == 'notebook') {
255 if(this.sessions[path_name] === undefined){
255 if(this.sessions[path_name] === undefined){
256 this.add_delete_button(item);
256 this.add_delete_button(item);
257 } else {
257 } else {
258 this.add_shutdown_button(item, this.sessions[path_name]);
258 this.add_shutdown_button(item, this.sessions[path_name]);
259 }
259 }
260 }
260 }
261 };
261 };
262
262
263
263
264 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
264 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
265 item.data('name', name);
265 item.data('name', name);
266 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
266 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
267 item.find(".item_name").empty().append(
267 item.find(".item_name").empty().append(
268 $('<input/>')
268 $('<input/>')
269 .addClass("filename_input")
269 .addClass("filename_input")
270 .attr('value', name)
270 .attr('value', name)
271 .attr('size', '30')
271 .attr('size', '30')
272 .attr('type', 'text')
272 .attr('type', 'text')
273 .keyup(function(event){
273 .keyup(function(event){
274 if(event.keyCode == 13){item.find('.upload_button').click();}
274 if(event.keyCode == 13){item.find('.upload_button').click();}
275 else if(event.keyCode == 27){item.remove();}
275 else if(event.keyCode == 27){item.remove();}
276 })
276 })
277 );
277 );
278 };
278 };
279
279
280
280
281 NotebookList.prototype.add_file_data = function (data, item) {
281 NotebookList.prototype.add_file_data = function (data, item) {
282 item.data('filedata', data);
282 item.data('filedata', data);
283 };
283 };
284
284
285
285
286 NotebookList.prototype.add_shutdown_button = function (item, session) {
286 NotebookList.prototype.add_shutdown_button = function (item, session) {
287 var that = this;
287 var that = this;
288 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
288 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
289 click(function (e) {
289 click(function (e) {
290 var settings = {
290 var settings = {
291 processData : false,
291 processData : false,
292 cache : false,
292 cache : false,
293 type : "DELETE",
293 type : "DELETE",
294 dataType : "json",
294 dataType : "json",
295 success : function () {
295 success : function () {
296 that.load_sessions();
296 that.load_sessions();
297 },
297 },
298 error : utils.log_ajax_error,
298 error : utils.log_ajax_error,
299 };
299 };
300 var url = utils.url_join_encode(
300 var url = utils.url_join_encode(
301 that.base_url,
301 that.base_url,
302 'api/sessions',
302 'api/sessions',
303 session
303 session
304 );
304 );
305 $.ajax(url, settings);
305 $.ajax(url, settings);
306 return false;
306 return false;
307 });
307 });
308 // var new_buttons = item.find('a'); // shutdown_button;
308 // var new_buttons = item.find('a'); // shutdown_button;
309 item.find(".item_buttons").text("").append(shutdown_button);
309 item.find(".item_buttons").text("").append(shutdown_button);
310 };
310 };
311
311
312 NotebookList.prototype.add_delete_button = function (item) {
312 NotebookList.prototype.add_delete_button = function (item) {
313 var notebooklist = this;
313 var notebooklist = this;
314 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
314 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
315 click(function (e) {
315 click(function (e) {
316 // $(this) is the button that was clicked.
316 // $(this) is the button that was clicked.
317 var that = $(this);
317 var that = $(this);
318 // We use the filename from the parent list_item element's
318 // We use the filename from the parent list_item element's
319 // data because the outer scope's values change as we iterate through the loop.
319 // data because the outer scope's values change as we iterate through the loop.
320 var parent_item = that.parents('div.list_item');
320 var parent_item = that.parents('div.list_item');
321 var name = parent_item.data('name');
321 var name = parent_item.data('name');
322 var path = parent_item.data('path');
322 var path = parent_item.data('path');
323 var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
323 var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
324 dialog.modal({
324 dialog.modal({
325 title : "Delete file",
325 title : "Delete file",
326 body : message,
326 body : message,
327 buttons : {
327 buttons : {
328 Delete : {
328 Delete : {
329 class: "btn-danger",
329 class: "btn-danger",
330 click: function() {
330 click: function() {
331 notebooklist.contents.delete(path, {
331 notebooklist.contents.delete(path).then(
332 success: function() {
332 function() {
333 notebooklist.notebook_deleted(path);
333 notebooklist.notebook_deleted(path);
334 }
334 }
335 });
335 );
336 }
336 }
337 },
337 },
338 Cancel : {}
338 Cancel : {}
339 }
339 }
340 });
340 });
341 return false;
341 return false;
342 });
342 });
343 item.find(".item_buttons").text("").append(delete_button);
343 item.find(".item_buttons").text("").append(delete_button);
344 };
344 };
345
345
346 NotebookList.prototype.notebook_deleted = function(path) {
346 NotebookList.prototype.notebook_deleted = function(path) {
347 // Remove the deleted notebook.
347 // Remove the deleted notebook.
348 $( ":data(path)" ).each(function() {
348 $( ":data(path)" ).each(function() {
349 var element = $(this);
349 var element = $(this);
350 if (element.data("path") == path) {
350 if (element.data("path") == path) {
351 element.remove();
351 element.remove();
352 }
352 }
353 });
353 });
354 };
354 };
355
355
356
356
357 NotebookList.prototype.add_upload_button = function (item) {
357 NotebookList.prototype.add_upload_button = function (item) {
358 var that = this;
358 var that = this;
359 var upload_button = $('<button/>').text("Upload")
359 var upload_button = $('<button/>').text("Upload")
360 .addClass('btn btn-primary btn-xs upload_button')
360 .addClass('btn btn-primary btn-xs upload_button')
361 .click(function (e) {
361 .click(function (e) {
362 var filename = item.find('.item_name > input').val();
362 var filename = item.find('.item_name > input').val();
363 var path = utils.url_path_join(that.notebook_path, filename);
363 var path = utils.url_path_join(that.notebook_path, filename);
364 var filedata = item.data('filedata');
364 var filedata = item.data('filedata');
365 var format = 'text';
365 var format = 'text';
366 if (filename.length === 0 || filename[0] === '.') {
366 if (filename.length === 0 || filename[0] === '.') {
367 dialog.modal({
367 dialog.modal({
368 title : 'Invalid file name',
368 title : 'Invalid file name',
369 body : "File names must be at least one character and not start with a dot",
369 body : "File names must be at least one character and not start with a dot",
370 buttons : {'OK' : { 'class' : 'btn-primary' }}
370 buttons : {'OK' : { 'class' : 'btn-primary' }}
371 });
371 });
372 return false;
372 return false;
373 }
373 }
374 if (filedata instanceof ArrayBuffer) {
374 if (filedata instanceof ArrayBuffer) {
375 // base64-encode binary file data
375 // base64-encode binary file data
376 var bytes = '';
376 var bytes = '';
377 var buf = new Uint8Array(filedata);
377 var buf = new Uint8Array(filedata);
378 var nbytes = buf.byteLength;
378 var nbytes = buf.byteLength;
379 for (var i=0; i<nbytes; i++) {
379 for (var i=0; i<nbytes; i++) {
380 bytes += String.fromCharCode(buf[i]);
380 bytes += String.fromCharCode(buf[i]);
381 }
381 }
382 filedata = btoa(bytes);
382 filedata = btoa(bytes);
383 format = 'base64';
383 format = 'base64';
384 }
384 }
385 var model = {};
385 var model = {};
386
386
387 var name_and_ext = utils.splitext(filename);
387 var name_and_ext = utils.splitext(filename);
388 var file_ext = name_and_ext[1];
388 var file_ext = name_and_ext[1];
389 var content_type;
389 var content_type;
390 if (file_ext === '.ipynb') {
390 if (file_ext === '.ipynb') {
391 model.type = 'notebook';
391 model.type = 'notebook';
392 model.format = 'json';
392 model.format = 'json';
393 try {
393 try {
394 model.content = JSON.parse(filedata);
394 model.content = JSON.parse(filedata);
395 } catch (e) {
395 } catch (e) {
396 dialog.modal({
396 dialog.modal({
397 title : 'Cannot upload invalid Notebook',
397 title : 'Cannot upload invalid Notebook',
398 body : "The error was: " + e,
398 body : "The error was: " + e,
399 buttons : {'OK' : {
399 buttons : {'OK' : {
400 'class' : 'btn-primary',
400 'class' : 'btn-primary',
401 click: function () {
401 click: function () {
402 item.remove();
402 item.remove();
403 }
403 }
404 }}
404 }}
405 });
405 });
406 return false;
406 return false;
407 }
407 }
408 content_type = 'application/json';
408 content_type = 'application/json';
409 } else {
409 } else {
410 model.type = 'file';
410 model.type = 'file';
411 model.format = format;
411 model.format = format;
412 model.content = filedata;
412 model.content = filedata;
413 content_type = 'application/octet-stream';
413 content_type = 'application/octet-stream';
414 }
414 }
415 filedata = item.data('filedata');
415 filedata = item.data('filedata');
416
416
417 var settings = {
417 var on_success = function () {
418 success : function () {
418 item.removeClass('new-file');
419 item.removeClass('new-file');
419 that.add_link(model, item);
420 that.add_link(model, item);
420 that.add_delete_button(item);
421 that.add_delete_button(item);
421 that.session_list.load_sessions();
422 that.session_list.load_sessions();
423 },
424 };
422 };
425
423
426 var exists = false;
424 var exists = false;
427 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
425 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
428 if ($(v).data('name') === filename) { exists = true; return false; }
426 if ($(v).data('name') === filename) { exists = true; return false; }
429 });
427 });
430
428
431 if (exists) {
429 if (exists) {
432 dialog.modal({
430 dialog.modal({
433 title : "Replace file",
431 title : "Replace file",
434 body : 'There is already a file named ' + filename + ', do you want to replace it?',
432 body : 'There is already a file named ' + filename + ', do you want to replace it?',
435 buttons : {
433 buttons : {
436 Overwrite : {
434 Overwrite : {
437 class: "btn-danger",
435 class: "btn-danger",
438 click: function () {
436 click: function () {
439 that.contents.save(path, model, settings);
437 that.contents.save(path, model).then(on_success);
440 }
438 }
441 },
439 },
442 Cancel : {
440 Cancel : {
443 click: function() { item.remove(); }
441 click: function() { item.remove(); }
444 }
442 }
445 }
443 }
446 });
444 });
447 } else {
445 } else {
448 that.contents.save(path, model, settings);
446 that.contents.save(path, model).then(on_success);
449 }
447 }
450
448
451 return false;
449 return false;
452 });
450 });
453 var cancel_button = $('<button/>').text("Cancel")
451 var cancel_button = $('<button/>').text("Cancel")
454 .addClass("btn btn-default btn-xs")
452 .addClass("btn btn-default btn-xs")
455 .click(function (e) {
453 .click(function (e) {
456 item.remove();
454 item.remove();
457 return false;
455 return false;
458 });
456 });
459 item.find(".item_buttons").empty()
457 item.find(".item_buttons").empty()
460 .append(upload_button)
458 .append(upload_button)
461 .append(cancel_button);
459 .append(cancel_button);
462 };
460 };
463
461
464
462
465 // Backwards compatability.
463 // Backwards compatability.
466 IPython.NotebookList = NotebookList;
464 IPython.NotebookList = NotebookList;
467
465
468 return {'NotebookList': NotebookList};
466 return {'NotebookList': NotebookList};
469 });
467 });
@@ -1,107 +1,108 b''
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
12
13 {% block stylesheet %}
13 {% block stylesheet %}
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
15 {% endblock %}
15 {% endblock %}
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
17 <script src="{{static_url("components/es6-promise/promise.min.js")}}" type="text/javascript" charset="utf-8"></script>
17 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
18 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
18 <script>
19 <script>
19 require.config({
20 require.config({
20 baseUrl: '{{static_url("", include_version=False)}}',
21 baseUrl: '{{static_url("", include_version=False)}}',
21 paths: {
22 paths: {
22 nbextensions : '{{ base_url }}nbextensions',
23 nbextensions : '{{ base_url }}nbextensions',
23 underscore : 'components/underscore/underscore-min',
24 underscore : 'components/underscore/underscore-min',
24 backbone : 'components/backbone/backbone-min',
25 backbone : 'components/backbone/backbone-min',
25 jquery: 'components/jquery/jquery.min',
26 jquery: 'components/jquery/jquery.min',
26 bootstrap: 'components/bootstrap/js/bootstrap.min',
27 bootstrap: 'components/bootstrap/js/bootstrap.min',
27 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
28 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
28 jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
29 jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
29 highlight: 'components/highlight.js/build/highlight.pack',
30 highlight: 'components/highlight.js/build/highlight.pack',
30 moment: "components/moment/moment",
31 moment: "components/moment/moment",
31 codemirror: 'components/codemirror',
32 codemirror: 'components/codemirror',
32 termjs: "components/term.js/src/term",
33 termjs: "components/term.js/src/term",
33 contents: '{{ contents_js_source }}',
34 contents: '{{ contents_js_source }}',
34 },
35 },
35 shim: {
36 shim: {
36 underscore: {
37 underscore: {
37 exports: '_'
38 exports: '_'
38 },
39 },
39 backbone: {
40 backbone: {
40 deps: ["underscore", "jquery"],
41 deps: ["underscore", "jquery"],
41 exports: "Backbone"
42 exports: "Backbone"
42 },
43 },
43 bootstrap: {
44 bootstrap: {
44 deps: ["jquery"],
45 deps: ["jquery"],
45 exports: "bootstrap"
46 exports: "bootstrap"
46 },
47 },
47 bootstraptour: {
48 bootstraptour: {
48 deps: ["bootstrap"],
49 deps: ["bootstrap"],
49 exports: "Tour"
50 exports: "Tour"
50 },
51 },
51 jqueryui: {
52 jqueryui: {
52 deps: ["jquery"],
53 deps: ["jquery"],
53 exports: "$"
54 exports: "$"
54 },
55 },
55 highlight: {
56 highlight: {
56 exports: "hljs"
57 exports: "hljs"
57 },
58 },
58 }
59 }
59 });
60 });
60 </script>
61 </script>
61
62
62 {% block meta %}
63 {% block meta %}
63 {% endblock %}
64 {% endblock %}
64
65
65 </head>
66 </head>
66
67
67 <body {% block params %}{% endblock %}>
68 <body {% block params %}{% endblock %}>
68
69
69 <noscript>
70 <noscript>
70 <div id='noscript'>
71 <div id='noscript'>
71 IPython Notebook requires JavaScript.<br>
72 IPython Notebook requires JavaScript.<br>
72 Please enable it to proceed.
73 Please enable it to proceed.
73 </div>
74 </div>
74 </noscript>
75 </noscript>
75
76
76 <div id="header" class="navbar navbar-static-top">
77 <div id="header" class="navbar navbar-static-top">
77 <div class="container">
78 <div class="container">
78 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
79 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
79
80
80 {% block login_widget %}
81 {% block login_widget %}
81
82
82 <span id="login_widget">
83 <span id="login_widget">
83 {% if logged_in %}
84 {% if logged_in %}
84 <button id="logout">Logout</button>
85 <button id="logout">Logout</button>
85 {% elif login_available and not logged_in %}
86 {% elif login_available and not logged_in %}
86 <button id="login">Login</button>
87 <button id="login">Login</button>
87 {% endif %}
88 {% endif %}
88 </span>
89 </span>
89
90
90 {% endblock %}
91 {% endblock %}
91
92
92 {% block header %}
93 {% block header %}
93 {% endblock %}
94 {% endblock %}
94 </div>
95 </div>
95 </div>
96 </div>
96
97
97 <div id="site">
98 <div id="site">
98 {% block site %}
99 {% block site %}
99 {% endblock %}
100 {% endblock %}
100 </div>
101 </div>
101
102
102 {% block script %}
103 {% block script %}
103 {% endblock %}
104 {% endblock %}
104
105
105 </body>
106 </body>
106
107
107 </html>
108 </html>
@@ -1,754 +1,755 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 import errno
17 import errno
18 import os
18 import os
19 import sys
19 import sys
20
20
21 from distutils import log
21 from distutils import log
22 from distutils.command.build_py import build_py
22 from distutils.command.build_py import build_py
23 from distutils.command.build_scripts import build_scripts
23 from distutils.command.build_scripts import build_scripts
24 from distutils.command.install import install
24 from distutils.command.install import install
25 from distutils.command.install_scripts import install_scripts
25 from distutils.command.install_scripts import install_scripts
26 from distutils.cmd import Command
26 from distutils.cmd import Command
27 from fnmatch import fnmatch
27 from fnmatch import fnmatch
28 from glob import glob
28 from glob import glob
29 from subprocess import check_call
29 from subprocess import check_call
30
30
31 from setupext import install_data_ext
31 from setupext import install_data_ext
32
32
33 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
34 # Useful globals and utility functions
34 # Useful globals and utility functions
35 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
36
36
37 # A few handy globals
37 # A few handy globals
38 isfile = os.path.isfile
38 isfile = os.path.isfile
39 pjoin = os.path.join
39 pjoin = os.path.join
40 repo_root = os.path.dirname(os.path.abspath(__file__))
40 repo_root = os.path.dirname(os.path.abspath(__file__))
41
41
42 def oscmd(s):
42 def oscmd(s):
43 print(">", s)
43 print(">", s)
44 os.system(s)
44 os.system(s)
45
45
46 # Py3 compatibility hacks, without assuming IPython itself is installed with
46 # Py3 compatibility hacks, without assuming IPython itself is installed with
47 # the full py3compat machinery.
47 # the full py3compat machinery.
48
48
49 try:
49 try:
50 execfile
50 execfile
51 except NameError:
51 except NameError:
52 def execfile(fname, globs, locs=None):
52 def execfile(fname, globs, locs=None):
53 locs = locs or globs
53 locs = locs or globs
54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
55
55
56 # A little utility we'll need below, since glob() does NOT allow you to do
56 # A little utility we'll need below, since glob() does NOT allow you to do
57 # exclusion on multiple endings!
57 # exclusion on multiple endings!
58 def file_doesnt_endwith(test,endings):
58 def file_doesnt_endwith(test,endings):
59 """Return true if test is a file and its name does NOT end with any
59 """Return true if test is a file and its name does NOT end with any
60 of the strings listed in endings."""
60 of the strings listed in endings."""
61 if not isfile(test):
61 if not isfile(test):
62 return False
62 return False
63 for e in endings:
63 for e in endings:
64 if test.endswith(e):
64 if test.endswith(e):
65 return False
65 return False
66 return True
66 return True
67
67
68 #---------------------------------------------------------------------------
68 #---------------------------------------------------------------------------
69 # Basic project information
69 # Basic project information
70 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
71
71
72 # release.py contains version, authors, license, url, keywords, etc.
72 # release.py contains version, authors, license, url, keywords, etc.
73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
74
74
75 # Create a dict with the basic information
75 # Create a dict with the basic information
76 # This dict is eventually passed to setup after additional keys are added.
76 # This dict is eventually passed to setup after additional keys are added.
77 setup_args = dict(
77 setup_args = dict(
78 name = name,
78 name = name,
79 version = version,
79 version = version,
80 description = description,
80 description = description,
81 long_description = long_description,
81 long_description = long_description,
82 author = author,
82 author = author,
83 author_email = author_email,
83 author_email = author_email,
84 url = url,
84 url = url,
85 download_url = download_url,
85 download_url = download_url,
86 license = license,
86 license = license,
87 platforms = platforms,
87 platforms = platforms,
88 keywords = keywords,
88 keywords = keywords,
89 classifiers = classifiers,
89 classifiers = classifiers,
90 cmdclass = {'install_data': install_data_ext},
90 cmdclass = {'install_data': install_data_ext},
91 )
91 )
92
92
93
93
94 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
95 # Find packages
95 # Find packages
96 #---------------------------------------------------------------------------
96 #---------------------------------------------------------------------------
97
97
98 def find_packages():
98 def find_packages():
99 """
99 """
100 Find all of IPython's packages.
100 Find all of IPython's packages.
101 """
101 """
102 excludes = ['deathrow', 'quarantine']
102 excludes = ['deathrow', 'quarantine']
103 packages = []
103 packages = []
104 for dir,subdirs,files in os.walk('IPython'):
104 for dir,subdirs,files in os.walk('IPython'):
105 package = dir.replace(os.path.sep, '.')
105 package = dir.replace(os.path.sep, '.')
106 if any(package.startswith('IPython.'+exc) for exc in excludes):
106 if any(package.startswith('IPython.'+exc) for exc in excludes):
107 # package is to be excluded (e.g. deathrow)
107 # package is to be excluded (e.g. deathrow)
108 continue
108 continue
109 if '__init__.py' not in files:
109 if '__init__.py' not in files:
110 # not a package
110 # not a package
111 continue
111 continue
112 packages.append(package)
112 packages.append(package)
113 return packages
113 return packages
114
114
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116 # Find package data
116 # Find package data
117 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
118
118
119 def find_package_data():
119 def find_package_data():
120 """
120 """
121 Find IPython's package_data.
121 Find IPython's package_data.
122 """
122 """
123 # This is not enough for these things to appear in an sdist.
123 # This is not enough for these things to appear in an sdist.
124 # We need to muck with the MANIFEST to get this to work
124 # We need to muck with the MANIFEST to get this to work
125
125
126 # exclude components and less from the walk;
126 # exclude components and less from the walk;
127 # we will build the components separately
127 # we will build the components separately
128 excludes = [
128 excludes = [
129 pjoin('static', 'components'),
129 pjoin('static', 'components'),
130 pjoin('static', '*', 'less'),
130 pjoin('static', '*', 'less'),
131 ]
131 ]
132
132
133 # walk notebook resources:
133 # walk notebook resources:
134 cwd = os.getcwd()
134 cwd = os.getcwd()
135 os.chdir(os.path.join('IPython', 'html'))
135 os.chdir(os.path.join('IPython', 'html'))
136 static_data = []
136 static_data = []
137 for parent, dirs, files in os.walk('static'):
137 for parent, dirs, files in os.walk('static'):
138 if any(fnmatch(parent, pat) for pat in excludes):
138 if any(fnmatch(parent, pat) for pat in excludes):
139 # prevent descending into subdirs
139 # prevent descending into subdirs
140 dirs[:] = []
140 dirs[:] = []
141 continue
141 continue
142 for f in files:
142 for f in files:
143 static_data.append(pjoin(parent, f))
143 static_data.append(pjoin(parent, f))
144
144
145 components = pjoin("static", "components")
145 components = pjoin("static", "components")
146 # select the components we actually need to install
146 # select the components we actually need to install
147 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
147 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
148 static_data.extend([
148 static_data.extend([
149 pjoin(components, "backbone", "backbone-min.js"),
149 pjoin(components, "backbone", "backbone-min.js"),
150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
153 pjoin(components, "es6-promise", "*.js"),
153 pjoin(components, "font-awesome", "fonts", "*.*"),
154 pjoin(components, "font-awesome", "fonts", "*.*"),
154 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
155 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
155 pjoin(components, "highlight.js", "build", "highlight.pack.js"),
156 pjoin(components, "highlight.js", "build", "highlight.pack.js"),
156 pjoin(components, "jquery", "jquery.min.js"),
157 pjoin(components, "jquery", "jquery.min.js"),
157 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
158 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
158 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
160 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
160 pjoin(components, "marked", "lib", "marked.js"),
161 pjoin(components, "marked", "lib", "marked.js"),
161 pjoin(components, "requirejs", "require.js"),
162 pjoin(components, "requirejs", "require.js"),
162 pjoin(components, "underscore", "underscore-min.js"),
163 pjoin(components, "underscore", "underscore-min.js"),
163 pjoin(components, "moment", "moment.js"),
164 pjoin(components, "moment", "moment.js"),
164 pjoin(components, "moment", "min", "moment.min.js"),
165 pjoin(components, "moment", "min", "moment.min.js"),
165 pjoin(components, "term.js", "src", "term.js"),
166 pjoin(components, "term.js", "src", "term.js"),
166 pjoin(components, "text-encoding", "lib", "encoding.js"),
167 pjoin(components, "text-encoding", "lib", "encoding.js"),
167 ])
168 ])
168
169
169 # Ship all of Codemirror's CSS and JS
170 # Ship all of Codemirror's CSS and JS
170 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
171 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
171 for f in files:
172 for f in files:
172 if f.endswith(('.js', '.css')):
173 if f.endswith(('.js', '.css')):
173 static_data.append(pjoin(parent, f))
174 static_data.append(pjoin(parent, f))
174
175
175 os.chdir(os.path.join('tests',))
176 os.chdir(os.path.join('tests',))
176 js_tests = glob('*.js') + glob('*/*.js')
177 js_tests = glob('*.js') + glob('*/*.js')
177
178
178 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
179 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
179 nbconvert_templates = [os.path.join(dirpath, '*.*')
180 nbconvert_templates = [os.path.join(dirpath, '*.*')
180 for dirpath, _, _ in os.walk('templates')]
181 for dirpath, _, _ in os.walk('templates')]
181
182
182 os.chdir(cwd)
183 os.chdir(cwd)
183
184
184 package_data = {
185 package_data = {
185 'IPython.config.profile' : ['README*', '*/*.py'],
186 'IPython.config.profile' : ['README*', '*/*.py'],
186 'IPython.core.tests' : ['*.png', '*.jpg'],
187 'IPython.core.tests' : ['*.png', '*.jpg'],
187 'IPython.lib.tests' : ['*.wav'],
188 'IPython.lib.tests' : ['*.wav'],
188 'IPython.testing.plugin' : ['*.txt'],
189 'IPython.testing.plugin' : ['*.txt'],
189 'IPython.html' : ['templates/*'] + static_data,
190 'IPython.html' : ['templates/*'] + static_data,
190 'IPython.html.tests' : js_tests,
191 'IPython.html.tests' : js_tests,
191 'IPython.qt.console' : ['resources/icon/*.svg'],
192 'IPython.qt.console' : ['resources/icon/*.svg'],
192 'IPython.nbconvert' : nbconvert_templates +
193 'IPython.nbconvert' : nbconvert_templates +
193 [
194 [
194 'tests/files/*.*',
195 'tests/files/*.*',
195 'exporters/tests/files/*.*',
196 'exporters/tests/files/*.*',
196 'preprocessors/tests/files/*.*',
197 'preprocessors/tests/files/*.*',
197 ],
198 ],
198 'IPython.nbconvert.filters' : ['marked.js'],
199 'IPython.nbconvert.filters' : ['marked.js'],
199 'IPython.nbformat' : [
200 'IPython.nbformat' : [
200 'tests/*.ipynb',
201 'tests/*.ipynb',
201 'v3/nbformat.v3.schema.json',
202 'v3/nbformat.v3.schema.json',
202 'v4/nbformat.v4.schema.json',
203 'v4/nbformat.v4.schema.json',
203 ]
204 ]
204 }
205 }
205
206
206 return package_data
207 return package_data
207
208
208
209
209 def check_package_data(package_data):
210 def check_package_data(package_data):
210 """verify that package_data globs make sense"""
211 """verify that package_data globs make sense"""
211 print("checking package data")
212 print("checking package data")
212 for pkg, data in package_data.items():
213 for pkg, data in package_data.items():
213 pkg_root = pjoin(*pkg.split('.'))
214 pkg_root = pjoin(*pkg.split('.'))
214 for d in data:
215 for d in data:
215 path = pjoin(pkg_root, d)
216 path = pjoin(pkg_root, d)
216 if '*' in path:
217 if '*' in path:
217 assert len(glob(path)) > 0, "No files match pattern %s" % path
218 assert len(glob(path)) > 0, "No files match pattern %s" % path
218 else:
219 else:
219 assert os.path.exists(path), "Missing package data: %s" % path
220 assert os.path.exists(path), "Missing package data: %s" % path
220
221
221
222
222 def check_package_data_first(command):
223 def check_package_data_first(command):
223 """decorator for checking package_data before running a given command
224 """decorator for checking package_data before running a given command
224
225
225 Probably only needs to wrap build_py
226 Probably only needs to wrap build_py
226 """
227 """
227 class DecoratedCommand(command):
228 class DecoratedCommand(command):
228 def run(self):
229 def run(self):
229 check_package_data(self.package_data)
230 check_package_data(self.package_data)
230 command.run(self)
231 command.run(self)
231 return DecoratedCommand
232 return DecoratedCommand
232
233
233
234
234 #---------------------------------------------------------------------------
235 #---------------------------------------------------------------------------
235 # Find data files
236 # Find data files
236 #---------------------------------------------------------------------------
237 #---------------------------------------------------------------------------
237
238
238 def make_dir_struct(tag,base,out_base):
239 def make_dir_struct(tag,base,out_base):
239 """Make the directory structure of all files below a starting dir.
240 """Make the directory structure of all files below a starting dir.
240
241
241 This is just a convenience routine to help build a nested directory
242 This is just a convenience routine to help build a nested directory
242 hierarchy because distutils is too stupid to do this by itself.
243 hierarchy because distutils is too stupid to do this by itself.
243
244
244 XXX - this needs a proper docstring!
245 XXX - this needs a proper docstring!
245 """
246 """
246
247
247 # we'll use these a lot below
248 # we'll use these a lot below
248 lbase = len(base)
249 lbase = len(base)
249 pathsep = os.path.sep
250 pathsep = os.path.sep
250 lpathsep = len(pathsep)
251 lpathsep = len(pathsep)
251
252
252 out = []
253 out = []
253 for (dirpath,dirnames,filenames) in os.walk(base):
254 for (dirpath,dirnames,filenames) in os.walk(base):
254 # we need to strip out the dirpath from the base to map it to the
255 # we need to strip out the dirpath from the base to map it to the
255 # output (installation) path. This requires possibly stripping the
256 # output (installation) path. This requires possibly stripping the
256 # path separator, because otherwise pjoin will not work correctly
257 # path separator, because otherwise pjoin will not work correctly
257 # (pjoin('foo/','/bar') returns '/bar').
258 # (pjoin('foo/','/bar') returns '/bar').
258
259
259 dp_eff = dirpath[lbase:]
260 dp_eff = dirpath[lbase:]
260 if dp_eff.startswith(pathsep):
261 if dp_eff.startswith(pathsep):
261 dp_eff = dp_eff[lpathsep:]
262 dp_eff = dp_eff[lpathsep:]
262 # The output path must be anchored at the out_base marker
263 # The output path must be anchored at the out_base marker
263 out_path = pjoin(out_base,dp_eff)
264 out_path = pjoin(out_base,dp_eff)
264 # Now we can generate the final filenames. Since os.walk only produces
265 # Now we can generate the final filenames. Since os.walk only produces
265 # filenames, we must join back with the dirpath to get full valid file
266 # filenames, we must join back with the dirpath to get full valid file
266 # paths:
267 # paths:
267 pfiles = [pjoin(dirpath,f) for f in filenames]
268 pfiles = [pjoin(dirpath,f) for f in filenames]
268 # Finally, generate the entry we need, which is a pari of (output
269 # Finally, generate the entry we need, which is a pari of (output
269 # path, files) for use as a data_files parameter in install_data.
270 # path, files) for use as a data_files parameter in install_data.
270 out.append((out_path, pfiles))
271 out.append((out_path, pfiles))
271
272
272 return out
273 return out
273
274
274
275
275 def find_data_files():
276 def find_data_files():
276 """
277 """
277 Find IPython's data_files.
278 Find IPython's data_files.
278
279
279 Just man pages at this point.
280 Just man pages at this point.
280 """
281 """
281
282
282 manpagebase = pjoin('share', 'man', 'man1')
283 manpagebase = pjoin('share', 'man', 'man1')
283
284
284 # Simple file lists can be made by hand
285 # Simple file lists can be made by hand
285 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
286 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
286 if not manpages:
287 if not manpages:
287 # When running from a source tree, the manpages aren't gzipped
288 # When running from a source tree, the manpages aren't gzipped
288 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
289 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
289
290
290 # And assemble the entire output list
291 # And assemble the entire output list
291 data_files = [ (manpagebase, manpages) ]
292 data_files = [ (manpagebase, manpages) ]
292
293
293 return data_files
294 return data_files
294
295
295
296
296 def make_man_update_target(manpage):
297 def make_man_update_target(manpage):
297 """Return a target_update-compliant tuple for the given manpage.
298 """Return a target_update-compliant tuple for the given manpage.
298
299
299 Parameters
300 Parameters
300 ----------
301 ----------
301 manpage : string
302 manpage : string
302 Name of the manpage, must include the section number (trailing number).
303 Name of the manpage, must include the section number (trailing number).
303
304
304 Example
305 Example
305 -------
306 -------
306
307
307 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
308 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
308 ('docs/man/ipython.1.gz',
309 ('docs/man/ipython.1.gz',
309 ['docs/man/ipython.1'],
310 ['docs/man/ipython.1'],
310 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
311 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
311 """
312 """
312 man_dir = pjoin('docs', 'man')
313 man_dir = pjoin('docs', 'man')
313 manpage_gz = manpage + '.gz'
314 manpage_gz = manpage + '.gz'
314 manpath = pjoin(man_dir, manpage)
315 manpath = pjoin(man_dir, manpage)
315 manpath_gz = pjoin(man_dir, manpage_gz)
316 manpath_gz = pjoin(man_dir, manpage_gz)
316 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
317 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
317 locals() )
318 locals() )
318 return (manpath_gz, [manpath], gz_cmd)
319 return (manpath_gz, [manpath], gz_cmd)
319
320
320 # The two functions below are copied from IPython.utils.path, so we don't need
321 # The two functions below are copied from IPython.utils.path, so we don't need
321 # to import IPython during setup, which fails on Python 3.
322 # to import IPython during setup, which fails on Python 3.
322
323
323 def target_outdated(target,deps):
324 def target_outdated(target,deps):
324 """Determine whether a target is out of date.
325 """Determine whether a target is out of date.
325
326
326 target_outdated(target,deps) -> 1/0
327 target_outdated(target,deps) -> 1/0
327
328
328 deps: list of filenames which MUST exist.
329 deps: list of filenames which MUST exist.
329 target: single filename which may or may not exist.
330 target: single filename which may or may not exist.
330
331
331 If target doesn't exist or is older than any file listed in deps, return
332 If target doesn't exist or is older than any file listed in deps, return
332 true, otherwise return false.
333 true, otherwise return false.
333 """
334 """
334 try:
335 try:
335 target_time = os.path.getmtime(target)
336 target_time = os.path.getmtime(target)
336 except os.error:
337 except os.error:
337 return 1
338 return 1
338 for dep in deps:
339 for dep in deps:
339 dep_time = os.path.getmtime(dep)
340 dep_time = os.path.getmtime(dep)
340 if dep_time > target_time:
341 if dep_time > target_time:
341 #print "For target",target,"Dep failed:",dep # dbg
342 #print "For target",target,"Dep failed:",dep # dbg
342 #print "times (dep,tar):",dep_time,target_time # dbg
343 #print "times (dep,tar):",dep_time,target_time # dbg
343 return 1
344 return 1
344 return 0
345 return 0
345
346
346
347
347 def target_update(target,deps,cmd):
348 def target_update(target,deps,cmd):
348 """Update a target with a given command given a list of dependencies.
349 """Update a target with a given command given a list of dependencies.
349
350
350 target_update(target,deps,cmd) -> runs cmd if target is outdated.
351 target_update(target,deps,cmd) -> runs cmd if target is outdated.
351
352
352 This is just a wrapper around target_outdated() which calls the given
353 This is just a wrapper around target_outdated() which calls the given
353 command if target is outdated."""
354 command if target is outdated."""
354
355
355 if target_outdated(target,deps):
356 if target_outdated(target,deps):
356 os.system(cmd)
357 os.system(cmd)
357
358
358 #---------------------------------------------------------------------------
359 #---------------------------------------------------------------------------
359 # Find scripts
360 # Find scripts
360 #---------------------------------------------------------------------------
361 #---------------------------------------------------------------------------
361
362
362 def find_entry_points():
363 def find_entry_points():
363 """Defines the command line entry points for IPython
364 """Defines the command line entry points for IPython
364
365
365 This always uses setuptools-style entry points. When setuptools is not in
366 This always uses setuptools-style entry points. When setuptools is not in
366 use, our own build_scripts_entrypt class below parses these and builds
367 use, our own build_scripts_entrypt class below parses these and builds
367 command line scripts.
368 command line scripts.
368
369
369 Each of our entry points gets both a plain name, e.g. ipython, and one
370 Each of our entry points gets both a plain name, e.g. ipython, and one
370 suffixed with the Python major version number, e.g. ipython3.
371 suffixed with the Python major version number, e.g. ipython3.
371 """
372 """
372 ep = [
373 ep = [
373 'ipython%s = IPython:start_ipython',
374 'ipython%s = IPython:start_ipython',
374 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
375 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
375 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
376 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
376 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
377 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
377 'iptest%s = IPython.testing.iptestcontroller:main',
378 'iptest%s = IPython.testing.iptestcontroller:main',
378 ]
379 ]
379 suffix = str(sys.version_info[0])
380 suffix = str(sys.version_info[0])
380 return [e % '' for e in ep] + [e % suffix for e in ep]
381 return [e % '' for e in ep] + [e % suffix for e in ep]
381
382
382 script_src = """#!{executable}
383 script_src = """#!{executable}
383 # This script was automatically generated by setup.py
384 # This script was automatically generated by setup.py
384 if __name__ == '__main__':
385 if __name__ == '__main__':
385 from {mod} import {func}
386 from {mod} import {func}
386 {func}()
387 {func}()
387 """
388 """
388
389
389 class build_scripts_entrypt(build_scripts):
390 class build_scripts_entrypt(build_scripts):
390 """Build the command line scripts
391 """Build the command line scripts
391
392
392 Parse setuptools style entry points and write simple scripts to run the
393 Parse setuptools style entry points and write simple scripts to run the
393 target functions.
394 target functions.
394
395
395 On Windows, this also creates .cmd wrappers for the scripts so that you can
396 On Windows, this also creates .cmd wrappers for the scripts so that you can
396 easily launch them from a command line.
397 easily launch them from a command line.
397 """
398 """
398 def run(self):
399 def run(self):
399 self.mkpath(self.build_dir)
400 self.mkpath(self.build_dir)
400 outfiles = []
401 outfiles = []
401 for script in find_entry_points():
402 for script in find_entry_points():
402 name, entrypt = script.split('=')
403 name, entrypt = script.split('=')
403 name = name.strip()
404 name = name.strip()
404 entrypt = entrypt.strip()
405 entrypt = entrypt.strip()
405 outfile = os.path.join(self.build_dir, name)
406 outfile = os.path.join(self.build_dir, name)
406 outfiles.append(outfile)
407 outfiles.append(outfile)
407 print('Writing script to', outfile)
408 print('Writing script to', outfile)
408
409
409 mod, func = entrypt.split(':')
410 mod, func = entrypt.split(':')
410 with open(outfile, 'w') as f:
411 with open(outfile, 'w') as f:
411 f.write(script_src.format(executable=sys.executable,
412 f.write(script_src.format(executable=sys.executable,
412 mod=mod, func=func))
413 mod=mod, func=func))
413
414
414 if sys.platform == 'win32':
415 if sys.platform == 'win32':
415 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
416 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
416 # command line
417 # command line
417 cmd_file = os.path.join(self.build_dir, name + '.cmd')
418 cmd_file = os.path.join(self.build_dir, name + '.cmd')
418 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
419 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
419 python=sys.executable, script=name)
420 python=sys.executable, script=name)
420 log.info("Writing %s wrapper script" % cmd_file)
421 log.info("Writing %s wrapper script" % cmd_file)
421 with open(cmd_file, 'w') as f:
422 with open(cmd_file, 'w') as f:
422 f.write(cmd)
423 f.write(cmd)
423
424
424 return outfiles, outfiles
425 return outfiles, outfiles
425
426
426 class install_lib_symlink(Command):
427 class install_lib_symlink(Command):
427 user_options = [
428 user_options = [
428 ('install-dir=', 'd', "directory to install to"),
429 ('install-dir=', 'd', "directory to install to"),
429 ]
430 ]
430
431
431 def initialize_options(self):
432 def initialize_options(self):
432 self.install_dir = None
433 self.install_dir = None
433
434
434 def finalize_options(self):
435 def finalize_options(self):
435 self.set_undefined_options('symlink',
436 self.set_undefined_options('symlink',
436 ('install_lib', 'install_dir'),
437 ('install_lib', 'install_dir'),
437 )
438 )
438
439
439 def run(self):
440 def run(self):
440 if sys.platform == 'win32':
441 if sys.platform == 'win32':
441 raise Exception("This doesn't work on Windows.")
442 raise Exception("This doesn't work on Windows.")
442 pkg = os.path.join(os.getcwd(), 'IPython')
443 pkg = os.path.join(os.getcwd(), 'IPython')
443 dest = os.path.join(self.install_dir, 'IPython')
444 dest = os.path.join(self.install_dir, 'IPython')
444 if os.path.islink(dest):
445 if os.path.islink(dest):
445 print('removing existing symlink at %s' % dest)
446 print('removing existing symlink at %s' % dest)
446 os.unlink(dest)
447 os.unlink(dest)
447 print('symlinking %s -> %s' % (pkg, dest))
448 print('symlinking %s -> %s' % (pkg, dest))
448 os.symlink(pkg, dest)
449 os.symlink(pkg, dest)
449
450
450 class unsymlink(install):
451 class unsymlink(install):
451 def run(self):
452 def run(self):
452 dest = os.path.join(self.install_lib, 'IPython')
453 dest = os.path.join(self.install_lib, 'IPython')
453 if os.path.islink(dest):
454 if os.path.islink(dest):
454 print('removing symlink at %s' % dest)
455 print('removing symlink at %s' % dest)
455 os.unlink(dest)
456 os.unlink(dest)
456 else:
457 else:
457 print('No symlink exists at %s' % dest)
458 print('No symlink exists at %s' % dest)
458
459
459 class install_symlinked(install):
460 class install_symlinked(install):
460 def run(self):
461 def run(self):
461 if sys.platform == 'win32':
462 if sys.platform == 'win32':
462 raise Exception("This doesn't work on Windows.")
463 raise Exception("This doesn't work on Windows.")
463
464
464 # Run all sub-commands (at least those that need to be run)
465 # Run all sub-commands (at least those that need to be run)
465 for cmd_name in self.get_sub_commands():
466 for cmd_name in self.get_sub_commands():
466 self.run_command(cmd_name)
467 self.run_command(cmd_name)
467
468
468 # 'sub_commands': a list of commands this command might have to run to
469 # 'sub_commands': a list of commands this command might have to run to
469 # get its work done. See cmd.py for more info.
470 # get its work done. See cmd.py for more info.
470 sub_commands = [('install_lib_symlink', lambda self:True),
471 sub_commands = [('install_lib_symlink', lambda self:True),
471 ('install_scripts_sym', lambda self:True),
472 ('install_scripts_sym', lambda self:True),
472 ]
473 ]
473
474
474 class install_scripts_for_symlink(install_scripts):
475 class install_scripts_for_symlink(install_scripts):
475 """Redefined to get options from 'symlink' instead of 'install'.
476 """Redefined to get options from 'symlink' instead of 'install'.
476
477
477 I love distutils almost as much as I love setuptools.
478 I love distutils almost as much as I love setuptools.
478 """
479 """
479 def finalize_options(self):
480 def finalize_options(self):
480 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
481 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
481 self.set_undefined_options('symlink',
482 self.set_undefined_options('symlink',
482 ('install_scripts', 'install_dir'),
483 ('install_scripts', 'install_dir'),
483 ('force', 'force'),
484 ('force', 'force'),
484 ('skip_build', 'skip_build'),
485 ('skip_build', 'skip_build'),
485 )
486 )
486
487
487 #---------------------------------------------------------------------------
488 #---------------------------------------------------------------------------
488 # Verify all dependencies
489 # Verify all dependencies
489 #---------------------------------------------------------------------------
490 #---------------------------------------------------------------------------
490
491
491 def check_for_dependencies():
492 def check_for_dependencies():
492 """Check for IPython's dependencies.
493 """Check for IPython's dependencies.
493
494
494 This function should NOT be called if running under setuptools!
495 This function should NOT be called if running under setuptools!
495 """
496 """
496 from setupext.setupext import (
497 from setupext.setupext import (
497 print_line, print_raw, print_status,
498 print_line, print_raw, print_status,
498 check_for_sphinx, check_for_pygments,
499 check_for_sphinx, check_for_pygments,
499 check_for_nose, check_for_pexpect,
500 check_for_nose, check_for_pexpect,
500 check_for_pyzmq, check_for_readline,
501 check_for_pyzmq, check_for_readline,
501 check_for_jinja2, check_for_tornado
502 check_for_jinja2, check_for_tornado
502 )
503 )
503 print_line()
504 print_line()
504 print_raw("BUILDING IPYTHON")
505 print_raw("BUILDING IPYTHON")
505 print_status('python', sys.version)
506 print_status('python', sys.version)
506 print_status('platform', sys.platform)
507 print_status('platform', sys.platform)
507 if sys.platform == 'win32':
508 if sys.platform == 'win32':
508 print_status('Windows version', sys.getwindowsversion())
509 print_status('Windows version', sys.getwindowsversion())
509
510
510 print_raw("")
511 print_raw("")
511 print_raw("OPTIONAL DEPENDENCIES")
512 print_raw("OPTIONAL DEPENDENCIES")
512
513
513 check_for_sphinx()
514 check_for_sphinx()
514 check_for_pygments()
515 check_for_pygments()
515 check_for_nose()
516 check_for_nose()
516 if os.name == 'posix':
517 if os.name == 'posix':
517 check_for_pexpect()
518 check_for_pexpect()
518 check_for_pyzmq()
519 check_for_pyzmq()
519 check_for_tornado()
520 check_for_tornado()
520 check_for_readline()
521 check_for_readline()
521 check_for_jinja2()
522 check_for_jinja2()
522
523
523 #---------------------------------------------------------------------------
524 #---------------------------------------------------------------------------
524 # VCS related
525 # VCS related
525 #---------------------------------------------------------------------------
526 #---------------------------------------------------------------------------
526
527
527 # utils.submodule has checks for submodule status
528 # utils.submodule has checks for submodule status
528 execfile(pjoin('IPython','utils','submodule.py'), globals())
529 execfile(pjoin('IPython','utils','submodule.py'), globals())
529
530
530 class UpdateSubmodules(Command):
531 class UpdateSubmodules(Command):
531 """Update git submodules
532 """Update git submodules
532
533
533 IPython's external javascript dependencies live in a separate repo.
534 IPython's external javascript dependencies live in a separate repo.
534 """
535 """
535 description = "Update git submodules"
536 description = "Update git submodules"
536 user_options = []
537 user_options = []
537
538
538 def initialize_options(self):
539 def initialize_options(self):
539 pass
540 pass
540
541
541 def finalize_options(self):
542 def finalize_options(self):
542 pass
543 pass
543
544
544 def run(self):
545 def run(self):
545 failure = False
546 failure = False
546 try:
547 try:
547 self.spawn('git submodule init'.split())
548 self.spawn('git submodule init'.split())
548 self.spawn('git submodule update --recursive'.split())
549 self.spawn('git submodule update --recursive'.split())
549 except Exception as e:
550 except Exception as e:
550 failure = e
551 failure = e
551 print(e)
552 print(e)
552
553
553 if not check_submodule_status(repo_root) == 'clean':
554 if not check_submodule_status(repo_root) == 'clean':
554 print("submodules could not be checked out")
555 print("submodules could not be checked out")
555 sys.exit(1)
556 sys.exit(1)
556
557
557
558
558 def git_prebuild(pkg_dir, build_cmd=build_py):
559 def git_prebuild(pkg_dir, build_cmd=build_py):
559 """Return extended build or sdist command class for recording commit
560 """Return extended build or sdist command class for recording commit
560
561
561 records git commit in IPython.utils._sysinfo.commit
562 records git commit in IPython.utils._sysinfo.commit
562
563
563 for use in IPython.utils.sysinfo.sys_info() calls after installation.
564 for use in IPython.utils.sysinfo.sys_info() calls after installation.
564
565
565 Also ensures that submodules exist prior to running
566 Also ensures that submodules exist prior to running
566 """
567 """
567
568
568 class MyBuildPy(build_cmd):
569 class MyBuildPy(build_cmd):
569 ''' Subclass to write commit data into installation tree '''
570 ''' Subclass to write commit data into installation tree '''
570 def run(self):
571 def run(self):
571 build_cmd.run(self)
572 build_cmd.run(self)
572 # this one will only fire for build commands
573 # this one will only fire for build commands
573 if hasattr(self, 'build_lib'):
574 if hasattr(self, 'build_lib'):
574 self._record_commit(self.build_lib)
575 self._record_commit(self.build_lib)
575
576
576 def make_release_tree(self, base_dir, files):
577 def make_release_tree(self, base_dir, files):
577 # this one will fire for sdist
578 # this one will fire for sdist
578 build_cmd.make_release_tree(self, base_dir, files)
579 build_cmd.make_release_tree(self, base_dir, files)
579 self._record_commit(base_dir)
580 self._record_commit(base_dir)
580
581
581 def _record_commit(self, base_dir):
582 def _record_commit(self, base_dir):
582 import subprocess
583 import subprocess
583 proc = subprocess.Popen('git rev-parse --short HEAD',
584 proc = subprocess.Popen('git rev-parse --short HEAD',
584 stdout=subprocess.PIPE,
585 stdout=subprocess.PIPE,
585 stderr=subprocess.PIPE,
586 stderr=subprocess.PIPE,
586 shell=True)
587 shell=True)
587 repo_commit, _ = proc.communicate()
588 repo_commit, _ = proc.communicate()
588 repo_commit = repo_commit.strip().decode("ascii")
589 repo_commit = repo_commit.strip().decode("ascii")
589
590
590 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
591 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
591 if os.path.isfile(out_pth) and not repo_commit:
592 if os.path.isfile(out_pth) and not repo_commit:
592 # nothing to write, don't clobber
593 # nothing to write, don't clobber
593 return
594 return
594
595
595 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
596 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
596
597
597 # remove to avoid overwriting original via hard link
598 # remove to avoid overwriting original via hard link
598 try:
599 try:
599 os.remove(out_pth)
600 os.remove(out_pth)
600 except (IOError, OSError):
601 except (IOError, OSError):
601 pass
602 pass
602 with open(out_pth, 'w') as out_file:
603 with open(out_pth, 'w') as out_file:
603 out_file.writelines([
604 out_file.writelines([
604 '# GENERATED BY setup.py\n',
605 '# GENERATED BY setup.py\n',
605 'commit = u"%s"\n' % repo_commit,
606 'commit = u"%s"\n' % repo_commit,
606 ])
607 ])
607 return require_submodules(MyBuildPy)
608 return require_submodules(MyBuildPy)
608
609
609
610
610 def require_submodules(command):
611 def require_submodules(command):
611 """decorator for instructing a command to check for submodules before running"""
612 """decorator for instructing a command to check for submodules before running"""
612 class DecoratedCommand(command):
613 class DecoratedCommand(command):
613 def run(self):
614 def run(self):
614 if not check_submodule_status(repo_root) == 'clean':
615 if not check_submodule_status(repo_root) == 'clean':
615 print("submodules missing! Run `setup.py submodule` and try again")
616 print("submodules missing! Run `setup.py submodule` and try again")
616 sys.exit(1)
617 sys.exit(1)
617 command.run(self)
618 command.run(self)
618 return DecoratedCommand
619 return DecoratedCommand
619
620
620 #---------------------------------------------------------------------------
621 #---------------------------------------------------------------------------
621 # bdist related
622 # bdist related
622 #---------------------------------------------------------------------------
623 #---------------------------------------------------------------------------
623
624
624 def get_bdist_wheel():
625 def get_bdist_wheel():
625 """Construct bdist_wheel command for building wheels
626 """Construct bdist_wheel command for building wheels
626
627
627 Constructs py2-none-any tag, instead of py2.7-none-any
628 Constructs py2-none-any tag, instead of py2.7-none-any
628 """
629 """
629 class RequiresWheel(Command):
630 class RequiresWheel(Command):
630 description = "Dummy command for missing bdist_wheel"
631 description = "Dummy command for missing bdist_wheel"
631 user_options = []
632 user_options = []
632
633
633 def initialize_options(self):
634 def initialize_options(self):
634 pass
635 pass
635
636
636 def finalize_options(self):
637 def finalize_options(self):
637 pass
638 pass
638
639
639 def run(self):
640 def run(self):
640 print("bdist_wheel requires the wheel package")
641 print("bdist_wheel requires the wheel package")
641 sys.exit(1)
642 sys.exit(1)
642
643
643 if 'setuptools' not in sys.modules:
644 if 'setuptools' not in sys.modules:
644 return RequiresWheel
645 return RequiresWheel
645 else:
646 else:
646 try:
647 try:
647 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
648 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
648 except ImportError:
649 except ImportError:
649 return RequiresWheel
650 return RequiresWheel
650
651
651 class bdist_wheel_tag(bdist_wheel):
652 class bdist_wheel_tag(bdist_wheel):
652
653
653 def add_requirements(self, metadata_path):
654 def add_requirements(self, metadata_path):
654 """transform platform-dependent requirements"""
655 """transform platform-dependent requirements"""
655 pkg_info = read_pkg_info(metadata_path)
656 pkg_info = read_pkg_info(metadata_path)
656 # pkg_info is an email.Message object (?!)
657 # pkg_info is an email.Message object (?!)
657 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
658 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
658 # and transform them to conditionals
659 # and transform them to conditionals
659 requires = pkg_info.get_all('Requires-Dist')
660 requires = pkg_info.get_all('Requires-Dist')
660 del pkg_info['Requires-Dist']
661 del pkg_info['Requires-Dist']
661 def _remove_startswith(lis, prefix):
662 def _remove_startswith(lis, prefix):
662 """like list.remove, but with startswith instead of =="""
663 """like list.remove, but with startswith instead of =="""
663 found = False
664 found = False
664 for idx, item in enumerate(lis):
665 for idx, item in enumerate(lis):
665 if item.startswith(prefix):
666 if item.startswith(prefix):
666 found = True
667 found = True
667 break
668 break
668 if found:
669 if found:
669 lis.pop(idx)
670 lis.pop(idx)
670
671
671 for pkg in ("gnureadline", "pyreadline", "mock"):
672 for pkg in ("gnureadline", "pyreadline", "mock"):
672 _remove_startswith(requires, pkg)
673 _remove_startswith(requires, pkg)
673 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
674 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
674 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
675 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
675 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
676 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
676 requires.append("mock; extra == 'test' and python_version < '3.3'")
677 requires.append("mock; extra == 'test' and python_version < '3.3'")
677 for r in requires:
678 for r in requires:
678 pkg_info['Requires-Dist'] = r
679 pkg_info['Requires-Dist'] = r
679 write_pkg_info(metadata_path, pkg_info)
680 write_pkg_info(metadata_path, pkg_info)
680
681
681 return bdist_wheel_tag
682 return bdist_wheel_tag
682
683
683 #---------------------------------------------------------------------------
684 #---------------------------------------------------------------------------
684 # Notebook related
685 # Notebook related
685 #---------------------------------------------------------------------------
686 #---------------------------------------------------------------------------
686
687
687 class CompileCSS(Command):
688 class CompileCSS(Command):
688 """Recompile Notebook CSS
689 """Recompile Notebook CSS
689
690
690 Regenerate the compiled CSS from LESS sources.
691 Regenerate the compiled CSS from LESS sources.
691
692
692 Requires various dev dependencies, such as invoke and lessc.
693 Requires various dev dependencies, such as invoke and lessc.
693 """
694 """
694 description = "Recompile Notebook CSS"
695 description = "Recompile Notebook CSS"
695 user_options = [
696 user_options = [
696 ('minify', 'x', "minify CSS"),
697 ('minify', 'x', "minify CSS"),
697 ('force', 'f', "force recompilation of CSS"),
698 ('force', 'f', "force recompilation of CSS"),
698 ]
699 ]
699
700
700 def initialize_options(self):
701 def initialize_options(self):
701 self.minify = False
702 self.minify = False
702 self.force = False
703 self.force = False
703
704
704 def finalize_options(self):
705 def finalize_options(self):
705 self.minify = bool(self.minify)
706 self.minify = bool(self.minify)
706 self.force = bool(self.force)
707 self.force = bool(self.force)
707
708
708 def run(self):
709 def run(self):
709 cmd = ['invoke', 'css']
710 cmd = ['invoke', 'css']
710 if self.minify:
711 if self.minify:
711 cmd.append('--minify')
712 cmd.append('--minify')
712 if self.force:
713 if self.force:
713 cmd.append('--force')
714 cmd.append('--force')
714 check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
715 check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
715
716
716
717
717 class JavascriptVersion(Command):
718 class JavascriptVersion(Command):
718 """write the javascript version to notebook javascript"""
719 """write the javascript version to notebook javascript"""
719 description = "Write IPython version to javascript"
720 description = "Write IPython version to javascript"
720 user_options = []
721 user_options = []
721
722
722 def initialize_options(self):
723 def initialize_options(self):
723 pass
724 pass
724
725
725 def finalize_options(self):
726 def finalize_options(self):
726 pass
727 pass
727
728
728 def run(self):
729 def run(self):
729 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
730 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
730 with open(nsfile) as f:
731 with open(nsfile) as f:
731 lines = f.readlines()
732 lines = f.readlines()
732 with open(nsfile, 'w') as f:
733 with open(nsfile, 'w') as f:
733 for line in lines:
734 for line in lines:
734 if line.startswith("IPython.version"):
735 if line.startswith("IPython.version"):
735 line = 'IPython.version = "{0}";\n'.format(version)
736 line = 'IPython.version = "{0}";\n'.format(version)
736 f.write(line)
737 f.write(line)
737
738
738
739
739 def css_js_prerelease(command, strict=True):
740 def css_js_prerelease(command, strict=True):
740 """decorator for building js/minified css prior to a release"""
741 """decorator for building js/minified css prior to a release"""
741 class DecoratedCommand(command):
742 class DecoratedCommand(command):
742 def run(self):
743 def run(self):
743 self.distribution.run_command('jsversion')
744 self.distribution.run_command('jsversion')
744 css = self.distribution.get_command_obj('css')
745 css = self.distribution.get_command_obj('css')
745 css.minify = True
746 css.minify = True
746 try:
747 try:
747 self.distribution.run_command('css')
748 self.distribution.run_command('css')
748 except Exception as e:
749 except Exception as e:
749 if strict:
750 if strict:
750 raise
751 raise
751 else:
752 else:
752 log.warn("Failed to build css sourcemaps: %s" % e)
753 log.warn("Failed to build css sourcemaps: %s" % e)
753 command.run(self)
754 command.run(self)
754 return DecoratedCommand
755 return DecoratedCommand
General Comments 0
You need to be logged in to leave comments. Login now