##// END OF EJS Templates
some $.html( -> $.text(...
Matthias BUSSONNIER -
Show More
@@ -1,90 +1,90 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
2 // Copyright (C) 2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // CellToolbar Example
9 // CellToolbar Example
10 //============================================================================
10 //============================================================================
11
11
12 (function(IPython) {
12 (function(IPython) {
13 "use strict";
13 "use strict";
14
14
15 var CellToolbar = IPython.CellToolbar;
15 var CellToolbar = IPython.CellToolbar;
16 var raw_cell_preset = [];
16 var raw_cell_preset = [];
17 var utils = IPython.utils;
17 var utils = IPython.utils;
18
18
19 var select_type = CellToolbar.utils.select_ui_generator([
19 var select_type = CellToolbar.utils.select_ui_generator([
20 ["None", "-"],
20 ["None", "-"],
21 ["LaTeX", "text/latex"],
21 ["LaTeX", "text/latex"],
22 ["reST", "text/restructuredtext"],
22 ["reST", "text/restructuredtext"],
23 ["HTML", "text/html"],
23 ["HTML", "text/html"],
24 ["Markdown", "text/markdown"],
24 ["Markdown", "text/markdown"],
25 ["Python", "text/x-python"],
25 ["Python", "text/x-python"],
26 ["Custom", "dialog"],
26 ["Custom", "dialog"],
27
27
28 ],
28 ],
29 // setter
29 // setter
30 function(cell, value) {
30 function(cell, value) {
31 if (value === "-") {
31 if (value === "-") {
32 delete cell.metadata.raw_mimetype;
32 delete cell.metadata.raw_mimetype;
33 } else if (value === 'dialog'){
33 } else if (value === 'dialog'){
34 var dialog = $('<div/>').append(
34 var dialog = $('<div/>').append(
35 $("<p/>")
35 $("<p/>")
36 .html("Set the MIME type of the raw cell:")
36 .text("Set the MIME type of the raw cell:")
37 ).append(
37 ).append(
38 $("<br/>")
38 $("<br/>")
39 ).append(
39 ).append(
40 $('<input/>').attr('type','text').attr('size','25')
40 $('<input/>').attr('type','text').attr('size','25')
41 .val(cell.metadata.raw_mimetype || "-")
41 .val(cell.metadata.raw_mimetype || "-")
42 );
42 );
43 IPython.dialog.modal({
43 IPython.dialog.modal({
44 title: "Raw Cell MIME Type",
44 title: "Raw Cell MIME Type",
45 body: dialog,
45 body: dialog,
46 buttons : {
46 buttons : {
47 "Cancel": {},
47 "Cancel": {},
48 "OK": {
48 "OK": {
49 class: "btn-primary",
49 class: "btn-primary",
50 click: function () {
50 click: function () {
51 console.log(cell);
51 console.log(cell);
52 cell.metadata.raw_mimetype = $(this).find('input').val();
52 cell.metadata.raw_mimetype = $(this).find('input').val();
53 console.log(cell.metadata);
53 console.log(cell.metadata);
54 }
54 }
55 }
55 }
56 },
56 },
57 open : function (event, ui) {
57 open : function (event, ui) {
58 var that = $(this);
58 var that = $(this);
59 // Upon ENTER, click the OK button.
59 // Upon ENTER, click the OK button.
60 that.find('input[type="text"]').keydown(function (event, ui) {
60 that.find('input[type="text"]').keydown(function (event, ui) {
61 if (event.which === utils.keycodes.ENTER) {
61 if (event.which === utils.keycodes.ENTER) {
62 that.find('.btn-primary').first().click();
62 that.find('.btn-primary').first().click();
63 return false;
63 return false;
64 }
64 }
65 });
65 });
66 that.find('input[type="text"]').focus().select();
66 that.find('input[type="text"]').focus().select();
67 }
67 }
68 });
68 });
69 } else {
69 } else {
70 cell.metadata.raw_mimetype = value;
70 cell.metadata.raw_mimetype = value;
71 }
71 }
72 },
72 },
73 //getter
73 //getter
74 function(cell) {
74 function(cell) {
75 return cell.metadata.raw_mimetype || "";
75 return cell.metadata.raw_mimetype || "";
76 },
76 },
77 // name
77 // name
78 "Raw NBConvert Format",
78 "Raw NBConvert Format",
79 // cell_types
79 // cell_types
80 ["raw"]
80 ["raw"]
81 );
81 );
82
82
83 CellToolbar.register_callback('raw_cell.select', select_type);
83 CellToolbar.register_callback('raw_cell.select', select_type);
84
84
85 raw_cell_preset.push('raw_cell.select');
85 raw_cell_preset.push('raw_cell.select');
86
86
87 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
87 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
88 console.log('Raw Cell Format toolbar preset loaded.');
88 console.log('Raw Cell Format toolbar preset loaded.');
89
89
90 }(IPython));
90 }(IPython));
@@ -1,259 +1,259 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // MathJax utility functions
9 // MathJax utility functions
10 //============================================================================
10 //============================================================================
11
11
12
12
13 IPython.namespace('IPython.mathjaxutils');
13 IPython.namespace('IPython.mathjaxutils');
14
14
15 IPython.mathjaxutils = (function (IPython) {
15 IPython.mathjaxutils = (function (IPython) {
16 "use strict";
16 "use strict";
17
17
18 var init = function () {
18 var init = function () {
19 if (window.MathJax) {
19 if (window.MathJax) {
20 // MathJax loaded
20 // MathJax loaded
21 MathJax.Hub.Config({
21 MathJax.Hub.Config({
22 tex2jax: {
22 tex2jax: {
23 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
23 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
24 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
24 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
25 processEscapes: true,
25 processEscapes: true,
26 processEnvironments: true
26 processEnvironments: true
27 },
27 },
28 // Center justify equations in code and markdown cells. Elsewhere
28 // Center justify equations in code and markdown cells. Elsewhere
29 // we use CSS to left justify single line equations in code cells.
29 // we use CSS to left justify single line equations in code cells.
30 displayAlign: 'center',
30 displayAlign: 'center',
31 "HTML-CSS": {
31 "HTML-CSS": {
32 styles: {'.MathJax_Display': {"margin": 0}},
32 styles: {'.MathJax_Display': {"margin": 0}},
33 linebreaks: { automatic: true }
33 linebreaks: { automatic: true }
34 }
34 }
35 });
35 });
36 MathJax.Hub.Configured();
36 MathJax.Hub.Configured();
37 } else if (window.mathjax_url !== "") {
37 } else if (window.mathjax_url !== "") {
38 // Don't have MathJax, but should. Show dialog.
38 // Don't have MathJax, but should. Show dialog.
39 var message = $('<div/>')
39 var message = $('<div/>')
40 .append(
40 .append(
41 $("<p/></p>").addClass('dialog').html(
41 $("<p/></p>").addClass('dialog').text(
42 "Math/LaTeX rendering will be disabled."
42 "Math/LaTeX rendering will be disabled."
43 )
43 )
44 ).append(
44 ).append(
45 $("<p></p>").addClass('dialog').html(
45 $("<p></p>").addClass('dialog').text(
46 "If you have administrative access to the notebook server and" +
46 "If you have administrative access to the notebook server and" +
47 " a working internet connection, you can install a local copy" +
47 " a working internet connection, you can install a local copy" +
48 " of MathJax for offline use with the following command on the server" +
48 " of MathJax for offline use with the following command on the server" +
49 " at a Python or IPython prompt:"
49 " at a Python or IPython prompt:"
50 )
50 )
51 ).append(
51 ).append(
52 $("<pre></pre>").addClass('dialog').html(
52 $("<pre></pre>").addClass('dialog').text(
53 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
53 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
54 )
54 )
55 ).append(
55 ).append(
56 $("<p></p>").addClass('dialog').html(
56 $("<p></p>").addClass('dialog').text(
57 "This will try to install MathJax into the IPython source directory."
57 "This will try to install MathJax into the IPython source directory."
58 )
58 )
59 ).append(
59 ).append(
60 $("<p></p>").addClass('dialog').html(
60 $("<p></p>").addClass('dialog').text(
61 "If IPython is installed to a location that requires" +
61 "If IPython is installed to a location that requires" +
62 " administrative privileges to write, you will need to make this call as" +
62 " administrative privileges to write, you will need to make this call as" +
63 " an administrator, via 'sudo'."
63 " an administrator, via 'sudo'."
64 )
64 )
65 ).append(
65 ).append(
66 $("<p></p>").addClass('dialog').html(
66 $("<p></p>").addClass('dialog').text(
67 "When you start the notebook server, you can instruct it to disable MathJax support altogether:"
67 "When you start the notebook server, you can instruct it to disable MathJax support altogether:"
68 )
68 )
69 ).append(
69 ).append(
70 $("<pre></pre>").addClass('dialog').html(
70 $("<pre></pre>").addClass('dialog').text(
71 "$ ipython notebook --no-mathjax"
71 "$ ipython notebook --no-mathjax"
72 )
72 )
73 ).append(
73 ).append(
74 $("<p></p>").addClass('dialog').html(
74 $("<p></p>").addClass('dialog').text(
75 "which will prevent this dialog from appearing."
75 "which will prevent this dialog from appearing."
76 )
76 )
77 );
77 );
78 IPython.dialog.modal({
78 IPython.dialog.modal({
79 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
79 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
80 body : message,
80 body : message,
81 buttons : {
81 buttons : {
82 OK : {class: "btn-danger"}
82 OK : {class: "btn-danger"}
83 }
83 }
84 });
84 });
85 }
85 }
86 };
86 };
87
87
88 // Some magic for deferring mathematical expressions to MathJax
88 // Some magic for deferring mathematical expressions to MathJax
89 // by hiding them from the Markdown parser.
89 // by hiding them from the Markdown parser.
90 // Some of the code here is adapted with permission from Davide Cervone
90 // Some of the code here is adapted with permission from Davide Cervone
91 // under the terms of the Apache2 license governing the MathJax project.
91 // under the terms of the Apache2 license governing the MathJax project.
92 // Other minor modifications are also due to StackExchange and are used with
92 // Other minor modifications are also due to StackExchange and are used with
93 // permission.
93 // permission.
94
94
95 var inline = "$"; // the inline math delimiter
95 var inline = "$"; // the inline math delimiter
96
96
97 // MATHSPLIT contains the pattern for math delimiters and special symbols
97 // MATHSPLIT contains the pattern for math delimiters and special symbols
98 // needed for searching for math in the text input.
98 // needed for searching for math in the text input.
99 var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
99 var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
100
100
101 // The math is in blocks i through j, so
101 // The math is in blocks i through j, so
102 // collect it into one block and clear the others.
102 // collect it into one block and clear the others.
103 // Replace &, <, and > by named entities.
103 // Replace &, <, and > by named entities.
104 // For IE, put <br> at the ends of comments since IE removes \n.
104 // For IE, put <br> at the ends of comments since IE removes \n.
105 // Clear the current math positions and store the index of the
105 // Clear the current math positions and store the index of the
106 // math, then push the math string onto the storage array.
106 // math, then push the math string onto the storage array.
107 // The preProcess function is called on all blocks if it has been passed in
107 // The preProcess function is called on all blocks if it has been passed in
108 var process_math = function (i, j, pre_process, math, blocks) {
108 var process_math = function (i, j, pre_process, math, blocks) {
109 var hub = MathJax.Hub;
109 var hub = MathJax.Hub;
110 var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&amp;") // use HTML entity for &
110 var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&amp;") // use HTML entity for &
111 .replace(/</g, "&lt;") // use HTML entity for <
111 .replace(/</g, "&lt;") // use HTML entity for <
112 .replace(/>/g, "&gt;") // use HTML entity for >
112 .replace(/>/g, "&gt;") // use HTML entity for >
113 ;
113 ;
114 if (hub.Browser.isMSIE) {
114 if (hub.Browser.isMSIE) {
115 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
115 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
116 }
116 }
117 while (j > i) {
117 while (j > i) {
118 blocks[j] = "";
118 blocks[j] = "";
119 j--;
119 j--;
120 }
120 }
121 blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
121 blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
122 if (pre_process){
122 if (pre_process){
123 block = pre_process(block);
123 block = pre_process(block);
124 }
124 }
125 math.push(block);
125 math.push(block);
126 return blocks;
126 return blocks;
127 };
127 };
128
128
129 // Break up the text into its component parts and search
129 // Break up the text into its component parts and search
130 // through them for math delimiters, braces, linebreaks, etc.
130 // through them for math delimiters, braces, linebreaks, etc.
131 // Math delimiters must match and braces must balance.
131 // Math delimiters must match and braces must balance.
132 // Don't allow math to pass through a double linebreak
132 // Don't allow math to pass through a double linebreak
133 // (which will be a paragraph).
133 // (which will be a paragraph).
134 //
134 //
135 var remove_math = function (text) {
135 var remove_math = function (text) {
136 if (!window.MathJax) {
136 if (!window.MathJax) {
137 return [text, null];
137 return [text, null];
138 }
138 }
139
139
140 var math = []; // stores math strings for later
140 var math = []; // stores math strings for later
141 var start;
141 var start;
142 var end;
142 var end;
143 var last;
143 var last;
144 var braces;
144 var braces;
145
145
146 // Except for extreme edge cases, this should catch precisely those pieces of the markdown
146 // Except for extreme edge cases, this should catch precisely those pieces of the markdown
147 // source that will later be turned into code spans. While MathJax will not TeXify code spans,
147 // source that will later be turned into code spans. While MathJax will not TeXify code spans,
148 // we still have to consider them at this point; the following issue has happened several times:
148 // we still have to consider them at this point; the following issue has happened several times:
149 //
149 //
150 // `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
150 // `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
151
151
152 var hasCodeSpans = /`/.test(text),
152 var hasCodeSpans = /`/.test(text),
153 de_tilde;
153 de_tilde;
154 if (hasCodeSpans) {
154 if (hasCodeSpans) {
155 text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {
155 text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {
156 return wholematch.replace(/\$/g, "~D");
156 return wholematch.replace(/\$/g, "~D");
157 });
157 });
158 de_tilde = function (text) {
158 de_tilde = function (text) {
159 return text.replace(/~([TD])/g, function (wholematch, character) {
159 return text.replace(/~([TD])/g, function (wholematch, character) {
160 return { T: "~", D: "$" }[character];
160 return { T: "~", D: "$" }[character];
161 });
161 });
162 };
162 };
163 } else {
163 } else {
164 de_tilde = function (text) { return text; };
164 de_tilde = function (text) { return text; };
165 }
165 }
166
166
167 var blocks = IPython.utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
167 var blocks = IPython.utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
168
168
169 for (var i = 1, m = blocks.length; i < m; i += 2) {
169 for (var i = 1, m = blocks.length; i < m; i += 2) {
170 var block = blocks[i];
170 var block = blocks[i];
171 if (block.charAt(0) === "@") {
171 if (block.charAt(0) === "@") {
172 //
172 //
173 // Things that look like our math markers will get
173 // Things that look like our math markers will get
174 // stored and then retrieved along with the math.
174 // stored and then retrieved along with the math.
175 //
175 //
176 blocks[i] = "@@" + math.length + "@@";
176 blocks[i] = "@@" + math.length + "@@";
177 math.push(block);
177 math.push(block);
178 }
178 }
179 else if (start) {
179 else if (start) {
180 //
180 //
181 // If we are in math, look for the end delimiter,
181 // If we are in math, look for the end delimiter,
182 // but don't go past double line breaks, and
182 // but don't go past double line breaks, and
183 // and balance braces within the math.
183 // and balance braces within the math.
184 //
184 //
185 if (block === end) {
185 if (block === end) {
186 if (braces) {
186 if (braces) {
187 last = i;
187 last = i;
188 }
188 }
189 else {
189 else {
190 blocks = process_math(start, i, de_tilde, math, blocks);
190 blocks = process_math(start, i, de_tilde, math, blocks);
191 start = null;
191 start = null;
192 end = null;
192 end = null;
193 last = null;
193 last = null;
194 }
194 }
195 }
195 }
196 else if (block.match(/\n.*\n/)) {
196 else if (block.match(/\n.*\n/)) {
197 if (last) {
197 if (last) {
198 i = last;
198 i = last;
199 blocks = process_math(start, i, de_tilde, math, blocks);
199 blocks = process_math(start, i, de_tilde, math, blocks);
200 }
200 }
201 start = null;
201 start = null;
202 end = null;
202 end = null;
203 last = null;
203 last = null;
204 braces = 0;
204 braces = 0;
205 }
205 }
206 else if (block === "{") {
206 else if (block === "{") {
207 braces++;
207 braces++;
208 }
208 }
209 else if (block === "}" && braces) {
209 else if (block === "}" && braces) {
210 braces--;
210 braces--;
211 }
211 }
212 }
212 }
213 else {
213 else {
214 //
214 //
215 // Look for math start delimiters and when
215 // Look for math start delimiters and when
216 // found, set up the end delimiter.
216 // found, set up the end delimiter.
217 //
217 //
218 if (block === inline || block === "$$") {
218 if (block === inline || block === "$$") {
219 start = i;
219 start = i;
220 end = block;
220 end = block;
221 braces = 0;
221 braces = 0;
222 }
222 }
223 else if (block.substr(1, 5) === "begin") {
223 else if (block.substr(1, 5) === "begin") {
224 start = i;
224 start = i;
225 end = "\\end" + block.substr(6);
225 end = "\\end" + block.substr(6);
226 braces = 0;
226 braces = 0;
227 }
227 }
228 }
228 }
229 }
229 }
230 if (last) {
230 if (last) {
231 blocks = process_math(start, last, de_tilde, math, blocks);
231 blocks = process_math(start, last, de_tilde, math, blocks);
232 start = null;
232 start = null;
233 end = null;
233 end = null;
234 last = null;
234 last = null;
235 }
235 }
236 return [de_tilde(blocks.join("")), math];
236 return [de_tilde(blocks.join("")), math];
237 };
237 };
238
238
239 //
239 //
240 // Put back the math strings that were saved,
240 // Put back the math strings that were saved,
241 // and clear the math array (no need to keep it around).
241 // and clear the math array (no need to keep it around).
242 //
242 //
243 var replace_math = function (text, math) {
243 var replace_math = function (text, math) {
244 if (!window.MathJax) {
244 if (!window.MathJax) {
245 return text;
245 return text;
246 }
246 }
247 text = text.replace(/@@(\d+)@@/g, function (match, n) {
247 text = text.replace(/@@(\d+)@@/g, function (match, n) {
248 return math[n];
248 return math[n];
249 });
249 });
250 return text;
250 return text;
251 };
251 };
252
252
253 return {
253 return {
254 init : init,
254 init : init,
255 remove_math : remove_math,
255 remove_math : remove_math,
256 replace_math : replace_math
256 replace_math : replace_math
257 };
257 };
258
258
259 }(IPython));
259 }(IPython));
@@ -1,2213 +1,2213 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 var options = options || {};
26 var options = options || {};
27 this._baseProjectUrl = options.baseProjectUrl;
27 this._baseProjectUrl = options.baseProjectUrl;
28 this.notebook_path = options.notebookPath;
28 this.notebook_path = options.notebookPath;
29 this.notebook_name = options.notebookName;
29 this.notebook_name = options.notebookName;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3 // Increment this when changing the nbformat
56 this.nbformat = 3 // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 };
61 };
62
62
63 /**
63 /**
64 * Tweak the notebook's CSS style.
64 * Tweak the notebook's CSS style.
65 *
65 *
66 * @method style
66 * @method style
67 */
67 */
68 Notebook.prototype.style = function () {
68 Notebook.prototype.style = function () {
69 $('div#notebook').addClass('border-box-sizing');
69 $('div#notebook').addClass('border-box-sizing');
70 };
70 };
71
71
72 /**
72 /**
73 * Get the root URL of the notebook server.
73 * Get the root URL of the notebook server.
74 *
74 *
75 * @method baseProjectUrl
75 * @method baseProjectUrl
76 * @return {String} The base project URL
76 * @return {String} The base project URL
77 */
77 */
78 Notebook.prototype.baseProjectUrl = function() {
78 Notebook.prototype.baseProjectUrl = function() {
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
80 };
80 };
81
81
82 Notebook.prototype.notebookName = function() {
82 Notebook.prototype.notebookName = function() {
83 return $('body').data('notebookName');
83 return $('body').data('notebookName');
84 };
84 };
85
85
86 Notebook.prototype.notebookPath = function() {
86 Notebook.prototype.notebookPath = function() {
87 return $('body').data('notebookPath');
87 return $('body').data('notebookPath');
88 };
88 };
89
89
90 /**
90 /**
91 * Create an HTML and CSS representation of the notebook.
91 * Create an HTML and CSS representation of the notebook.
92 *
92 *
93 * @method create_elements
93 * @method create_elements
94 */
94 */
95 Notebook.prototype.create_elements = function () {
95 Notebook.prototype.create_elements = function () {
96 var that = this;
96 var that = this;
97 this.element.attr('tabindex','-1');
97 this.element.attr('tabindex','-1');
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
99 // We add this end_space div to the end of the notebook div to:
99 // We add this end_space div to the end of the notebook div to:
100 // i) provide a margin between the last cell and the end of the notebook
100 // i) provide a margin between the last cell and the end of the notebook
101 // ii) to prevent the div from scrolling up when the last cell is being
101 // ii) to prevent the div from scrolling up when the last cell is being
102 // edited, but is too low on the page, which browsers will do automatically.
102 // edited, but is too low on the page, which browsers will do automatically.
103 var end_space = $('<div/>').addClass('end_space');
103 var end_space = $('<div/>').addClass('end_space');
104 end_space.dblclick(function (e) {
104 end_space.dblclick(function (e) {
105 var ncells = that.ncells();
105 var ncells = that.ncells();
106 that.insert_cell_below('code',ncells-1);
106 that.insert_cell_below('code',ncells-1);
107 });
107 });
108 this.element.append(this.container);
108 this.element.append(this.container);
109 this.container.append(end_space);
109 this.container.append(end_space);
110 };
110 };
111
111
112 /**
112 /**
113 * Bind JavaScript events: key presses and custom IPython events.
113 * Bind JavaScript events: key presses and custom IPython events.
114 *
114 *
115 * @method bind_events
115 * @method bind_events
116 */
116 */
117 Notebook.prototype.bind_events = function () {
117 Notebook.prototype.bind_events = function () {
118 var that = this;
118 var that = this;
119
119
120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
121 var index = that.find_cell_index(data.cell);
121 var index = that.find_cell_index(data.cell);
122 var new_cell = that.insert_cell_below('code',index);
122 var new_cell = that.insert_cell_below('code',index);
123 new_cell.set_text(data.text);
123 new_cell.set_text(data.text);
124 that.dirty = true;
124 that.dirty = true;
125 });
125 });
126
126
127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
128 that.dirty = data.value;
128 that.dirty = data.value;
129 });
129 });
130
130
131 $([IPython.events]).on('select.Cell', function (event, data) {
131 $([IPython.events]).on('select.Cell', function (event, data) {
132 var index = that.find_cell_index(data.cell);
132 var index = that.find_cell_index(data.cell);
133 that.select(index);
133 that.select(index);
134 });
134 });
135
135
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
137 var index = that.find_cell_index(data.cell);
137 var index = that.find_cell_index(data.cell);
138 that.select(index);
138 that.select(index);
139 that.edit_mode();
139 that.edit_mode();
140 });
140 });
141
141
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
143 that.command_mode();
143 that.command_mode();
144 });
144 });
145
145
146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
147 IPython.dialog.modal({
147 IPython.dialog.modal({
148 title: "Kernel Restarting",
148 title: "Kernel Restarting",
149 body: "The kernel appears to have died. It will restart automatically.",
149 body: "The kernel appears to have died. It will restart automatically.",
150 buttons: {
150 buttons: {
151 OK : {
151 OK : {
152 class : "btn-primary"
152 class : "btn-primary"
153 }
153 }
154 }
154 }
155 });
155 });
156 });
156 });
157
157
158 var collapse_time = function (time) {
158 var collapse_time = function (time) {
159 var app_height = $('#ipython-main-app').height(); // content height
159 var app_height = $('#ipython-main-app').height(); // content height
160 var splitter_height = $('div#pager_splitter').outerHeight(true);
160 var splitter_height = $('div#pager_splitter').outerHeight(true);
161 var new_height = app_height - splitter_height;
161 var new_height = app_height - splitter_height;
162 that.element.animate({height : new_height + 'px'}, time);
162 that.element.animate({height : new_height + 'px'}, time);
163 };
163 };
164
164
165 this.element.bind('collapse_pager', function (event, extrap) {
165 this.element.bind('collapse_pager', function (event, extrap) {
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
167 collapse_time(time);
167 collapse_time(time);
168 });
168 });
169
169
170 var expand_time = function (time) {
170 var expand_time = function (time) {
171 var app_height = $('#ipython-main-app').height(); // content height
171 var app_height = $('#ipython-main-app').height(); // content height
172 var splitter_height = $('div#pager_splitter').outerHeight(true);
172 var splitter_height = $('div#pager_splitter').outerHeight(true);
173 var pager_height = $('div#pager').outerHeight(true);
173 var pager_height = $('div#pager').outerHeight(true);
174 var new_height = app_height - pager_height - splitter_height;
174 var new_height = app_height - pager_height - splitter_height;
175 that.element.animate({height : new_height + 'px'}, time);
175 that.element.animate({height : new_height + 'px'}, time);
176 };
176 };
177
177
178 this.element.bind('expand_pager', function (event, extrap) {
178 this.element.bind('expand_pager', function (event, extrap) {
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
180 expand_time(time);
180 expand_time(time);
181 });
181 });
182
182
183 // Firefox 22 broke $(window).on("beforeunload")
183 // Firefox 22 broke $(window).on("beforeunload")
184 // I'm not sure why or how.
184 // I'm not sure why or how.
185 window.onbeforeunload = function (e) {
185 window.onbeforeunload = function (e) {
186 // TODO: Make killing the kernel configurable.
186 // TODO: Make killing the kernel configurable.
187 var kill_kernel = false;
187 var kill_kernel = false;
188 if (kill_kernel) {
188 if (kill_kernel) {
189 that.session.kill_kernel();
189 that.session.kill_kernel();
190 }
190 }
191 // if we are autosaving, trigger an autosave on nav-away.
191 // if we are autosaving, trigger an autosave on nav-away.
192 // still warn, because if we don't the autosave may fail.
192 // still warn, because if we don't the autosave may fail.
193 if (that.dirty) {
193 if (that.dirty) {
194 if ( that.autosave_interval ) {
194 if ( that.autosave_interval ) {
195 // schedule autosave in a timeout
195 // schedule autosave in a timeout
196 // this gives you a chance to forcefully discard changes
196 // this gives you a chance to forcefully discard changes
197 // by reloading the page if you *really* want to.
197 // by reloading the page if you *really* want to.
198 // the timer doesn't start until you *dismiss* the dialog.
198 // the timer doesn't start until you *dismiss* the dialog.
199 setTimeout(function () {
199 setTimeout(function () {
200 if (that.dirty) {
200 if (that.dirty) {
201 that.save_notebook();
201 that.save_notebook();
202 }
202 }
203 }, 1000);
203 }, 1000);
204 return "Autosave in progress, latest changes may be lost.";
204 return "Autosave in progress, latest changes may be lost.";
205 } else {
205 } else {
206 return "Unsaved changes will be lost.";
206 return "Unsaved changes will be lost.";
207 }
207 }
208 };
208 };
209 // Null is the *only* return value that will make the browser not
209 // Null is the *only* return value that will make the browser not
210 // pop up the "don't leave" dialog.
210 // pop up the "don't leave" dialog.
211 return null;
211 return null;
212 };
212 };
213 };
213 };
214
214
215 /**
215 /**
216 * Set the dirty flag, and trigger the set_dirty.Notebook event
216 * Set the dirty flag, and trigger the set_dirty.Notebook event
217 *
217 *
218 * @method set_dirty
218 * @method set_dirty
219 */
219 */
220 Notebook.prototype.set_dirty = function (value) {
220 Notebook.prototype.set_dirty = function (value) {
221 if (value === undefined) {
221 if (value === undefined) {
222 value = true;
222 value = true;
223 }
223 }
224 if (this.dirty == value) {
224 if (this.dirty == value) {
225 return;
225 return;
226 }
226 }
227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
228 };
228 };
229
229
230 /**
230 /**
231 * Scroll the top of the page to a given cell.
231 * Scroll the top of the page to a given cell.
232 *
232 *
233 * @method scroll_to_cell
233 * @method scroll_to_cell
234 * @param {Number} cell_number An index of the cell to view
234 * @param {Number} cell_number An index of the cell to view
235 * @param {Number} time Animation time in milliseconds
235 * @param {Number} time Animation time in milliseconds
236 * @return {Number} Pixel offset from the top of the container
236 * @return {Number} Pixel offset from the top of the container
237 */
237 */
238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
239 var cells = this.get_cells();
239 var cells = this.get_cells();
240 var time = time || 0;
240 var time = time || 0;
241 cell_number = Math.min(cells.length-1,cell_number);
241 cell_number = Math.min(cells.length-1,cell_number);
242 cell_number = Math.max(0 ,cell_number);
242 cell_number = Math.max(0 ,cell_number);
243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
244 this.element.animate({scrollTop:scroll_value}, time);
244 this.element.animate({scrollTop:scroll_value}, time);
245 return scroll_value;
245 return scroll_value;
246 };
246 };
247
247
248 /**
248 /**
249 * Scroll to the bottom of the page.
249 * Scroll to the bottom of the page.
250 *
250 *
251 * @method scroll_to_bottom
251 * @method scroll_to_bottom
252 */
252 */
253 Notebook.prototype.scroll_to_bottom = function () {
253 Notebook.prototype.scroll_to_bottom = function () {
254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
255 };
255 };
256
256
257 /**
257 /**
258 * Scroll to the top of the page.
258 * Scroll to the top of the page.
259 *
259 *
260 * @method scroll_to_top
260 * @method scroll_to_top
261 */
261 */
262 Notebook.prototype.scroll_to_top = function () {
262 Notebook.prototype.scroll_to_top = function () {
263 this.element.animate({scrollTop:0}, 0);
263 this.element.animate({scrollTop:0}, 0);
264 };
264 };
265
265
266 // Edit Notebook metadata
266 // Edit Notebook metadata
267
267
268 Notebook.prototype.edit_metadata = function () {
268 Notebook.prototype.edit_metadata = function () {
269 var that = this;
269 var that = this;
270 IPython.dialog.edit_metadata(this.metadata, function (md) {
270 IPython.dialog.edit_metadata(this.metadata, function (md) {
271 that.metadata = md;
271 that.metadata = md;
272 }, 'Notebook');
272 }, 'Notebook');
273 };
273 };
274
274
275 // Cell indexing, retrieval, etc.
275 // Cell indexing, retrieval, etc.
276
276
277 /**
277 /**
278 * Get all cell elements in the notebook.
278 * Get all cell elements in the notebook.
279 *
279 *
280 * @method get_cell_elements
280 * @method get_cell_elements
281 * @return {jQuery} A selector of all cell elements
281 * @return {jQuery} A selector of all cell elements
282 */
282 */
283 Notebook.prototype.get_cell_elements = function () {
283 Notebook.prototype.get_cell_elements = function () {
284 return this.container.children("div.cell");
284 return this.container.children("div.cell");
285 };
285 };
286
286
287 /**
287 /**
288 * Get a particular cell element.
288 * Get a particular cell element.
289 *
289 *
290 * @method get_cell_element
290 * @method get_cell_element
291 * @param {Number} index An index of a cell to select
291 * @param {Number} index An index of a cell to select
292 * @return {jQuery} A selector of the given cell.
292 * @return {jQuery} A selector of the given cell.
293 */
293 */
294 Notebook.prototype.get_cell_element = function (index) {
294 Notebook.prototype.get_cell_element = function (index) {
295 var result = null;
295 var result = null;
296 var e = this.get_cell_elements().eq(index);
296 var e = this.get_cell_elements().eq(index);
297 if (e.length !== 0) {
297 if (e.length !== 0) {
298 result = e;
298 result = e;
299 }
299 }
300 return result;
300 return result;
301 };
301 };
302
302
303 /**
303 /**
304 * Count the cells in this notebook.
304 * Count the cells in this notebook.
305 *
305 *
306 * @method ncells
306 * @method ncells
307 * @return {Number} The number of cells in this notebook
307 * @return {Number} The number of cells in this notebook
308 */
308 */
309 Notebook.prototype.ncells = function () {
309 Notebook.prototype.ncells = function () {
310 return this.get_cell_elements().length;
310 return this.get_cell_elements().length;
311 };
311 };
312
312
313 /**
313 /**
314 * Get all Cell objects in this notebook.
314 * Get all Cell objects in this notebook.
315 *
315 *
316 * @method get_cells
316 * @method get_cells
317 * @return {Array} This notebook's Cell objects
317 * @return {Array} This notebook's Cell objects
318 */
318 */
319 // TODO: we are often calling cells as cells()[i], which we should optimize
319 // TODO: we are often calling cells as cells()[i], which we should optimize
320 // to cells(i) or a new method.
320 // to cells(i) or a new method.
321 Notebook.prototype.get_cells = function () {
321 Notebook.prototype.get_cells = function () {
322 return this.get_cell_elements().toArray().map(function (e) {
322 return this.get_cell_elements().toArray().map(function (e) {
323 return $(e).data("cell");
323 return $(e).data("cell");
324 });
324 });
325 };
325 };
326
326
327 /**
327 /**
328 * Get a Cell object from this notebook.
328 * Get a Cell object from this notebook.
329 *
329 *
330 * @method get_cell
330 * @method get_cell
331 * @param {Number} index An index of a cell to retrieve
331 * @param {Number} index An index of a cell to retrieve
332 * @return {Cell} A particular cell
332 * @return {Cell} A particular cell
333 */
333 */
334 Notebook.prototype.get_cell = function (index) {
334 Notebook.prototype.get_cell = function (index) {
335 var result = null;
335 var result = null;
336 var ce = this.get_cell_element(index);
336 var ce = this.get_cell_element(index);
337 if (ce !== null) {
337 if (ce !== null) {
338 result = ce.data('cell');
338 result = ce.data('cell');
339 }
339 }
340 return result;
340 return result;
341 }
341 }
342
342
343 /**
343 /**
344 * Get the cell below a given cell.
344 * Get the cell below a given cell.
345 *
345 *
346 * @method get_next_cell
346 * @method get_next_cell
347 * @param {Cell} cell The provided cell
347 * @param {Cell} cell The provided cell
348 * @return {Cell} The next cell
348 * @return {Cell} The next cell
349 */
349 */
350 Notebook.prototype.get_next_cell = function (cell) {
350 Notebook.prototype.get_next_cell = function (cell) {
351 var result = null;
351 var result = null;
352 var index = this.find_cell_index(cell);
352 var index = this.find_cell_index(cell);
353 if (this.is_valid_cell_index(index+1)) {
353 if (this.is_valid_cell_index(index+1)) {
354 result = this.get_cell(index+1);
354 result = this.get_cell(index+1);
355 }
355 }
356 return result;
356 return result;
357 }
357 }
358
358
359 /**
359 /**
360 * Get the cell above a given cell.
360 * Get the cell above a given cell.
361 *
361 *
362 * @method get_prev_cell
362 * @method get_prev_cell
363 * @param {Cell} cell The provided cell
363 * @param {Cell} cell The provided cell
364 * @return {Cell} The previous cell
364 * @return {Cell} The previous cell
365 */
365 */
366 Notebook.prototype.get_prev_cell = function (cell) {
366 Notebook.prototype.get_prev_cell = function (cell) {
367 // TODO: off-by-one
367 // TODO: off-by-one
368 // nb.get_prev_cell(nb.get_cell(1)) is null
368 // nb.get_prev_cell(nb.get_cell(1)) is null
369 var result = null;
369 var result = null;
370 var index = this.find_cell_index(cell);
370 var index = this.find_cell_index(cell);
371 if (index !== null && index > 1) {
371 if (index !== null && index > 1) {
372 result = this.get_cell(index-1);
372 result = this.get_cell(index-1);
373 }
373 }
374 return result;
374 return result;
375 }
375 }
376
376
377 /**
377 /**
378 * Get the numeric index of a given cell.
378 * Get the numeric index of a given cell.
379 *
379 *
380 * @method find_cell_index
380 * @method find_cell_index
381 * @param {Cell} cell The provided cell
381 * @param {Cell} cell The provided cell
382 * @return {Number} The cell's numeric index
382 * @return {Number} The cell's numeric index
383 */
383 */
384 Notebook.prototype.find_cell_index = function (cell) {
384 Notebook.prototype.find_cell_index = function (cell) {
385 var result = null;
385 var result = null;
386 this.get_cell_elements().filter(function (index) {
386 this.get_cell_elements().filter(function (index) {
387 if ($(this).data("cell") === cell) {
387 if ($(this).data("cell") === cell) {
388 result = index;
388 result = index;
389 };
389 };
390 });
390 });
391 return result;
391 return result;
392 };
392 };
393
393
394 /**
394 /**
395 * Get a given index , or the selected index if none is provided.
395 * Get a given index , or the selected index if none is provided.
396 *
396 *
397 * @method index_or_selected
397 * @method index_or_selected
398 * @param {Number} index A cell's index
398 * @param {Number} index A cell's index
399 * @return {Number} The given index, or selected index if none is provided.
399 * @return {Number} The given index, or selected index if none is provided.
400 */
400 */
401 Notebook.prototype.index_or_selected = function (index) {
401 Notebook.prototype.index_or_selected = function (index) {
402 var i;
402 var i;
403 if (index === undefined || index === null) {
403 if (index === undefined || index === null) {
404 i = this.get_selected_index();
404 i = this.get_selected_index();
405 if (i === null) {
405 if (i === null) {
406 i = 0;
406 i = 0;
407 }
407 }
408 } else {
408 } else {
409 i = index;
409 i = index;
410 }
410 }
411 return i;
411 return i;
412 };
412 };
413
413
414 /**
414 /**
415 * Get the currently selected cell.
415 * Get the currently selected cell.
416 * @method get_selected_cell
416 * @method get_selected_cell
417 * @return {Cell} The selected cell
417 * @return {Cell} The selected cell
418 */
418 */
419 Notebook.prototype.get_selected_cell = function () {
419 Notebook.prototype.get_selected_cell = function () {
420 var index = this.get_selected_index();
420 var index = this.get_selected_index();
421 return this.get_cell(index);
421 return this.get_cell(index);
422 };
422 };
423
423
424 /**
424 /**
425 * Check whether a cell index is valid.
425 * Check whether a cell index is valid.
426 *
426 *
427 * @method is_valid_cell_index
427 * @method is_valid_cell_index
428 * @param {Number} index A cell index
428 * @param {Number} index A cell index
429 * @return True if the index is valid, false otherwise
429 * @return True if the index is valid, false otherwise
430 */
430 */
431 Notebook.prototype.is_valid_cell_index = function (index) {
431 Notebook.prototype.is_valid_cell_index = function (index) {
432 if (index !== null && index >= 0 && index < this.ncells()) {
432 if (index !== null && index >= 0 && index < this.ncells()) {
433 return true;
433 return true;
434 } else {
434 } else {
435 return false;
435 return false;
436 };
436 };
437 }
437 }
438
438
439 /**
439 /**
440 * Get the index of the currently selected cell.
440 * Get the index of the currently selected cell.
441
441
442 * @method get_selected_index
442 * @method get_selected_index
443 * @return {Number} The selected cell's numeric index
443 * @return {Number} The selected cell's numeric index
444 */
444 */
445 Notebook.prototype.get_selected_index = function () {
445 Notebook.prototype.get_selected_index = function () {
446 var result = null;
446 var result = null;
447 this.get_cell_elements().filter(function (index) {
447 this.get_cell_elements().filter(function (index) {
448 if ($(this).data("cell").selected === true) {
448 if ($(this).data("cell").selected === true) {
449 result = index;
449 result = index;
450 };
450 };
451 });
451 });
452 return result;
452 return result;
453 };
453 };
454
454
455
455
456 // Cell selection.
456 // Cell selection.
457
457
458 /**
458 /**
459 * Programmatically select a cell.
459 * Programmatically select a cell.
460 *
460 *
461 * @method select
461 * @method select
462 * @param {Number} index A cell's index
462 * @param {Number} index A cell's index
463 * @return {Notebook} This notebook
463 * @return {Notebook} This notebook
464 */
464 */
465 Notebook.prototype.select = function (index) {
465 Notebook.prototype.select = function (index) {
466 if (this.is_valid_cell_index(index)) {
466 if (this.is_valid_cell_index(index)) {
467 var sindex = this.get_selected_index()
467 var sindex = this.get_selected_index()
468 if (sindex !== null && index !== sindex) {
468 if (sindex !== null && index !== sindex) {
469 this.command_mode();
469 this.command_mode();
470 this.get_cell(sindex).unselect();
470 this.get_cell(sindex).unselect();
471 };
471 };
472 var cell = this.get_cell(index);
472 var cell = this.get_cell(index);
473 cell.select();
473 cell.select();
474 if (cell.cell_type === 'heading') {
474 if (cell.cell_type === 'heading') {
475 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
475 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
476 {'cell_type':cell.cell_type,level:cell.level}
476 {'cell_type':cell.cell_type,level:cell.level}
477 );
477 );
478 } else {
478 } else {
479 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
479 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
480 {'cell_type':cell.cell_type}
480 {'cell_type':cell.cell_type}
481 );
481 );
482 };
482 };
483 };
483 };
484 return this;
484 return this;
485 };
485 };
486
486
487 /**
487 /**
488 * Programmatically select the next cell.
488 * Programmatically select the next cell.
489 *
489 *
490 * @method select_next
490 * @method select_next
491 * @return {Notebook} This notebook
491 * @return {Notebook} This notebook
492 */
492 */
493 Notebook.prototype.select_next = function () {
493 Notebook.prototype.select_next = function () {
494 var index = this.get_selected_index();
494 var index = this.get_selected_index();
495 this.select(index+1);
495 this.select(index+1);
496 return this;
496 return this;
497 };
497 };
498
498
499 /**
499 /**
500 * Programmatically select the previous cell.
500 * Programmatically select the previous cell.
501 *
501 *
502 * @method select_prev
502 * @method select_prev
503 * @return {Notebook} This notebook
503 * @return {Notebook} This notebook
504 */
504 */
505 Notebook.prototype.select_prev = function () {
505 Notebook.prototype.select_prev = function () {
506 var index = this.get_selected_index();
506 var index = this.get_selected_index();
507 this.select(index-1);
507 this.select(index-1);
508 return this;
508 return this;
509 };
509 };
510
510
511
511
512 // Edit/Command mode
512 // Edit/Command mode
513
513
514 Notebook.prototype.get_edit_index = function () {
514 Notebook.prototype.get_edit_index = function () {
515 var result = null;
515 var result = null;
516 this.get_cell_elements().filter(function (index) {
516 this.get_cell_elements().filter(function (index) {
517 if ($(this).data("cell").mode === 'edit') {
517 if ($(this).data("cell").mode === 'edit') {
518 result = index;
518 result = index;
519 };
519 };
520 });
520 });
521 return result;
521 return result;
522 };
522 };
523
523
524 Notebook.prototype.command_mode = function () {
524 Notebook.prototype.command_mode = function () {
525 if (this.mode !== 'command') {
525 if (this.mode !== 'command') {
526 var index = this.get_edit_index();
526 var index = this.get_edit_index();
527 var cell = this.get_cell(index);
527 var cell = this.get_cell(index);
528 if (cell) {
528 if (cell) {
529 cell.command_mode();
529 cell.command_mode();
530 };
530 };
531 this.mode = 'command';
531 this.mode = 'command';
532 IPython.keyboard_manager.command_mode();
532 IPython.keyboard_manager.command_mode();
533 };
533 };
534 };
534 };
535
535
536 Notebook.prototype.edit_mode = function () {
536 Notebook.prototype.edit_mode = function () {
537 if (this.mode !== 'edit') {
537 if (this.mode !== 'edit') {
538 var cell = this.get_selected_cell();
538 var cell = this.get_selected_cell();
539 if (cell === null) {return;} // No cell is selected
539 if (cell === null) {return;} // No cell is selected
540 // We need to set the mode to edit to prevent reentering this method
540 // We need to set the mode to edit to prevent reentering this method
541 // when cell.edit_mode() is called below.
541 // when cell.edit_mode() is called below.
542 this.mode = 'edit';
542 this.mode = 'edit';
543 IPython.keyboard_manager.edit_mode();
543 IPython.keyboard_manager.edit_mode();
544 cell.edit_mode();
544 cell.edit_mode();
545 };
545 };
546 };
546 };
547
547
548 Notebook.prototype.focus_cell = function () {
548 Notebook.prototype.focus_cell = function () {
549 var cell = this.get_selected_cell();
549 var cell = this.get_selected_cell();
550 if (cell === null) {return;} // No cell is selected
550 if (cell === null) {return;} // No cell is selected
551 cell.focus_cell();
551 cell.focus_cell();
552 };
552 };
553
553
554 // Cell movement
554 // Cell movement
555
555
556 /**
556 /**
557 * Move given (or selected) cell up and select it.
557 * Move given (or selected) cell up and select it.
558 *
558 *
559 * @method move_cell_up
559 * @method move_cell_up
560 * @param [index] {integer} cell index
560 * @param [index] {integer} cell index
561 * @return {Notebook} This notebook
561 * @return {Notebook} This notebook
562 **/
562 **/
563 Notebook.prototype.move_cell_up = function (index) {
563 Notebook.prototype.move_cell_up = function (index) {
564 var i = this.index_or_selected(index);
564 var i = this.index_or_selected(index);
565 if (this.is_valid_cell_index(i) && i > 0) {
565 if (this.is_valid_cell_index(i) && i > 0) {
566 var pivot = this.get_cell_element(i-1);
566 var pivot = this.get_cell_element(i-1);
567 var tomove = this.get_cell_element(i);
567 var tomove = this.get_cell_element(i);
568 if (pivot !== null && tomove !== null) {
568 if (pivot !== null && tomove !== null) {
569 tomove.detach();
569 tomove.detach();
570 pivot.before(tomove);
570 pivot.before(tomove);
571 this.select(i-1);
571 this.select(i-1);
572 var cell = this.get_selected_cell();
572 var cell = this.get_selected_cell();
573 cell.focus_cell();
573 cell.focus_cell();
574 };
574 };
575 this.set_dirty(true);
575 this.set_dirty(true);
576 };
576 };
577 return this;
577 return this;
578 };
578 };
579
579
580
580
581 /**
581 /**
582 * Move given (or selected) cell down and select it
582 * Move given (or selected) cell down and select it
583 *
583 *
584 * @method move_cell_down
584 * @method move_cell_down
585 * @param [index] {integer} cell index
585 * @param [index] {integer} cell index
586 * @return {Notebook} This notebook
586 * @return {Notebook} This notebook
587 **/
587 **/
588 Notebook.prototype.move_cell_down = function (index) {
588 Notebook.prototype.move_cell_down = function (index) {
589 var i = this.index_or_selected(index);
589 var i = this.index_or_selected(index);
590 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
590 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
591 var pivot = this.get_cell_element(i+1);
591 var pivot = this.get_cell_element(i+1);
592 var tomove = this.get_cell_element(i);
592 var tomove = this.get_cell_element(i);
593 if (pivot !== null && tomove !== null) {
593 if (pivot !== null && tomove !== null) {
594 tomove.detach();
594 tomove.detach();
595 pivot.after(tomove);
595 pivot.after(tomove);
596 this.select(i+1);
596 this.select(i+1);
597 var cell = this.get_selected_cell();
597 var cell = this.get_selected_cell();
598 cell.focus_cell();
598 cell.focus_cell();
599 };
599 };
600 };
600 };
601 this.set_dirty();
601 this.set_dirty();
602 return this;
602 return this;
603 };
603 };
604
604
605
605
606 // Insertion, deletion.
606 // Insertion, deletion.
607
607
608 /**
608 /**
609 * Delete a cell from the notebook.
609 * Delete a cell from the notebook.
610 *
610 *
611 * @method delete_cell
611 * @method delete_cell
612 * @param [index] A cell's numeric index
612 * @param [index] A cell's numeric index
613 * @return {Notebook} This notebook
613 * @return {Notebook} This notebook
614 */
614 */
615 Notebook.prototype.delete_cell = function (index) {
615 Notebook.prototype.delete_cell = function (index) {
616 var i = this.index_or_selected(index);
616 var i = this.index_or_selected(index);
617 var cell = this.get_selected_cell();
617 var cell = this.get_selected_cell();
618 this.undelete_backup = cell.toJSON();
618 this.undelete_backup = cell.toJSON();
619 $('#undelete_cell').removeClass('disabled');
619 $('#undelete_cell').removeClass('disabled');
620 if (this.is_valid_cell_index(i)) {
620 if (this.is_valid_cell_index(i)) {
621 var old_ncells = this.ncells();
621 var old_ncells = this.ncells();
622 var ce = this.get_cell_element(i);
622 var ce = this.get_cell_element(i);
623 ce.remove();
623 ce.remove();
624 if (i === 0) {
624 if (i === 0) {
625 // Always make sure we have at least one cell.
625 // Always make sure we have at least one cell.
626 if (old_ncells === 1) {
626 if (old_ncells === 1) {
627 this.insert_cell_below('code');
627 this.insert_cell_below('code');
628 }
628 }
629 this.select(0);
629 this.select(0);
630 this.undelete_index = 0;
630 this.undelete_index = 0;
631 this.undelete_below = false;
631 this.undelete_below = false;
632 } else if (i === old_ncells-1 && i !== 0) {
632 } else if (i === old_ncells-1 && i !== 0) {
633 this.select(i-1);
633 this.select(i-1);
634 this.undelete_index = i - 1;
634 this.undelete_index = i - 1;
635 this.undelete_below = true;
635 this.undelete_below = true;
636 } else {
636 } else {
637 this.select(i);
637 this.select(i);
638 this.undelete_index = i;
638 this.undelete_index = i;
639 this.undelete_below = false;
639 this.undelete_below = false;
640 };
640 };
641 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
641 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
642 this.set_dirty(true);
642 this.set_dirty(true);
643 };
643 };
644 return this;
644 return this;
645 };
645 };
646
646
647 /**
647 /**
648 * Restore the most recently deleted cell.
648 * Restore the most recently deleted cell.
649 *
649 *
650 * @method undelete
650 * @method undelete
651 */
651 */
652 Notebook.prototype.undelete_cell = function() {
652 Notebook.prototype.undelete_cell = function() {
653 if (this.undelete_backup !== null && this.undelete_index !== null) {
653 if (this.undelete_backup !== null && this.undelete_index !== null) {
654 var current_index = this.get_selected_index();
654 var current_index = this.get_selected_index();
655 if (this.undelete_index < current_index) {
655 if (this.undelete_index < current_index) {
656 current_index = current_index + 1;
656 current_index = current_index + 1;
657 }
657 }
658 if (this.undelete_index >= this.ncells()) {
658 if (this.undelete_index >= this.ncells()) {
659 this.select(this.ncells() - 1);
659 this.select(this.ncells() - 1);
660 }
660 }
661 else {
661 else {
662 this.select(this.undelete_index);
662 this.select(this.undelete_index);
663 }
663 }
664 var cell_data = this.undelete_backup;
664 var cell_data = this.undelete_backup;
665 var new_cell = null;
665 var new_cell = null;
666 if (this.undelete_below) {
666 if (this.undelete_below) {
667 new_cell = this.insert_cell_below(cell_data.cell_type);
667 new_cell = this.insert_cell_below(cell_data.cell_type);
668 } else {
668 } else {
669 new_cell = this.insert_cell_above(cell_data.cell_type);
669 new_cell = this.insert_cell_above(cell_data.cell_type);
670 }
670 }
671 new_cell.fromJSON(cell_data);
671 new_cell.fromJSON(cell_data);
672 if (this.undelete_below) {
672 if (this.undelete_below) {
673 this.select(current_index+1);
673 this.select(current_index+1);
674 } else {
674 } else {
675 this.select(current_index);
675 this.select(current_index);
676 }
676 }
677 this.undelete_backup = null;
677 this.undelete_backup = null;
678 this.undelete_index = null;
678 this.undelete_index = null;
679 }
679 }
680 $('#undelete_cell').addClass('disabled');
680 $('#undelete_cell').addClass('disabled');
681 }
681 }
682
682
683 /**
683 /**
684 * Insert a cell so that after insertion the cell is at given index.
684 * Insert a cell so that after insertion the cell is at given index.
685 *
685 *
686 * Similar to insert_above, but index parameter is mandatory
686 * Similar to insert_above, but index parameter is mandatory
687 *
687 *
688 * Index will be brought back into the accissible range [0,n]
688 * Index will be brought back into the accissible range [0,n]
689 *
689 *
690 * @method insert_cell_at_index
690 * @method insert_cell_at_index
691 * @param type {string} in ['code','markdown','heading']
691 * @param type {string} in ['code','markdown','heading']
692 * @param [index] {int} a valid index where to inser cell
692 * @param [index] {int} a valid index where to inser cell
693 *
693 *
694 * @return cell {cell|null} created cell or null
694 * @return cell {cell|null} created cell or null
695 **/
695 **/
696 Notebook.prototype.insert_cell_at_index = function(type, index){
696 Notebook.prototype.insert_cell_at_index = function(type, index){
697
697
698 var ncells = this.ncells();
698 var ncells = this.ncells();
699 var index = Math.min(index,ncells);
699 var index = Math.min(index,ncells);
700 index = Math.max(index,0);
700 index = Math.max(index,0);
701 var cell = null;
701 var cell = null;
702
702
703 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
703 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
704 if (type === 'code') {
704 if (type === 'code') {
705 cell = new IPython.CodeCell(this.kernel);
705 cell = new IPython.CodeCell(this.kernel);
706 cell.set_input_prompt();
706 cell.set_input_prompt();
707 } else if (type === 'markdown') {
707 } else if (type === 'markdown') {
708 cell = new IPython.MarkdownCell();
708 cell = new IPython.MarkdownCell();
709 } else if (type === 'raw') {
709 } else if (type === 'raw') {
710 cell = new IPython.RawCell();
710 cell = new IPython.RawCell();
711 } else if (type === 'heading') {
711 } else if (type === 'heading') {
712 cell = new IPython.HeadingCell();
712 cell = new IPython.HeadingCell();
713 }
713 }
714
714
715 if(this._insert_element_at_index(cell.element,index)) {
715 if(this._insert_element_at_index(cell.element,index)) {
716 cell.render();
716 cell.render();
717 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
717 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
718 cell.refresh();
718 cell.refresh();
719 // We used to select the cell after we refresh it, but there
719 // We used to select the cell after we refresh it, but there
720 // are now cases were this method is called where select is
720 // are now cases were this method is called where select is
721 // not appropriate. The selection logic should be handled by the
721 // not appropriate. The selection logic should be handled by the
722 // caller of the the top level insert_cell methods.
722 // caller of the the top level insert_cell methods.
723 this.set_dirty(true);
723 this.set_dirty(true);
724 }
724 }
725 }
725 }
726 return cell;
726 return cell;
727
727
728 };
728 };
729
729
730 /**
730 /**
731 * Insert an element at given cell index.
731 * Insert an element at given cell index.
732 *
732 *
733 * @method _insert_element_at_index
733 * @method _insert_element_at_index
734 * @param element {dom element} a cell element
734 * @param element {dom element} a cell element
735 * @param [index] {int} a valid index where to inser cell
735 * @param [index] {int} a valid index where to inser cell
736 * @private
736 * @private
737 *
737 *
738 * return true if everything whent fine.
738 * return true if everything whent fine.
739 **/
739 **/
740 Notebook.prototype._insert_element_at_index = function(element, index){
740 Notebook.prototype._insert_element_at_index = function(element, index){
741 if (element === undefined){
741 if (element === undefined){
742 return false;
742 return false;
743 }
743 }
744
744
745 var ncells = this.ncells();
745 var ncells = this.ncells();
746
746
747 if (ncells === 0) {
747 if (ncells === 0) {
748 // special case append if empty
748 // special case append if empty
749 this.element.find('div.end_space').before(element);
749 this.element.find('div.end_space').before(element);
750 } else if ( ncells === index ) {
750 } else if ( ncells === index ) {
751 // special case append it the end, but not empty
751 // special case append it the end, but not empty
752 this.get_cell_element(index-1).after(element);
752 this.get_cell_element(index-1).after(element);
753 } else if (this.is_valid_cell_index(index)) {
753 } else if (this.is_valid_cell_index(index)) {
754 // otherwise always somewhere to append to
754 // otherwise always somewhere to append to
755 this.get_cell_element(index).before(element);
755 this.get_cell_element(index).before(element);
756 } else {
756 } else {
757 return false;
757 return false;
758 }
758 }
759
759
760 if (this.undelete_index !== null && index <= this.undelete_index) {
760 if (this.undelete_index !== null && index <= this.undelete_index) {
761 this.undelete_index = this.undelete_index + 1;
761 this.undelete_index = this.undelete_index + 1;
762 this.set_dirty(true);
762 this.set_dirty(true);
763 }
763 }
764 return true;
764 return true;
765 };
765 };
766
766
767 /**
767 /**
768 * Insert a cell of given type above given index, or at top
768 * Insert a cell of given type above given index, or at top
769 * of notebook if index smaller than 0.
769 * of notebook if index smaller than 0.
770 *
770 *
771 * default index value is the one of currently selected cell
771 * default index value is the one of currently selected cell
772 *
772 *
773 * @method insert_cell_above
773 * @method insert_cell_above
774 * @param type {string} cell type
774 * @param type {string} cell type
775 * @param [index] {integer}
775 * @param [index] {integer}
776 *
776 *
777 * @return handle to created cell or null
777 * @return handle to created cell or null
778 **/
778 **/
779 Notebook.prototype.insert_cell_above = function (type, index) {
779 Notebook.prototype.insert_cell_above = function (type, index) {
780 index = this.index_or_selected(index);
780 index = this.index_or_selected(index);
781 return this.insert_cell_at_index(type, index);
781 return this.insert_cell_at_index(type, index);
782 };
782 };
783
783
784 /**
784 /**
785 * Insert a cell of given type below given index, or at bottom
785 * Insert a cell of given type below given index, or at bottom
786 * of notebook if index greater thatn number of cell
786 * of notebook if index greater thatn number of cell
787 *
787 *
788 * default index value is the one of currently selected cell
788 * default index value is the one of currently selected cell
789 *
789 *
790 * @method insert_cell_below
790 * @method insert_cell_below
791 * @param type {string} cell type
791 * @param type {string} cell type
792 * @param [index] {integer}
792 * @param [index] {integer}
793 *
793 *
794 * @return handle to created cell or null
794 * @return handle to created cell or null
795 *
795 *
796 **/
796 **/
797 Notebook.prototype.insert_cell_below = function (type, index) {
797 Notebook.prototype.insert_cell_below = function (type, index) {
798 index = this.index_or_selected(index);
798 index = this.index_or_selected(index);
799 return this.insert_cell_at_index(type, index+1);
799 return this.insert_cell_at_index(type, index+1);
800 };
800 };
801
801
802
802
803 /**
803 /**
804 * Insert cell at end of notebook
804 * Insert cell at end of notebook
805 *
805 *
806 * @method insert_cell_at_bottom
806 * @method insert_cell_at_bottom
807 * @param {String} type cell type
807 * @param {String} type cell type
808 *
808 *
809 * @return the added cell; or null
809 * @return the added cell; or null
810 **/
810 **/
811 Notebook.prototype.insert_cell_at_bottom = function (type){
811 Notebook.prototype.insert_cell_at_bottom = function (type){
812 var len = this.ncells();
812 var len = this.ncells();
813 return this.insert_cell_below(type,len-1);
813 return this.insert_cell_below(type,len-1);
814 };
814 };
815
815
816 /**
816 /**
817 * Turn a cell into a code cell.
817 * Turn a cell into a code cell.
818 *
818 *
819 * @method to_code
819 * @method to_code
820 * @param {Number} [index] A cell's index
820 * @param {Number} [index] A cell's index
821 */
821 */
822 Notebook.prototype.to_code = function (index) {
822 Notebook.prototype.to_code = function (index) {
823 var i = this.index_or_selected(index);
823 var i = this.index_or_selected(index);
824 if (this.is_valid_cell_index(i)) {
824 if (this.is_valid_cell_index(i)) {
825 var source_element = this.get_cell_element(i);
825 var source_element = this.get_cell_element(i);
826 var source_cell = source_element.data("cell");
826 var source_cell = source_element.data("cell");
827 if (!(source_cell instanceof IPython.CodeCell)) {
827 if (!(source_cell instanceof IPython.CodeCell)) {
828 var target_cell = this.insert_cell_below('code',i);
828 var target_cell = this.insert_cell_below('code',i);
829 var text = source_cell.get_text();
829 var text = source_cell.get_text();
830 if (text === source_cell.placeholder) {
830 if (text === source_cell.placeholder) {
831 text = '';
831 text = '';
832 }
832 }
833 target_cell.set_text(text);
833 target_cell.set_text(text);
834 // make this value the starting point, so that we can only undo
834 // make this value the starting point, so that we can only undo
835 // to this state, instead of a blank cell
835 // to this state, instead of a blank cell
836 target_cell.code_mirror.clearHistory();
836 target_cell.code_mirror.clearHistory();
837 source_element.remove();
837 source_element.remove();
838 this.select(i);
838 this.select(i);
839 this.edit_mode();
839 this.edit_mode();
840 this.set_dirty(true);
840 this.set_dirty(true);
841 };
841 };
842 };
842 };
843 };
843 };
844
844
845 /**
845 /**
846 * Turn a cell into a Markdown cell.
846 * Turn a cell into a Markdown cell.
847 *
847 *
848 * @method to_markdown
848 * @method to_markdown
849 * @param {Number} [index] A cell's index
849 * @param {Number} [index] A cell's index
850 */
850 */
851 Notebook.prototype.to_markdown = function (index) {
851 Notebook.prototype.to_markdown = function (index) {
852 var i = this.index_or_selected(index);
852 var i = this.index_or_selected(index);
853 if (this.is_valid_cell_index(i)) {
853 if (this.is_valid_cell_index(i)) {
854 var source_element = this.get_cell_element(i);
854 var source_element = this.get_cell_element(i);
855 var source_cell = source_element.data("cell");
855 var source_cell = source_element.data("cell");
856 if (!(source_cell instanceof IPython.MarkdownCell)) {
856 if (!(source_cell instanceof IPython.MarkdownCell)) {
857 var target_cell = this.insert_cell_below('markdown',i);
857 var target_cell = this.insert_cell_below('markdown',i);
858 var text = source_cell.get_text();
858 var text = source_cell.get_text();
859 if (text === source_cell.placeholder) {
859 if (text === source_cell.placeholder) {
860 text = '';
860 text = '';
861 };
861 };
862 // We must show the editor before setting its contents
862 // We must show the editor before setting its contents
863 target_cell.unrender();
863 target_cell.unrender();
864 target_cell.set_text(text);
864 target_cell.set_text(text);
865 // make this value the starting point, so that we can only undo
865 // make this value the starting point, so that we can only undo
866 // to this state, instead of a blank cell
866 // to this state, instead of a blank cell
867 target_cell.code_mirror.clearHistory();
867 target_cell.code_mirror.clearHistory();
868 source_element.remove();
868 source_element.remove();
869 this.select(i);
869 this.select(i);
870 this.edit_mode();
870 this.edit_mode();
871 this.set_dirty(true);
871 this.set_dirty(true);
872 };
872 };
873 };
873 };
874 };
874 };
875
875
876 /**
876 /**
877 * Turn a cell into a raw text cell.
877 * Turn a cell into a raw text cell.
878 *
878 *
879 * @method to_raw
879 * @method to_raw
880 * @param {Number} [index] A cell's index
880 * @param {Number} [index] A cell's index
881 */
881 */
882 Notebook.prototype.to_raw = function (index) {
882 Notebook.prototype.to_raw = function (index) {
883 var i = this.index_or_selected(index);
883 var i = this.index_or_selected(index);
884 if (this.is_valid_cell_index(i)) {
884 if (this.is_valid_cell_index(i)) {
885 var source_element = this.get_cell_element(i);
885 var source_element = this.get_cell_element(i);
886 var source_cell = source_element.data("cell");
886 var source_cell = source_element.data("cell");
887 var target_cell = null;
887 var target_cell = null;
888 if (!(source_cell instanceof IPython.RawCell)) {
888 if (!(source_cell instanceof IPython.RawCell)) {
889 target_cell = this.insert_cell_below('raw',i);
889 target_cell = this.insert_cell_below('raw',i);
890 var text = source_cell.get_text();
890 var text = source_cell.get_text();
891 if (text === source_cell.placeholder) {
891 if (text === source_cell.placeholder) {
892 text = '';
892 text = '';
893 };
893 };
894 // We must show the editor before setting its contents
894 // We must show the editor before setting its contents
895 target_cell.unrender();
895 target_cell.unrender();
896 target_cell.set_text(text);
896 target_cell.set_text(text);
897 // make this value the starting point, so that we can only undo
897 // make this value the starting point, so that we can only undo
898 // to this state, instead of a blank cell
898 // to this state, instead of a blank cell
899 target_cell.code_mirror.clearHistory();
899 target_cell.code_mirror.clearHistory();
900 source_element.remove();
900 source_element.remove();
901 this.select(i);
901 this.select(i);
902 this.edit_mode();
902 this.edit_mode();
903 this.set_dirty(true);
903 this.set_dirty(true);
904 };
904 };
905 };
905 };
906 };
906 };
907
907
908 /**
908 /**
909 * Turn a cell into a heading cell.
909 * Turn a cell into a heading cell.
910 *
910 *
911 * @method to_heading
911 * @method to_heading
912 * @param {Number} [index] A cell's index
912 * @param {Number} [index] A cell's index
913 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
913 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
914 */
914 */
915 Notebook.prototype.to_heading = function (index, level) {
915 Notebook.prototype.to_heading = function (index, level) {
916 level = level || 1;
916 level = level || 1;
917 var i = this.index_or_selected(index);
917 var i = this.index_or_selected(index);
918 if (this.is_valid_cell_index(i)) {
918 if (this.is_valid_cell_index(i)) {
919 var source_element = this.get_cell_element(i);
919 var source_element = this.get_cell_element(i);
920 var source_cell = source_element.data("cell");
920 var source_cell = source_element.data("cell");
921 var target_cell = null;
921 var target_cell = null;
922 if (source_cell instanceof IPython.HeadingCell) {
922 if (source_cell instanceof IPython.HeadingCell) {
923 source_cell.set_level(level);
923 source_cell.set_level(level);
924 } else {
924 } else {
925 target_cell = this.insert_cell_below('heading',i);
925 target_cell = this.insert_cell_below('heading',i);
926 var text = source_cell.get_text();
926 var text = source_cell.get_text();
927 if (text === source_cell.placeholder) {
927 if (text === source_cell.placeholder) {
928 text = '';
928 text = '';
929 };
929 };
930 // We must show the editor before setting its contents
930 // We must show the editor before setting its contents
931 target_cell.set_level(level);
931 target_cell.set_level(level);
932 target_cell.unrender();
932 target_cell.unrender();
933 target_cell.set_text(text);
933 target_cell.set_text(text);
934 // make this value the starting point, so that we can only undo
934 // make this value the starting point, so that we can only undo
935 // to this state, instead of a blank cell
935 // to this state, instead of a blank cell
936 target_cell.code_mirror.clearHistory();
936 target_cell.code_mirror.clearHistory();
937 source_element.remove();
937 source_element.remove();
938 this.select(i);
938 this.select(i);
939 };
939 };
940 this.edit_mode();
940 this.edit_mode();
941 this.set_dirty(true);
941 this.set_dirty(true);
942 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
942 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
943 {'cell_type':'heading',level:level}
943 {'cell_type':'heading',level:level}
944 );
944 );
945 };
945 };
946 };
946 };
947
947
948
948
949 // Cut/Copy/Paste
949 // Cut/Copy/Paste
950
950
951 /**
951 /**
952 * Enable UI elements for pasting cells.
952 * Enable UI elements for pasting cells.
953 *
953 *
954 * @method enable_paste
954 * @method enable_paste
955 */
955 */
956 Notebook.prototype.enable_paste = function () {
956 Notebook.prototype.enable_paste = function () {
957 var that = this;
957 var that = this;
958 if (!this.paste_enabled) {
958 if (!this.paste_enabled) {
959 $('#paste_cell_replace').removeClass('disabled')
959 $('#paste_cell_replace').removeClass('disabled')
960 .on('click', function () {that.paste_cell_replace();});
960 .on('click', function () {that.paste_cell_replace();});
961 $('#paste_cell_above').removeClass('disabled')
961 $('#paste_cell_above').removeClass('disabled')
962 .on('click', function () {that.paste_cell_above();});
962 .on('click', function () {that.paste_cell_above();});
963 $('#paste_cell_below').removeClass('disabled')
963 $('#paste_cell_below').removeClass('disabled')
964 .on('click', function () {that.paste_cell_below();});
964 .on('click', function () {that.paste_cell_below();});
965 this.paste_enabled = true;
965 this.paste_enabled = true;
966 };
966 };
967 };
967 };
968
968
969 /**
969 /**
970 * Disable UI elements for pasting cells.
970 * Disable UI elements for pasting cells.
971 *
971 *
972 * @method disable_paste
972 * @method disable_paste
973 */
973 */
974 Notebook.prototype.disable_paste = function () {
974 Notebook.prototype.disable_paste = function () {
975 if (this.paste_enabled) {
975 if (this.paste_enabled) {
976 $('#paste_cell_replace').addClass('disabled').off('click');
976 $('#paste_cell_replace').addClass('disabled').off('click');
977 $('#paste_cell_above').addClass('disabled').off('click');
977 $('#paste_cell_above').addClass('disabled').off('click');
978 $('#paste_cell_below').addClass('disabled').off('click');
978 $('#paste_cell_below').addClass('disabled').off('click');
979 this.paste_enabled = false;
979 this.paste_enabled = false;
980 };
980 };
981 };
981 };
982
982
983 /**
983 /**
984 * Cut a cell.
984 * Cut a cell.
985 *
985 *
986 * @method cut_cell
986 * @method cut_cell
987 */
987 */
988 Notebook.prototype.cut_cell = function () {
988 Notebook.prototype.cut_cell = function () {
989 this.copy_cell();
989 this.copy_cell();
990 this.delete_cell();
990 this.delete_cell();
991 }
991 }
992
992
993 /**
993 /**
994 * Copy a cell.
994 * Copy a cell.
995 *
995 *
996 * @method copy_cell
996 * @method copy_cell
997 */
997 */
998 Notebook.prototype.copy_cell = function () {
998 Notebook.prototype.copy_cell = function () {
999 var cell = this.get_selected_cell();
999 var cell = this.get_selected_cell();
1000 this.clipboard = cell.toJSON();
1000 this.clipboard = cell.toJSON();
1001 this.enable_paste();
1001 this.enable_paste();
1002 };
1002 };
1003
1003
1004 /**
1004 /**
1005 * Replace the selected cell with a cell in the clipboard.
1005 * Replace the selected cell with a cell in the clipboard.
1006 *
1006 *
1007 * @method paste_cell_replace
1007 * @method paste_cell_replace
1008 */
1008 */
1009 Notebook.prototype.paste_cell_replace = function () {
1009 Notebook.prototype.paste_cell_replace = function () {
1010 if (this.clipboard !== null && this.paste_enabled) {
1010 if (this.clipboard !== null && this.paste_enabled) {
1011 var cell_data = this.clipboard;
1011 var cell_data = this.clipboard;
1012 var new_cell = this.insert_cell_above(cell_data.cell_type);
1012 var new_cell = this.insert_cell_above(cell_data.cell_type);
1013 new_cell.fromJSON(cell_data);
1013 new_cell.fromJSON(cell_data);
1014 var old_cell = this.get_next_cell(new_cell);
1014 var old_cell = this.get_next_cell(new_cell);
1015 this.delete_cell(this.find_cell_index(old_cell));
1015 this.delete_cell(this.find_cell_index(old_cell));
1016 this.select(this.find_cell_index(new_cell));
1016 this.select(this.find_cell_index(new_cell));
1017 };
1017 };
1018 };
1018 };
1019
1019
1020 /**
1020 /**
1021 * Paste a cell from the clipboard above the selected cell.
1021 * Paste a cell from the clipboard above the selected cell.
1022 *
1022 *
1023 * @method paste_cell_above
1023 * @method paste_cell_above
1024 */
1024 */
1025 Notebook.prototype.paste_cell_above = function () {
1025 Notebook.prototype.paste_cell_above = function () {
1026 if (this.clipboard !== null && this.paste_enabled) {
1026 if (this.clipboard !== null && this.paste_enabled) {
1027 var cell_data = this.clipboard;
1027 var cell_data = this.clipboard;
1028 var new_cell = this.insert_cell_above(cell_data.cell_type);
1028 var new_cell = this.insert_cell_above(cell_data.cell_type);
1029 new_cell.fromJSON(cell_data);
1029 new_cell.fromJSON(cell_data);
1030 };
1030 };
1031 };
1031 };
1032
1032
1033 /**
1033 /**
1034 * Paste a cell from the clipboard below the selected cell.
1034 * Paste a cell from the clipboard below the selected cell.
1035 *
1035 *
1036 * @method paste_cell_below
1036 * @method paste_cell_below
1037 */
1037 */
1038 Notebook.prototype.paste_cell_below = function () {
1038 Notebook.prototype.paste_cell_below = function () {
1039 if (this.clipboard !== null && this.paste_enabled) {
1039 if (this.clipboard !== null && this.paste_enabled) {
1040 var cell_data = this.clipboard;
1040 var cell_data = this.clipboard;
1041 var new_cell = this.insert_cell_below(cell_data.cell_type);
1041 var new_cell = this.insert_cell_below(cell_data.cell_type);
1042 new_cell.fromJSON(cell_data);
1042 new_cell.fromJSON(cell_data);
1043 };
1043 };
1044 };
1044 };
1045
1045
1046 // Split/merge
1046 // Split/merge
1047
1047
1048 /**
1048 /**
1049 * Split the selected cell into two, at the cursor.
1049 * Split the selected cell into two, at the cursor.
1050 *
1050 *
1051 * @method split_cell
1051 * @method split_cell
1052 */
1052 */
1053 Notebook.prototype.split_cell = function () {
1053 Notebook.prototype.split_cell = function () {
1054 var mdc = IPython.MarkdownCell;
1054 var mdc = IPython.MarkdownCell;
1055 var rc = IPython.RawCell;
1055 var rc = IPython.RawCell;
1056 var cell = this.get_selected_cell();
1056 var cell = this.get_selected_cell();
1057 if (cell.is_splittable()) {
1057 if (cell.is_splittable()) {
1058 var texta = cell.get_pre_cursor();
1058 var texta = cell.get_pre_cursor();
1059 var textb = cell.get_post_cursor();
1059 var textb = cell.get_post_cursor();
1060 if (cell instanceof IPython.CodeCell) {
1060 if (cell instanceof IPython.CodeCell) {
1061 // In this case the operations keep the notebook in its existing mode
1061 // In this case the operations keep the notebook in its existing mode
1062 // so we don't need to do any post-op mode changes.
1062 // so we don't need to do any post-op mode changes.
1063 cell.set_text(textb);
1063 cell.set_text(textb);
1064 var new_cell = this.insert_cell_above('code');
1064 var new_cell = this.insert_cell_above('code');
1065 new_cell.set_text(texta);
1065 new_cell.set_text(texta);
1066 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1066 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1067 // We know cell is !rendered so we can use set_text.
1067 // We know cell is !rendered so we can use set_text.
1068 cell.set_text(textb);
1068 cell.set_text(textb);
1069 var new_cell = this.insert_cell_above(cell.cell_type);
1069 var new_cell = this.insert_cell_above(cell.cell_type);
1070 // Unrender the new cell so we can call set_text.
1070 // Unrender the new cell so we can call set_text.
1071 new_cell.unrender();
1071 new_cell.unrender();
1072 new_cell.set_text(texta);
1072 new_cell.set_text(texta);
1073 }
1073 }
1074 };
1074 };
1075 };
1075 };
1076
1076
1077 /**
1077 /**
1078 * Combine the selected cell into the cell above it.
1078 * Combine the selected cell into the cell above it.
1079 *
1079 *
1080 * @method merge_cell_above
1080 * @method merge_cell_above
1081 */
1081 */
1082 Notebook.prototype.merge_cell_above = function () {
1082 Notebook.prototype.merge_cell_above = function () {
1083 var mdc = IPython.MarkdownCell;
1083 var mdc = IPython.MarkdownCell;
1084 var rc = IPython.RawCell;
1084 var rc = IPython.RawCell;
1085 var index = this.get_selected_index();
1085 var index = this.get_selected_index();
1086 var cell = this.get_cell(index);
1086 var cell = this.get_cell(index);
1087 var render = cell.rendered;
1087 var render = cell.rendered;
1088 if (!cell.is_mergeable()) {
1088 if (!cell.is_mergeable()) {
1089 return;
1089 return;
1090 }
1090 }
1091 if (index > 0) {
1091 if (index > 0) {
1092 var upper_cell = this.get_cell(index-1);
1092 var upper_cell = this.get_cell(index-1);
1093 if (!upper_cell.is_mergeable()) {
1093 if (!upper_cell.is_mergeable()) {
1094 return;
1094 return;
1095 }
1095 }
1096 var upper_text = upper_cell.get_text();
1096 var upper_text = upper_cell.get_text();
1097 var text = cell.get_text();
1097 var text = cell.get_text();
1098 if (cell instanceof IPython.CodeCell) {
1098 if (cell instanceof IPython.CodeCell) {
1099 cell.set_text(upper_text+'\n'+text);
1099 cell.set_text(upper_text+'\n'+text);
1100 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1100 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1101 cell.unrender(); // Must unrender before we set_text.
1101 cell.unrender(); // Must unrender before we set_text.
1102 cell.set_text(upper_text+'\n\n'+text);
1102 cell.set_text(upper_text+'\n\n'+text);
1103 if (render) {
1103 if (render) {
1104 // The rendered state of the final cell should match
1104 // The rendered state of the final cell should match
1105 // that of the original selected cell;
1105 // that of the original selected cell;
1106 cell.render();
1106 cell.render();
1107 }
1107 }
1108 };
1108 };
1109 this.delete_cell(index-1);
1109 this.delete_cell(index-1);
1110 this.select(this.find_cell_index(cell));
1110 this.select(this.find_cell_index(cell));
1111 };
1111 };
1112 };
1112 };
1113
1113
1114 /**
1114 /**
1115 * Combine the selected cell into the cell below it.
1115 * Combine the selected cell into the cell below it.
1116 *
1116 *
1117 * @method merge_cell_below
1117 * @method merge_cell_below
1118 */
1118 */
1119 Notebook.prototype.merge_cell_below = function () {
1119 Notebook.prototype.merge_cell_below = function () {
1120 var mdc = IPython.MarkdownCell;
1120 var mdc = IPython.MarkdownCell;
1121 var rc = IPython.RawCell;
1121 var rc = IPython.RawCell;
1122 var index = this.get_selected_index();
1122 var index = this.get_selected_index();
1123 var cell = this.get_cell(index);
1123 var cell = this.get_cell(index);
1124 var render = cell.rendered;
1124 var render = cell.rendered;
1125 if (!cell.is_mergeable()) {
1125 if (!cell.is_mergeable()) {
1126 return;
1126 return;
1127 }
1127 }
1128 if (index < this.ncells()-1) {
1128 if (index < this.ncells()-1) {
1129 var lower_cell = this.get_cell(index+1);
1129 var lower_cell = this.get_cell(index+1);
1130 if (!lower_cell.is_mergeable()) {
1130 if (!lower_cell.is_mergeable()) {
1131 return;
1131 return;
1132 }
1132 }
1133 var lower_text = lower_cell.get_text();
1133 var lower_text = lower_cell.get_text();
1134 var text = cell.get_text();
1134 var text = cell.get_text();
1135 if (cell instanceof IPython.CodeCell) {
1135 if (cell instanceof IPython.CodeCell) {
1136 cell.set_text(text+'\n'+lower_text);
1136 cell.set_text(text+'\n'+lower_text);
1137 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1137 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1138 cell.unrender(); // Must unrender before we set_text.
1138 cell.unrender(); // Must unrender before we set_text.
1139 cell.set_text(text+'\n\n'+lower_text);
1139 cell.set_text(text+'\n\n'+lower_text);
1140 if (render) {
1140 if (render) {
1141 // The rendered state of the final cell should match
1141 // The rendered state of the final cell should match
1142 // that of the original selected cell;
1142 // that of the original selected cell;
1143 cell.render();
1143 cell.render();
1144 }
1144 }
1145 };
1145 };
1146 this.delete_cell(index+1);
1146 this.delete_cell(index+1);
1147 this.select(this.find_cell_index(cell));
1147 this.select(this.find_cell_index(cell));
1148 };
1148 };
1149 };
1149 };
1150
1150
1151
1151
1152 // Cell collapsing and output clearing
1152 // Cell collapsing and output clearing
1153
1153
1154 /**
1154 /**
1155 * Hide a cell's output.
1155 * Hide a cell's output.
1156 *
1156 *
1157 * @method collapse
1157 * @method collapse
1158 * @param {Number} index A cell's numeric index
1158 * @param {Number} index A cell's numeric index
1159 */
1159 */
1160 Notebook.prototype.collapse = function (index) {
1160 Notebook.prototype.collapse = function (index) {
1161 var i = this.index_or_selected(index);
1161 var i = this.index_or_selected(index);
1162 this.get_cell(i).collapse();
1162 this.get_cell(i).collapse();
1163 this.set_dirty(true);
1163 this.set_dirty(true);
1164 };
1164 };
1165
1165
1166 /**
1166 /**
1167 * Show a cell's output.
1167 * Show a cell's output.
1168 *
1168 *
1169 * @method expand
1169 * @method expand
1170 * @param {Number} index A cell's numeric index
1170 * @param {Number} index A cell's numeric index
1171 */
1171 */
1172 Notebook.prototype.expand = function (index) {
1172 Notebook.prototype.expand = function (index) {
1173 var i = this.index_or_selected(index);
1173 var i = this.index_or_selected(index);
1174 this.get_cell(i).expand();
1174 this.get_cell(i).expand();
1175 this.set_dirty(true);
1175 this.set_dirty(true);
1176 };
1176 };
1177
1177
1178 /** Toggle whether a cell's output is collapsed or expanded.
1178 /** Toggle whether a cell's output is collapsed or expanded.
1179 *
1179 *
1180 * @method toggle_output
1180 * @method toggle_output
1181 * @param {Number} index A cell's numeric index
1181 * @param {Number} index A cell's numeric index
1182 */
1182 */
1183 Notebook.prototype.toggle_output = function (index) {
1183 Notebook.prototype.toggle_output = function (index) {
1184 var i = this.index_or_selected(index);
1184 var i = this.index_or_selected(index);
1185 this.get_cell(i).toggle_output();
1185 this.get_cell(i).toggle_output();
1186 this.set_dirty(true);
1186 this.set_dirty(true);
1187 };
1187 };
1188
1188
1189 /**
1189 /**
1190 * Toggle a scrollbar for long cell outputs.
1190 * Toggle a scrollbar for long cell outputs.
1191 *
1191 *
1192 * @method toggle_output_scroll
1192 * @method toggle_output_scroll
1193 * @param {Number} index A cell's numeric index
1193 * @param {Number} index A cell's numeric index
1194 */
1194 */
1195 Notebook.prototype.toggle_output_scroll = function (index) {
1195 Notebook.prototype.toggle_output_scroll = function (index) {
1196 var i = this.index_or_selected(index);
1196 var i = this.index_or_selected(index);
1197 this.get_cell(i).toggle_output_scroll();
1197 this.get_cell(i).toggle_output_scroll();
1198 };
1198 };
1199
1199
1200 /**
1200 /**
1201 * Hide each code cell's output area.
1201 * Hide each code cell's output area.
1202 *
1202 *
1203 * @method collapse_all_output
1203 * @method collapse_all_output
1204 */
1204 */
1205 Notebook.prototype.collapse_all_output = function () {
1205 Notebook.prototype.collapse_all_output = function () {
1206 var ncells = this.ncells();
1206 var ncells = this.ncells();
1207 var cells = this.get_cells();
1207 var cells = this.get_cells();
1208 for (var i=0; i<ncells; i++) {
1208 for (var i=0; i<ncells; i++) {
1209 if (cells[i] instanceof IPython.CodeCell) {
1209 if (cells[i] instanceof IPython.CodeCell) {
1210 cells[i].output_area.collapse();
1210 cells[i].output_area.collapse();
1211 }
1211 }
1212 };
1212 };
1213 // this should not be set if the `collapse` key is removed from nbformat
1213 // this should not be set if the `collapse` key is removed from nbformat
1214 this.set_dirty(true);
1214 this.set_dirty(true);
1215 };
1215 };
1216
1216
1217 /**
1217 /**
1218 * Expand each code cell's output area, and add a scrollbar for long output.
1218 * Expand each code cell's output area, and add a scrollbar for long output.
1219 *
1219 *
1220 * @method scroll_all_output
1220 * @method scroll_all_output
1221 */
1221 */
1222 Notebook.prototype.scroll_all_output = function () {
1222 Notebook.prototype.scroll_all_output = function () {
1223 var ncells = this.ncells();
1223 var ncells = this.ncells();
1224 var cells = this.get_cells();
1224 var cells = this.get_cells();
1225 for (var i=0; i<ncells; i++) {
1225 for (var i=0; i<ncells; i++) {
1226 if (cells[i] instanceof IPython.CodeCell) {
1226 if (cells[i] instanceof IPython.CodeCell) {
1227 cells[i].output_area.expand();
1227 cells[i].output_area.expand();
1228 cells[i].output_area.scroll_if_long();
1228 cells[i].output_area.scroll_if_long();
1229 }
1229 }
1230 };
1230 };
1231 // this should not be set if the `collapse` key is removed from nbformat
1231 // this should not be set if the `collapse` key is removed from nbformat
1232 this.set_dirty(true);
1232 this.set_dirty(true);
1233 };
1233 };
1234
1234
1235 /**
1235 /**
1236 * Expand each code cell's output area, and remove scrollbars.
1236 * Expand each code cell's output area, and remove scrollbars.
1237 *
1237 *
1238 * @method expand_all_output
1238 * @method expand_all_output
1239 */
1239 */
1240 Notebook.prototype.expand_all_output = function () {
1240 Notebook.prototype.expand_all_output = function () {
1241 var ncells = this.ncells();
1241 var ncells = this.ncells();
1242 var cells = this.get_cells();
1242 var cells = this.get_cells();
1243 for (var i=0; i<ncells; i++) {
1243 for (var i=0; i<ncells; i++) {
1244 if (cells[i] instanceof IPython.CodeCell) {
1244 if (cells[i] instanceof IPython.CodeCell) {
1245 cells[i].output_area.expand();
1245 cells[i].output_area.expand();
1246 cells[i].output_area.unscroll_area();
1246 cells[i].output_area.unscroll_area();
1247 }
1247 }
1248 };
1248 };
1249 // this should not be set if the `collapse` key is removed from nbformat
1249 // this should not be set if the `collapse` key is removed from nbformat
1250 this.set_dirty(true);
1250 this.set_dirty(true);
1251 };
1251 };
1252
1252
1253 /**
1253 /**
1254 * Clear each code cell's output area.
1254 * Clear each code cell's output area.
1255 *
1255 *
1256 * @method clear_all_output
1256 * @method clear_all_output
1257 */
1257 */
1258 Notebook.prototype.clear_all_output = function () {
1258 Notebook.prototype.clear_all_output = function () {
1259 var ncells = this.ncells();
1259 var ncells = this.ncells();
1260 var cells = this.get_cells();
1260 var cells = this.get_cells();
1261 for (var i=0; i<ncells; i++) {
1261 for (var i=0; i<ncells; i++) {
1262 if (cells[i] instanceof IPython.CodeCell) {
1262 if (cells[i] instanceof IPython.CodeCell) {
1263 cells[i].clear_output();
1263 cells[i].clear_output();
1264 // Make all In[] prompts blank, as well
1264 // Make all In[] prompts blank, as well
1265 // TODO: make this configurable (via checkbox?)
1265 // TODO: make this configurable (via checkbox?)
1266 cells[i].set_input_prompt();
1266 cells[i].set_input_prompt();
1267 }
1267 }
1268 };
1268 };
1269 this.set_dirty(true);
1269 this.set_dirty(true);
1270 };
1270 };
1271
1271
1272
1272
1273 // Other cell functions: line numbers, ...
1273 // Other cell functions: line numbers, ...
1274
1274
1275 /**
1275 /**
1276 * Toggle line numbers in the selected cell's input area.
1276 * Toggle line numbers in the selected cell's input area.
1277 *
1277 *
1278 * @method cell_toggle_line_numbers
1278 * @method cell_toggle_line_numbers
1279 */
1279 */
1280 Notebook.prototype.cell_toggle_line_numbers = function() {
1280 Notebook.prototype.cell_toggle_line_numbers = function() {
1281 this.get_selected_cell().toggle_line_numbers();
1281 this.get_selected_cell().toggle_line_numbers();
1282 };
1282 };
1283
1283
1284 // Session related things
1284 // Session related things
1285
1285
1286 /**
1286 /**
1287 * Start a new session and set it on each code cell.
1287 * Start a new session and set it on each code cell.
1288 *
1288 *
1289 * @method start_session
1289 * @method start_session
1290 */
1290 */
1291 Notebook.prototype.start_session = function () {
1291 Notebook.prototype.start_session = function () {
1292 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1292 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1293 this.session.start($.proxy(this._session_started, this));
1293 this.session.start($.proxy(this._session_started, this));
1294 };
1294 };
1295
1295
1296
1296
1297 /**
1297 /**
1298 * Once a session is started, link the code cells to the kernel
1298 * Once a session is started, link the code cells to the kernel
1299 *
1299 *
1300 */
1300 */
1301 Notebook.prototype._session_started = function(){
1301 Notebook.prototype._session_started = function(){
1302 this.kernel = this.session.kernel;
1302 this.kernel = this.session.kernel;
1303 var ncells = this.ncells();
1303 var ncells = this.ncells();
1304 for (var i=0; i<ncells; i++) {
1304 for (var i=0; i<ncells; i++) {
1305 var cell = this.get_cell(i);
1305 var cell = this.get_cell(i);
1306 if (cell instanceof IPython.CodeCell) {
1306 if (cell instanceof IPython.CodeCell) {
1307 cell.set_kernel(this.session.kernel);
1307 cell.set_kernel(this.session.kernel);
1308 };
1308 };
1309 };
1309 };
1310 };
1310 };
1311
1311
1312 /**
1312 /**
1313 * Prompt the user to restart the IPython kernel.
1313 * Prompt the user to restart the IPython kernel.
1314 *
1314 *
1315 * @method restart_kernel
1315 * @method restart_kernel
1316 */
1316 */
1317 Notebook.prototype.restart_kernel = function () {
1317 Notebook.prototype.restart_kernel = function () {
1318 var that = this;
1318 var that = this;
1319 IPython.dialog.modal({
1319 IPython.dialog.modal({
1320 title : "Restart kernel or continue running?",
1320 title : "Restart kernel or continue running?",
1321 body : $("<p/>").html(
1321 body : $("<p/>").text(
1322 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1322 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1323 ),
1323 ),
1324 buttons : {
1324 buttons : {
1325 "Continue running" : {},
1325 "Continue running" : {},
1326 "Restart" : {
1326 "Restart" : {
1327 "class" : "btn-danger",
1327 "class" : "btn-danger",
1328 "click" : function() {
1328 "click" : function() {
1329 that.session.restart_kernel();
1329 that.session.restart_kernel();
1330 }
1330 }
1331 }
1331 }
1332 }
1332 }
1333 });
1333 });
1334 };
1334 };
1335
1335
1336 /**
1336 /**
1337 * Execute or render cell outputs and go into command mode.
1337 * Execute or render cell outputs and go into command mode.
1338 *
1338 *
1339 * @method execute_cell
1339 * @method execute_cell
1340 */
1340 */
1341 Notebook.prototype.execute_cell = function () {
1341 Notebook.prototype.execute_cell = function () {
1342 // mode = shift, ctrl, alt
1342 // mode = shift, ctrl, alt
1343 var cell = this.get_selected_cell();
1343 var cell = this.get_selected_cell();
1344 var cell_index = this.find_cell_index(cell);
1344 var cell_index = this.find_cell_index(cell);
1345
1345
1346 cell.execute();
1346 cell.execute();
1347 this.command_mode();
1347 this.command_mode();
1348 cell.focus_cell();
1348 cell.focus_cell();
1349 this.set_dirty(true);
1349 this.set_dirty(true);
1350 }
1350 }
1351
1351
1352 /**
1352 /**
1353 * Execute or render cell outputs and insert a new cell below.
1353 * Execute or render cell outputs and insert a new cell below.
1354 *
1354 *
1355 * @method execute_cell_and_insert_below
1355 * @method execute_cell_and_insert_below
1356 */
1356 */
1357 Notebook.prototype.execute_cell_and_insert_below = function () {
1357 Notebook.prototype.execute_cell_and_insert_below = function () {
1358 var cell = this.get_selected_cell();
1358 var cell = this.get_selected_cell();
1359 var cell_index = this.find_cell_index(cell);
1359 var cell_index = this.find_cell_index(cell);
1360
1360
1361 cell.execute();
1361 cell.execute();
1362
1362
1363 // If we are at the end always insert a new cell and return
1363 // If we are at the end always insert a new cell and return
1364 if (cell_index === (this.ncells()-1)) {
1364 if (cell_index === (this.ncells()-1)) {
1365 this.insert_cell_below('code');
1365 this.insert_cell_below('code');
1366 this.select(cell_index+1);
1366 this.select(cell_index+1);
1367 this.edit_mode();
1367 this.edit_mode();
1368 this.scroll_to_bottom();
1368 this.scroll_to_bottom();
1369 this.set_dirty(true);
1369 this.set_dirty(true);
1370 return;
1370 return;
1371 }
1371 }
1372
1372
1373 // Only insert a new cell, if we ended up in an already populated cell
1373 // Only insert a new cell, if we ended up in an already populated cell
1374 var next_text = this.get_cell(cell_index+1).get_text();
1374 var next_text = this.get_cell(cell_index+1).get_text();
1375 if (/\S/.test(next_text) === true) {
1375 if (/\S/.test(next_text) === true) {
1376 this.insert_cell_below('code');
1376 this.insert_cell_below('code');
1377 }
1377 }
1378 this.select(cell_index+1);
1378 this.select(cell_index+1);
1379 this.edit_mode();
1379 this.edit_mode();
1380 this.set_dirty(true);
1380 this.set_dirty(true);
1381 };
1381 };
1382
1382
1383 /**
1383 /**
1384 * Execute or render cell outputs and select the next cell.
1384 * Execute or render cell outputs and select the next cell.
1385 *
1385 *
1386 * @method execute_cell_and_select_below
1386 * @method execute_cell_and_select_below
1387 */
1387 */
1388 Notebook.prototype.execute_cell_and_select_below = function () {
1388 Notebook.prototype.execute_cell_and_select_below = function () {
1389
1389
1390 var cell = this.get_selected_cell();
1390 var cell = this.get_selected_cell();
1391 var cell_index = this.find_cell_index(cell);
1391 var cell_index = this.find_cell_index(cell);
1392
1392
1393 cell.execute();
1393 cell.execute();
1394
1394
1395 // If we are at the end always insert a new cell and return
1395 // If we are at the end always insert a new cell and return
1396 if (cell_index === (this.ncells()-1)) {
1396 if (cell_index === (this.ncells()-1)) {
1397 this.insert_cell_below('code');
1397 this.insert_cell_below('code');
1398 this.select(cell_index+1);
1398 this.select(cell_index+1);
1399 this.edit_mode();
1399 this.edit_mode();
1400 this.scroll_to_bottom();
1400 this.scroll_to_bottom();
1401 this.set_dirty(true);
1401 this.set_dirty(true);
1402 return;
1402 return;
1403 }
1403 }
1404
1404
1405 this.select(cell_index+1);
1405 this.select(cell_index+1);
1406 this.get_cell(cell_index+1).focus_cell();
1406 this.get_cell(cell_index+1).focus_cell();
1407 this.set_dirty(true);
1407 this.set_dirty(true);
1408 };
1408 };
1409
1409
1410 /**
1410 /**
1411 * Execute all cells below the selected cell.
1411 * Execute all cells below the selected cell.
1412 *
1412 *
1413 * @method execute_cells_below
1413 * @method execute_cells_below
1414 */
1414 */
1415 Notebook.prototype.execute_cells_below = function () {
1415 Notebook.prototype.execute_cells_below = function () {
1416 this.execute_cell_range(this.get_selected_index(), this.ncells());
1416 this.execute_cell_range(this.get_selected_index(), this.ncells());
1417 this.scroll_to_bottom();
1417 this.scroll_to_bottom();
1418 };
1418 };
1419
1419
1420 /**
1420 /**
1421 * Execute all cells above the selected cell.
1421 * Execute all cells above the selected cell.
1422 *
1422 *
1423 * @method execute_cells_above
1423 * @method execute_cells_above
1424 */
1424 */
1425 Notebook.prototype.execute_cells_above = function () {
1425 Notebook.prototype.execute_cells_above = function () {
1426 this.execute_cell_range(0, this.get_selected_index());
1426 this.execute_cell_range(0, this.get_selected_index());
1427 };
1427 };
1428
1428
1429 /**
1429 /**
1430 * Execute all cells.
1430 * Execute all cells.
1431 *
1431 *
1432 * @method execute_all_cells
1432 * @method execute_all_cells
1433 */
1433 */
1434 Notebook.prototype.execute_all_cells = function () {
1434 Notebook.prototype.execute_all_cells = function () {
1435 this.execute_cell_range(0, this.ncells());
1435 this.execute_cell_range(0, this.ncells());
1436 this.scroll_to_bottom();
1436 this.scroll_to_bottom();
1437 };
1437 };
1438
1438
1439 /**
1439 /**
1440 * Execute a contiguous range of cells.
1440 * Execute a contiguous range of cells.
1441 *
1441 *
1442 * @method execute_cell_range
1442 * @method execute_cell_range
1443 * @param {Number} start Index of the first cell to execute (inclusive)
1443 * @param {Number} start Index of the first cell to execute (inclusive)
1444 * @param {Number} end Index of the last cell to execute (exclusive)
1444 * @param {Number} end Index of the last cell to execute (exclusive)
1445 */
1445 */
1446 Notebook.prototype.execute_cell_range = function (start, end) {
1446 Notebook.prototype.execute_cell_range = function (start, end) {
1447 for (var i=start; i<end; i++) {
1447 for (var i=start; i<end; i++) {
1448 this.select(i);
1448 this.select(i);
1449 this.execute_cell();
1449 this.execute_cell();
1450 };
1450 };
1451 };
1451 };
1452
1452
1453 // Persistance and loading
1453 // Persistance and loading
1454
1454
1455 /**
1455 /**
1456 * Getter method for this notebook's name.
1456 * Getter method for this notebook's name.
1457 *
1457 *
1458 * @method get_notebook_name
1458 * @method get_notebook_name
1459 * @return {String} This notebook's name
1459 * @return {String} This notebook's name
1460 */
1460 */
1461 Notebook.prototype.get_notebook_name = function () {
1461 Notebook.prototype.get_notebook_name = function () {
1462 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1462 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1463 return nbname;
1463 return nbname;
1464 };
1464 };
1465
1465
1466 /**
1466 /**
1467 * Setter method for this notebook's name.
1467 * Setter method for this notebook's name.
1468 *
1468 *
1469 * @method set_notebook_name
1469 * @method set_notebook_name
1470 * @param {String} name A new name for this notebook
1470 * @param {String} name A new name for this notebook
1471 */
1471 */
1472 Notebook.prototype.set_notebook_name = function (name) {
1472 Notebook.prototype.set_notebook_name = function (name) {
1473 this.notebook_name = name;
1473 this.notebook_name = name;
1474 };
1474 };
1475
1475
1476 /**
1476 /**
1477 * Check that a notebook's name is valid.
1477 * Check that a notebook's name is valid.
1478 *
1478 *
1479 * @method test_notebook_name
1479 * @method test_notebook_name
1480 * @param {String} nbname A name for this notebook
1480 * @param {String} nbname A name for this notebook
1481 * @return {Boolean} True if the name is valid, false if invalid
1481 * @return {Boolean} True if the name is valid, false if invalid
1482 */
1482 */
1483 Notebook.prototype.test_notebook_name = function (nbname) {
1483 Notebook.prototype.test_notebook_name = function (nbname) {
1484 nbname = nbname || '';
1484 nbname = nbname || '';
1485 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1485 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1486 return true;
1486 return true;
1487 } else {
1487 } else {
1488 return false;
1488 return false;
1489 };
1489 };
1490 };
1490 };
1491
1491
1492 /**
1492 /**
1493 * Load a notebook from JSON (.ipynb).
1493 * Load a notebook from JSON (.ipynb).
1494 *
1494 *
1495 * This currently handles one worksheet: others are deleted.
1495 * This currently handles one worksheet: others are deleted.
1496 *
1496 *
1497 * @method fromJSON
1497 * @method fromJSON
1498 * @param {Object} data JSON representation of a notebook
1498 * @param {Object} data JSON representation of a notebook
1499 */
1499 */
1500 Notebook.prototype.fromJSON = function (data) {
1500 Notebook.prototype.fromJSON = function (data) {
1501 var content = data.content;
1501 var content = data.content;
1502 var ncells = this.ncells();
1502 var ncells = this.ncells();
1503 var i;
1503 var i;
1504 for (i=0; i<ncells; i++) {
1504 for (i=0; i<ncells; i++) {
1505 // Always delete cell 0 as they get renumbered as they are deleted.
1505 // Always delete cell 0 as they get renumbered as they are deleted.
1506 this.delete_cell(0);
1506 this.delete_cell(0);
1507 };
1507 };
1508 // Save the metadata and name.
1508 // Save the metadata and name.
1509 this.metadata = content.metadata;
1509 this.metadata = content.metadata;
1510 this.notebook_name = data.name;
1510 this.notebook_name = data.name;
1511 // Only handle 1 worksheet for now.
1511 // Only handle 1 worksheet for now.
1512 var worksheet = content.worksheets[0];
1512 var worksheet = content.worksheets[0];
1513 if (worksheet !== undefined) {
1513 if (worksheet !== undefined) {
1514 if (worksheet.metadata) {
1514 if (worksheet.metadata) {
1515 this.worksheet_metadata = worksheet.metadata;
1515 this.worksheet_metadata = worksheet.metadata;
1516 }
1516 }
1517 var new_cells = worksheet.cells;
1517 var new_cells = worksheet.cells;
1518 ncells = new_cells.length;
1518 ncells = new_cells.length;
1519 var cell_data = null;
1519 var cell_data = null;
1520 var new_cell = null;
1520 var new_cell = null;
1521 for (i=0; i<ncells; i++) {
1521 for (i=0; i<ncells; i++) {
1522 cell_data = new_cells[i];
1522 cell_data = new_cells[i];
1523 // VERSIONHACK: plaintext -> raw
1523 // VERSIONHACK: plaintext -> raw
1524 // handle never-released plaintext name for raw cells
1524 // handle never-released plaintext name for raw cells
1525 if (cell_data.cell_type === 'plaintext'){
1525 if (cell_data.cell_type === 'plaintext'){
1526 cell_data.cell_type = 'raw';
1526 cell_data.cell_type = 'raw';
1527 }
1527 }
1528
1528
1529 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1529 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1530 new_cell.fromJSON(cell_data);
1530 new_cell.fromJSON(cell_data);
1531 };
1531 };
1532 };
1532 };
1533 if (content.worksheets.length > 1) {
1533 if (content.worksheets.length > 1) {
1534 IPython.dialog.modal({
1534 IPython.dialog.modal({
1535 title : "Multiple worksheets",
1535 title : "Multiple worksheets",
1536 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1536 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1537 "but this version of IPython can only handle the first. " +
1537 "but this version of IPython can only handle the first. " +
1538 "If you save this notebook, worksheets after the first will be lost.",
1538 "If you save this notebook, worksheets after the first will be lost.",
1539 buttons : {
1539 buttons : {
1540 OK : {
1540 OK : {
1541 class : "btn-danger"
1541 class : "btn-danger"
1542 }
1542 }
1543 }
1543 }
1544 });
1544 });
1545 }
1545 }
1546 };
1546 };
1547
1547
1548 /**
1548 /**
1549 * Dump this notebook into a JSON-friendly object.
1549 * Dump this notebook into a JSON-friendly object.
1550 *
1550 *
1551 * @method toJSON
1551 * @method toJSON
1552 * @return {Object} A JSON-friendly representation of this notebook.
1552 * @return {Object} A JSON-friendly representation of this notebook.
1553 */
1553 */
1554 Notebook.prototype.toJSON = function () {
1554 Notebook.prototype.toJSON = function () {
1555 var cells = this.get_cells();
1555 var cells = this.get_cells();
1556 var ncells = cells.length;
1556 var ncells = cells.length;
1557 var cell_array = new Array(ncells);
1557 var cell_array = new Array(ncells);
1558 for (var i=0; i<ncells; i++) {
1558 for (var i=0; i<ncells; i++) {
1559 cell_array[i] = cells[i].toJSON();
1559 cell_array[i] = cells[i].toJSON();
1560 };
1560 };
1561 var data = {
1561 var data = {
1562 // Only handle 1 worksheet for now.
1562 // Only handle 1 worksheet for now.
1563 worksheets : [{
1563 worksheets : [{
1564 cells: cell_array,
1564 cells: cell_array,
1565 metadata: this.worksheet_metadata
1565 metadata: this.worksheet_metadata
1566 }],
1566 }],
1567 metadata : this.metadata
1567 metadata : this.metadata
1568 };
1568 };
1569 return data;
1569 return data;
1570 };
1570 };
1571
1571
1572 /**
1572 /**
1573 * Start an autosave timer, for periodically saving the notebook.
1573 * Start an autosave timer, for periodically saving the notebook.
1574 *
1574 *
1575 * @method set_autosave_interval
1575 * @method set_autosave_interval
1576 * @param {Integer} interval the autosave interval in milliseconds
1576 * @param {Integer} interval the autosave interval in milliseconds
1577 */
1577 */
1578 Notebook.prototype.set_autosave_interval = function (interval) {
1578 Notebook.prototype.set_autosave_interval = function (interval) {
1579 var that = this;
1579 var that = this;
1580 // clear previous interval, so we don't get simultaneous timers
1580 // clear previous interval, so we don't get simultaneous timers
1581 if (this.autosave_timer) {
1581 if (this.autosave_timer) {
1582 clearInterval(this.autosave_timer);
1582 clearInterval(this.autosave_timer);
1583 }
1583 }
1584
1584
1585 this.autosave_interval = this.minimum_autosave_interval = interval;
1585 this.autosave_interval = this.minimum_autosave_interval = interval;
1586 if (interval) {
1586 if (interval) {
1587 this.autosave_timer = setInterval(function() {
1587 this.autosave_timer = setInterval(function() {
1588 if (that.dirty) {
1588 if (that.dirty) {
1589 that.save_notebook();
1589 that.save_notebook();
1590 }
1590 }
1591 }, interval);
1591 }, interval);
1592 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1592 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1593 } else {
1593 } else {
1594 this.autosave_timer = null;
1594 this.autosave_timer = null;
1595 $([IPython.events]).trigger("autosave_disabled.Notebook");
1595 $([IPython.events]).trigger("autosave_disabled.Notebook");
1596 };
1596 };
1597 };
1597 };
1598
1598
1599 /**
1599 /**
1600 * Save this notebook on the server.
1600 * Save this notebook on the server.
1601 *
1601 *
1602 * @method save_notebook
1602 * @method save_notebook
1603 */
1603 */
1604 Notebook.prototype.save_notebook = function (extra_settings) {
1604 Notebook.prototype.save_notebook = function (extra_settings) {
1605 // Create a JSON model to be sent to the server.
1605 // Create a JSON model to be sent to the server.
1606 var model = {};
1606 var model = {};
1607 model.name = this.notebook_name;
1607 model.name = this.notebook_name;
1608 model.path = this.notebook_path;
1608 model.path = this.notebook_path;
1609 model.content = this.toJSON();
1609 model.content = this.toJSON();
1610 model.content.nbformat = this.nbformat;
1610 model.content.nbformat = this.nbformat;
1611 model.content.nbformat_minor = this.nbformat_minor;
1611 model.content.nbformat_minor = this.nbformat_minor;
1612 // time the ajax call for autosave tuning purposes.
1612 // time the ajax call for autosave tuning purposes.
1613 var start = new Date().getTime();
1613 var start = new Date().getTime();
1614 // We do the call with settings so we can set cache to false.
1614 // We do the call with settings so we can set cache to false.
1615 var settings = {
1615 var settings = {
1616 processData : false,
1616 processData : false,
1617 cache : false,
1617 cache : false,
1618 type : "PUT",
1618 type : "PUT",
1619 data : JSON.stringify(model),
1619 data : JSON.stringify(model),
1620 headers : {'Content-Type': 'application/json'},
1620 headers : {'Content-Type': 'application/json'},
1621 success : $.proxy(this.save_notebook_success, this, start),
1621 success : $.proxy(this.save_notebook_success, this, start),
1622 error : $.proxy(this.save_notebook_error, this)
1622 error : $.proxy(this.save_notebook_error, this)
1623 };
1623 };
1624 if (extra_settings) {
1624 if (extra_settings) {
1625 for (var key in extra_settings) {
1625 for (var key in extra_settings) {
1626 settings[key] = extra_settings[key];
1626 settings[key] = extra_settings[key];
1627 }
1627 }
1628 }
1628 }
1629 $([IPython.events]).trigger('notebook_saving.Notebook');
1629 $([IPython.events]).trigger('notebook_saving.Notebook');
1630 var url = utils.url_join_encode(
1630 var url = utils.url_join_encode(
1631 this._baseProjectUrl,
1631 this._baseProjectUrl,
1632 'api/notebooks',
1632 'api/notebooks',
1633 this.notebook_path,
1633 this.notebook_path,
1634 this.notebook_name
1634 this.notebook_name
1635 );
1635 );
1636 $.ajax(url, settings);
1636 $.ajax(url, settings);
1637 };
1637 };
1638
1638
1639 /**
1639 /**
1640 * Success callback for saving a notebook.
1640 * Success callback for saving a notebook.
1641 *
1641 *
1642 * @method save_notebook_success
1642 * @method save_notebook_success
1643 * @param {Integer} start the time when the save request started
1643 * @param {Integer} start the time when the save request started
1644 * @param {Object} data JSON representation of a notebook
1644 * @param {Object} data JSON representation of a notebook
1645 * @param {String} status Description of response status
1645 * @param {String} status Description of response status
1646 * @param {jqXHR} xhr jQuery Ajax object
1646 * @param {jqXHR} xhr jQuery Ajax object
1647 */
1647 */
1648 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1648 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1649 this.set_dirty(false);
1649 this.set_dirty(false);
1650 $([IPython.events]).trigger('notebook_saved.Notebook');
1650 $([IPython.events]).trigger('notebook_saved.Notebook');
1651 this._update_autosave_interval(start);
1651 this._update_autosave_interval(start);
1652 if (this._checkpoint_after_save) {
1652 if (this._checkpoint_after_save) {
1653 this.create_checkpoint();
1653 this.create_checkpoint();
1654 this._checkpoint_after_save = false;
1654 this._checkpoint_after_save = false;
1655 };
1655 };
1656 };
1656 };
1657
1657
1658 /**
1658 /**
1659 * update the autosave interval based on how long the last save took
1659 * update the autosave interval based on how long the last save took
1660 *
1660 *
1661 * @method _update_autosave_interval
1661 * @method _update_autosave_interval
1662 * @param {Integer} timestamp when the save request started
1662 * @param {Integer} timestamp when the save request started
1663 */
1663 */
1664 Notebook.prototype._update_autosave_interval = function (start) {
1664 Notebook.prototype._update_autosave_interval = function (start) {
1665 var duration = (new Date().getTime() - start);
1665 var duration = (new Date().getTime() - start);
1666 if (this.autosave_interval) {
1666 if (this.autosave_interval) {
1667 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1667 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1668 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1668 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1669 // round to 10 seconds, otherwise we will be setting a new interval too often
1669 // round to 10 seconds, otherwise we will be setting a new interval too often
1670 interval = 10000 * Math.round(interval / 10000);
1670 interval = 10000 * Math.round(interval / 10000);
1671 // set new interval, if it's changed
1671 // set new interval, if it's changed
1672 if (interval != this.autosave_interval) {
1672 if (interval != this.autosave_interval) {
1673 this.set_autosave_interval(interval);
1673 this.set_autosave_interval(interval);
1674 }
1674 }
1675 }
1675 }
1676 };
1676 };
1677
1677
1678 /**
1678 /**
1679 * Failure callback for saving a notebook.
1679 * Failure callback for saving a notebook.
1680 *
1680 *
1681 * @method save_notebook_error
1681 * @method save_notebook_error
1682 * @param {jqXHR} xhr jQuery Ajax object
1682 * @param {jqXHR} xhr jQuery Ajax object
1683 * @param {String} status Description of response status
1683 * @param {String} status Description of response status
1684 * @param {String} error HTTP error message
1684 * @param {String} error HTTP error message
1685 */
1685 */
1686 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1686 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1687 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1687 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1688 };
1688 };
1689
1689
1690 Notebook.prototype.new_notebook = function(){
1690 Notebook.prototype.new_notebook = function(){
1691 var path = this.notebook_path;
1691 var path = this.notebook_path;
1692 var base_project_url = this._baseProjectUrl;
1692 var base_project_url = this._baseProjectUrl;
1693 var settings = {
1693 var settings = {
1694 processData : false,
1694 processData : false,
1695 cache : false,
1695 cache : false,
1696 type : "POST",
1696 type : "POST",
1697 dataType : "json",
1697 dataType : "json",
1698 async : false,
1698 async : false,
1699 success : function (data, status, xhr){
1699 success : function (data, status, xhr){
1700 var notebook_name = data.name;
1700 var notebook_name = data.name;
1701 window.open(
1701 window.open(
1702 utils.url_join_encode(
1702 utils.url_join_encode(
1703 base_project_url,
1703 base_project_url,
1704 'notebooks',
1704 'notebooks',
1705 path,
1705 path,
1706 notebook_name
1706 notebook_name
1707 ),
1707 ),
1708 '_blank'
1708 '_blank'
1709 );
1709 );
1710 }
1710 }
1711 };
1711 };
1712 var url = utils.url_join_encode(
1712 var url = utils.url_join_encode(
1713 base_project_url,
1713 base_project_url,
1714 'api/notebooks',
1714 'api/notebooks',
1715 path
1715 path
1716 );
1716 );
1717 $.ajax(url,settings);
1717 $.ajax(url,settings);
1718 };
1718 };
1719
1719
1720
1720
1721 Notebook.prototype.copy_notebook = function(){
1721 Notebook.prototype.copy_notebook = function(){
1722 var path = this.notebook_path;
1722 var path = this.notebook_path;
1723 var base_project_url = this._baseProjectUrl;
1723 var base_project_url = this._baseProjectUrl;
1724 var settings = {
1724 var settings = {
1725 processData : false,
1725 processData : false,
1726 cache : false,
1726 cache : false,
1727 type : "POST",
1727 type : "POST",
1728 dataType : "json",
1728 dataType : "json",
1729 data : JSON.stringify({copy_from : this.notebook_name}),
1729 data : JSON.stringify({copy_from : this.notebook_name}),
1730 async : false,
1730 async : false,
1731 success : function (data, status, xhr) {
1731 success : function (data, status, xhr) {
1732 window.open(utils.url_join_encode(
1732 window.open(utils.url_join_encode(
1733 base_project_url,
1733 base_project_url,
1734 'notebooks',
1734 'notebooks',
1735 data.path,
1735 data.path,
1736 data.name
1736 data.name
1737 ), '_blank');
1737 ), '_blank');
1738 }
1738 }
1739 };
1739 };
1740 var url = utils.url_join_encode(
1740 var url = utils.url_join_encode(
1741 base_project_url,
1741 base_project_url,
1742 'api/notebooks',
1742 'api/notebooks',
1743 path
1743 path
1744 );
1744 );
1745 $.ajax(url,settings);
1745 $.ajax(url,settings);
1746 };
1746 };
1747
1747
1748 Notebook.prototype.rename = function (nbname) {
1748 Notebook.prototype.rename = function (nbname) {
1749 var that = this;
1749 var that = this;
1750 var data = {name: nbname + '.ipynb'};
1750 var data = {name: nbname + '.ipynb'};
1751 var settings = {
1751 var settings = {
1752 processData : false,
1752 processData : false,
1753 cache : false,
1753 cache : false,
1754 type : "PATCH",
1754 type : "PATCH",
1755 data : JSON.stringify(data),
1755 data : JSON.stringify(data),
1756 dataType: "json",
1756 dataType: "json",
1757 headers : {'Content-Type': 'application/json'},
1757 headers : {'Content-Type': 'application/json'},
1758 success : $.proxy(that.rename_success, this),
1758 success : $.proxy(that.rename_success, this),
1759 error : $.proxy(that.rename_error, this)
1759 error : $.proxy(that.rename_error, this)
1760 };
1760 };
1761 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1761 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1762 var url = utils.url_join_encode(
1762 var url = utils.url_join_encode(
1763 this._baseProjectUrl,
1763 this._baseProjectUrl,
1764 'api/notebooks',
1764 'api/notebooks',
1765 this.notebook_path,
1765 this.notebook_path,
1766 this.notebook_name
1766 this.notebook_name
1767 );
1767 );
1768 $.ajax(url, settings);
1768 $.ajax(url, settings);
1769 };
1769 };
1770
1770
1771
1771
1772 Notebook.prototype.rename_success = function (json, status, xhr) {
1772 Notebook.prototype.rename_success = function (json, status, xhr) {
1773 this.notebook_name = json.name;
1773 this.notebook_name = json.name;
1774 var name = this.notebook_name;
1774 var name = this.notebook_name;
1775 var path = json.path;
1775 var path = json.path;
1776 this.session.rename_notebook(name, path);
1776 this.session.rename_notebook(name, path);
1777 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1777 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1778 }
1778 }
1779
1779
1780 Notebook.prototype.rename_error = function (xhr, status, error) {
1780 Notebook.prototype.rename_error = function (xhr, status, error) {
1781 var that = this;
1781 var that = this;
1782 var dialog = $('<div/>').append(
1782 var dialog = $('<div/>').append(
1783 $("<p/>").addClass("rename-message")
1783 $("<p/>").addClass("rename-message")
1784 .html('This notebook name already exists.')
1784 .text('This notebook name already exists.')
1785 )
1785 )
1786 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1786 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1787 IPython.dialog.modal({
1787 IPython.dialog.modal({
1788 title: "Notebook Rename Error!",
1788 title: "Notebook Rename Error!",
1789 body: dialog,
1789 body: dialog,
1790 buttons : {
1790 buttons : {
1791 "Cancel": {},
1791 "Cancel": {},
1792 "OK": {
1792 "OK": {
1793 class: "btn-primary",
1793 class: "btn-primary",
1794 click: function () {
1794 click: function () {
1795 IPython.save_widget.rename_notebook();
1795 IPython.save_widget.rename_notebook();
1796 }}
1796 }}
1797 },
1797 },
1798 open : function (event, ui) {
1798 open : function (event, ui) {
1799 var that = $(this);
1799 var that = $(this);
1800 // Upon ENTER, click the OK button.
1800 // Upon ENTER, click the OK button.
1801 that.find('input[type="text"]').keydown(function (event, ui) {
1801 that.find('input[type="text"]').keydown(function (event, ui) {
1802 if (event.which === utils.keycodes.ENTER) {
1802 if (event.which === utils.keycodes.ENTER) {
1803 that.find('.btn-primary').first().click();
1803 that.find('.btn-primary').first().click();
1804 }
1804 }
1805 });
1805 });
1806 that.find('input[type="text"]').focus();
1806 that.find('input[type="text"]').focus();
1807 }
1807 }
1808 });
1808 });
1809 }
1809 }
1810
1810
1811 /**
1811 /**
1812 * Request a notebook's data from the server.
1812 * Request a notebook's data from the server.
1813 *
1813 *
1814 * @method load_notebook
1814 * @method load_notebook
1815 * @param {String} notebook_name and path A notebook to load
1815 * @param {String} notebook_name and path A notebook to load
1816 */
1816 */
1817 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1817 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1818 var that = this;
1818 var that = this;
1819 this.notebook_name = notebook_name;
1819 this.notebook_name = notebook_name;
1820 this.notebook_path = notebook_path;
1820 this.notebook_path = notebook_path;
1821 // We do the call with settings so we can set cache to false.
1821 // We do the call with settings so we can set cache to false.
1822 var settings = {
1822 var settings = {
1823 processData : false,
1823 processData : false,
1824 cache : false,
1824 cache : false,
1825 type : "GET",
1825 type : "GET",
1826 dataType : "json",
1826 dataType : "json",
1827 success : $.proxy(this.load_notebook_success,this),
1827 success : $.proxy(this.load_notebook_success,this),
1828 error : $.proxy(this.load_notebook_error,this),
1828 error : $.proxy(this.load_notebook_error,this),
1829 };
1829 };
1830 $([IPython.events]).trigger('notebook_loading.Notebook');
1830 $([IPython.events]).trigger('notebook_loading.Notebook');
1831 var url = utils.url_join_encode(
1831 var url = utils.url_join_encode(
1832 this._baseProjectUrl,
1832 this._baseProjectUrl,
1833 'api/notebooks',
1833 'api/notebooks',
1834 this.notebook_path,
1834 this.notebook_path,
1835 this.notebook_name
1835 this.notebook_name
1836 );
1836 );
1837 $.ajax(url, settings);
1837 $.ajax(url, settings);
1838 };
1838 };
1839
1839
1840 /**
1840 /**
1841 * Success callback for loading a notebook from the server.
1841 * Success callback for loading a notebook from the server.
1842 *
1842 *
1843 * Load notebook data from the JSON response.
1843 * Load notebook data from the JSON response.
1844 *
1844 *
1845 * @method load_notebook_success
1845 * @method load_notebook_success
1846 * @param {Object} data JSON representation of a notebook
1846 * @param {Object} data JSON representation of a notebook
1847 * @param {String} status Description of response status
1847 * @param {String} status Description of response status
1848 * @param {jqXHR} xhr jQuery Ajax object
1848 * @param {jqXHR} xhr jQuery Ajax object
1849 */
1849 */
1850 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1850 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1851 this.fromJSON(data);
1851 this.fromJSON(data);
1852 if (this.ncells() === 0) {
1852 if (this.ncells() === 0) {
1853 this.insert_cell_below('code');
1853 this.insert_cell_below('code');
1854 this.select(0);
1854 this.select(0);
1855 this.edit_mode();
1855 this.edit_mode();
1856 } else {
1856 } else {
1857 this.select(0);
1857 this.select(0);
1858 this.command_mode();
1858 this.command_mode();
1859 };
1859 };
1860 this.set_dirty(false);
1860 this.set_dirty(false);
1861 this.scroll_to_top();
1861 this.scroll_to_top();
1862 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1862 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1863 var msg = "This notebook has been converted from an older " +
1863 var msg = "This notebook has been converted from an older " +
1864 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1864 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1865 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1865 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1866 "newer notebook format will be used and older versions of IPython " +
1866 "newer notebook format will be used and older versions of IPython " +
1867 "may not be able to read it. To keep the older version, close the " +
1867 "may not be able to read it. To keep the older version, close the " +
1868 "notebook without saving it.";
1868 "notebook without saving it.";
1869 IPython.dialog.modal({
1869 IPython.dialog.modal({
1870 title : "Notebook converted",
1870 title : "Notebook converted",
1871 body : msg,
1871 body : msg,
1872 buttons : {
1872 buttons : {
1873 OK : {
1873 OK : {
1874 class : "btn-primary"
1874 class : "btn-primary"
1875 }
1875 }
1876 }
1876 }
1877 });
1877 });
1878 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1878 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1879 var that = this;
1879 var that = this;
1880 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1880 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1881 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1881 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1882 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1882 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1883 this_vs + ". You can still work with this notebook, but some features " +
1883 this_vs + ". You can still work with this notebook, but some features " +
1884 "introduced in later notebook versions may not be available."
1884 "introduced in later notebook versions may not be available."
1885
1885
1886 IPython.dialog.modal({
1886 IPython.dialog.modal({
1887 title : "Newer Notebook",
1887 title : "Newer Notebook",
1888 body : msg,
1888 body : msg,
1889 buttons : {
1889 buttons : {
1890 OK : {
1890 OK : {
1891 class : "btn-danger"
1891 class : "btn-danger"
1892 }
1892 }
1893 }
1893 }
1894 });
1894 });
1895
1895
1896 }
1896 }
1897
1897
1898 // Create the session after the notebook is completely loaded to prevent
1898 // Create the session after the notebook is completely loaded to prevent
1899 // code execution upon loading, which is a security risk.
1899 // code execution upon loading, which is a security risk.
1900 if (this.session == null) {
1900 if (this.session == null) {
1901 this.start_session();
1901 this.start_session();
1902 }
1902 }
1903 // load our checkpoint list
1903 // load our checkpoint list
1904 this.list_checkpoints();
1904 this.list_checkpoints();
1905
1905
1906 // load toolbar state
1906 // load toolbar state
1907 if (this.metadata.celltoolbar) {
1907 if (this.metadata.celltoolbar) {
1908 IPython.CellToolbar.global_show();
1908 IPython.CellToolbar.global_show();
1909 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1909 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1910 }
1910 }
1911
1911
1912 $([IPython.events]).trigger('notebook_loaded.Notebook');
1912 $([IPython.events]).trigger('notebook_loaded.Notebook');
1913 };
1913 };
1914
1914
1915 /**
1915 /**
1916 * Failure callback for loading a notebook from the server.
1916 * Failure callback for loading a notebook from the server.
1917 *
1917 *
1918 * @method load_notebook_error
1918 * @method load_notebook_error
1919 * @param {jqXHR} xhr jQuery Ajax object
1919 * @param {jqXHR} xhr jQuery Ajax object
1920 * @param {String} status Description of response status
1920 * @param {String} status Description of response status
1921 * @param {String} error HTTP error message
1921 * @param {String} error HTTP error message
1922 */
1922 */
1923 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1923 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1924 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1924 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1925 if (xhr.status === 400) {
1925 if (xhr.status === 400) {
1926 var msg = error;
1926 var msg = error;
1927 } else if (xhr.status === 500) {
1927 } else if (xhr.status === 500) {
1928 var msg = "An unknown error occurred while loading this notebook. " +
1928 var msg = "An unknown error occurred while loading this notebook. " +
1929 "This version can load notebook formats " +
1929 "This version can load notebook formats " +
1930 "v" + this.nbformat + " or earlier.";
1930 "v" + this.nbformat + " or earlier.";
1931 }
1931 }
1932 IPython.dialog.modal({
1932 IPython.dialog.modal({
1933 title: "Error loading notebook",
1933 title: "Error loading notebook",
1934 body : msg,
1934 body : msg,
1935 buttons : {
1935 buttons : {
1936 "OK": {}
1936 "OK": {}
1937 }
1937 }
1938 });
1938 });
1939 }
1939 }
1940
1940
1941 /********************* checkpoint-related *********************/
1941 /********************* checkpoint-related *********************/
1942
1942
1943 /**
1943 /**
1944 * Save the notebook then immediately create a checkpoint.
1944 * Save the notebook then immediately create a checkpoint.
1945 *
1945 *
1946 * @method save_checkpoint
1946 * @method save_checkpoint
1947 */
1947 */
1948 Notebook.prototype.save_checkpoint = function () {
1948 Notebook.prototype.save_checkpoint = function () {
1949 this._checkpoint_after_save = true;
1949 this._checkpoint_after_save = true;
1950 this.save_notebook();
1950 this.save_notebook();
1951 };
1951 };
1952
1952
1953 /**
1953 /**
1954 * Add a checkpoint for this notebook.
1954 * Add a checkpoint for this notebook.
1955 * for use as a callback from checkpoint creation.
1955 * for use as a callback from checkpoint creation.
1956 *
1956 *
1957 * @method add_checkpoint
1957 * @method add_checkpoint
1958 */
1958 */
1959 Notebook.prototype.add_checkpoint = function (checkpoint) {
1959 Notebook.prototype.add_checkpoint = function (checkpoint) {
1960 var found = false;
1960 var found = false;
1961 for (var i = 0; i < this.checkpoints.length; i++) {
1961 for (var i = 0; i < this.checkpoints.length; i++) {
1962 var existing = this.checkpoints[i];
1962 var existing = this.checkpoints[i];
1963 if (existing.id == checkpoint.id) {
1963 if (existing.id == checkpoint.id) {
1964 found = true;
1964 found = true;
1965 this.checkpoints[i] = checkpoint;
1965 this.checkpoints[i] = checkpoint;
1966 break;
1966 break;
1967 }
1967 }
1968 }
1968 }
1969 if (!found) {
1969 if (!found) {
1970 this.checkpoints.push(checkpoint);
1970 this.checkpoints.push(checkpoint);
1971 }
1971 }
1972 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1972 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1973 };
1973 };
1974
1974
1975 /**
1975 /**
1976 * List checkpoints for this notebook.
1976 * List checkpoints for this notebook.
1977 *
1977 *
1978 * @method list_checkpoints
1978 * @method list_checkpoints
1979 */
1979 */
1980 Notebook.prototype.list_checkpoints = function () {
1980 Notebook.prototype.list_checkpoints = function () {
1981 var url = utils.url_join_encode(
1981 var url = utils.url_join_encode(
1982 this._baseProjectUrl,
1982 this._baseProjectUrl,
1983 'api/notebooks',
1983 'api/notebooks',
1984 this.notebook_path,
1984 this.notebook_path,
1985 this.notebook_name,
1985 this.notebook_name,
1986 'checkpoints'
1986 'checkpoints'
1987 );
1987 );
1988 $.get(url).done(
1988 $.get(url).done(
1989 $.proxy(this.list_checkpoints_success, this)
1989 $.proxy(this.list_checkpoints_success, this)
1990 ).fail(
1990 ).fail(
1991 $.proxy(this.list_checkpoints_error, this)
1991 $.proxy(this.list_checkpoints_error, this)
1992 );
1992 );
1993 };
1993 };
1994
1994
1995 /**
1995 /**
1996 * Success callback for listing checkpoints.
1996 * Success callback for listing checkpoints.
1997 *
1997 *
1998 * @method list_checkpoint_success
1998 * @method list_checkpoint_success
1999 * @param {Object} data JSON representation of a checkpoint
1999 * @param {Object} data JSON representation of a checkpoint
2000 * @param {String} status Description of response status
2000 * @param {String} status Description of response status
2001 * @param {jqXHR} xhr jQuery Ajax object
2001 * @param {jqXHR} xhr jQuery Ajax object
2002 */
2002 */
2003 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2003 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2004 var data = $.parseJSON(data);
2004 var data = $.parseJSON(data);
2005 this.checkpoints = data;
2005 this.checkpoints = data;
2006 if (data.length) {
2006 if (data.length) {
2007 this.last_checkpoint = data[data.length - 1];
2007 this.last_checkpoint = data[data.length - 1];
2008 } else {
2008 } else {
2009 this.last_checkpoint = null;
2009 this.last_checkpoint = null;
2010 }
2010 }
2011 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2011 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2012 };
2012 };
2013
2013
2014 /**
2014 /**
2015 * Failure callback for listing a checkpoint.
2015 * Failure callback for listing a checkpoint.
2016 *
2016 *
2017 * @method list_checkpoint_error
2017 * @method list_checkpoint_error
2018 * @param {jqXHR} xhr jQuery Ajax object
2018 * @param {jqXHR} xhr jQuery Ajax object
2019 * @param {String} status Description of response status
2019 * @param {String} status Description of response status
2020 * @param {String} error_msg HTTP error message
2020 * @param {String} error_msg HTTP error message
2021 */
2021 */
2022 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2022 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2023 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2023 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2024 };
2024 };
2025
2025
2026 /**
2026 /**
2027 * Create a checkpoint of this notebook on the server from the most recent save.
2027 * Create a checkpoint of this notebook on the server from the most recent save.
2028 *
2028 *
2029 * @method create_checkpoint
2029 * @method create_checkpoint
2030 */
2030 */
2031 Notebook.prototype.create_checkpoint = function () {
2031 Notebook.prototype.create_checkpoint = function () {
2032 var url = utils.url_join_encode(
2032 var url = utils.url_join_encode(
2033 this._baseProjectUrl,
2033 this._baseProjectUrl,
2034 'api/notebooks',
2034 'api/notebooks',
2035 this.notebookPath(),
2035 this.notebookPath(),
2036 this.notebook_name,
2036 this.notebook_name,
2037 'checkpoints'
2037 'checkpoints'
2038 );
2038 );
2039 $.post(url).done(
2039 $.post(url).done(
2040 $.proxy(this.create_checkpoint_success, this)
2040 $.proxy(this.create_checkpoint_success, this)
2041 ).fail(
2041 ).fail(
2042 $.proxy(this.create_checkpoint_error, this)
2042 $.proxy(this.create_checkpoint_error, this)
2043 );
2043 );
2044 };
2044 };
2045
2045
2046 /**
2046 /**
2047 * Success callback for creating a checkpoint.
2047 * Success callback for creating a checkpoint.
2048 *
2048 *
2049 * @method create_checkpoint_success
2049 * @method create_checkpoint_success
2050 * @param {Object} data JSON representation of a checkpoint
2050 * @param {Object} data JSON representation of a checkpoint
2051 * @param {String} status Description of response status
2051 * @param {String} status Description of response status
2052 * @param {jqXHR} xhr jQuery Ajax object
2052 * @param {jqXHR} xhr jQuery Ajax object
2053 */
2053 */
2054 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2054 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2055 var data = $.parseJSON(data);
2055 var data = $.parseJSON(data);
2056 this.add_checkpoint(data);
2056 this.add_checkpoint(data);
2057 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2057 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2058 };
2058 };
2059
2059
2060 /**
2060 /**
2061 * Failure callback for creating a checkpoint.
2061 * Failure callback for creating a checkpoint.
2062 *
2062 *
2063 * @method create_checkpoint_error
2063 * @method create_checkpoint_error
2064 * @param {jqXHR} xhr jQuery Ajax object
2064 * @param {jqXHR} xhr jQuery Ajax object
2065 * @param {String} status Description of response status
2065 * @param {String} status Description of response status
2066 * @param {String} error_msg HTTP error message
2066 * @param {String} error_msg HTTP error message
2067 */
2067 */
2068 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2068 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2069 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2069 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2070 };
2070 };
2071
2071
2072 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2072 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2073 var that = this;
2073 var that = this;
2074 var checkpoint = checkpoint || this.last_checkpoint;
2074 var checkpoint = checkpoint || this.last_checkpoint;
2075 if ( ! checkpoint ) {
2075 if ( ! checkpoint ) {
2076 console.log("restore dialog, but no checkpoint to restore to!");
2076 console.log("restore dialog, but no checkpoint to restore to!");
2077 return;
2077 return;
2078 }
2078 }
2079 var body = $('<div/>').append(
2079 var body = $('<div/>').append(
2080 $('<p/>').addClass("p-space").text(
2080 $('<p/>').addClass("p-space").text(
2081 "Are you sure you want to revert the notebook to " +
2081 "Are you sure you want to revert the notebook to " +
2082 "the latest checkpoint?"
2082 "the latest checkpoint?"
2083 ).append(
2083 ).append(
2084 $("<strong/>").text(
2084 $("<strong/>").text(
2085 " This cannot be undone."
2085 " This cannot be undone."
2086 )
2086 )
2087 )
2087 )
2088 ).append(
2088 ).append(
2089 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2089 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2090 ).append(
2090 ).append(
2091 $('<p/>').addClass("p-space").text(
2091 $('<p/>').addClass("p-space").text(
2092 Date(checkpoint.last_modified)
2092 Date(checkpoint.last_modified)
2093 ).css("text-align", "center")
2093 ).css("text-align", "center")
2094 );
2094 );
2095
2095
2096 IPython.dialog.modal({
2096 IPython.dialog.modal({
2097 title : "Revert notebook to checkpoint",
2097 title : "Revert notebook to checkpoint",
2098 body : body,
2098 body : body,
2099 buttons : {
2099 buttons : {
2100 Revert : {
2100 Revert : {
2101 class : "btn-danger",
2101 class : "btn-danger",
2102 click : function () {
2102 click : function () {
2103 that.restore_checkpoint(checkpoint.id);
2103 that.restore_checkpoint(checkpoint.id);
2104 }
2104 }
2105 },
2105 },
2106 Cancel : {}
2106 Cancel : {}
2107 }
2107 }
2108 });
2108 });
2109 }
2109 }
2110
2110
2111 /**
2111 /**
2112 * Restore the notebook to a checkpoint state.
2112 * Restore the notebook to a checkpoint state.
2113 *
2113 *
2114 * @method restore_checkpoint
2114 * @method restore_checkpoint
2115 * @param {String} checkpoint ID
2115 * @param {String} checkpoint ID
2116 */
2116 */
2117 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2117 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2118 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2118 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2119 var url = utils.url_join_encode(
2119 var url = utils.url_join_encode(
2120 this._baseProjectUrl,
2120 this._baseProjectUrl,
2121 'api/notebooks',
2121 'api/notebooks',
2122 this.notebookPath(),
2122 this.notebookPath(),
2123 this.notebook_name,
2123 this.notebook_name,
2124 'checkpoints',
2124 'checkpoints',
2125 checkpoint
2125 checkpoint
2126 );
2126 );
2127 $.post(url).done(
2127 $.post(url).done(
2128 $.proxy(this.restore_checkpoint_success, this)
2128 $.proxy(this.restore_checkpoint_success, this)
2129 ).fail(
2129 ).fail(
2130 $.proxy(this.restore_checkpoint_error, this)
2130 $.proxy(this.restore_checkpoint_error, this)
2131 );
2131 );
2132 };
2132 };
2133
2133
2134 /**
2134 /**
2135 * Success callback for restoring a notebook to a checkpoint.
2135 * Success callback for restoring a notebook to a checkpoint.
2136 *
2136 *
2137 * @method restore_checkpoint_success
2137 * @method restore_checkpoint_success
2138 * @param {Object} data (ignored, should be empty)
2138 * @param {Object} data (ignored, should be empty)
2139 * @param {String} status Description of response status
2139 * @param {String} status Description of response status
2140 * @param {jqXHR} xhr jQuery Ajax object
2140 * @param {jqXHR} xhr jQuery Ajax object
2141 */
2141 */
2142 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2142 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2143 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2143 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2144 this.load_notebook(this.notebook_name, this.notebook_path);
2144 this.load_notebook(this.notebook_name, this.notebook_path);
2145 };
2145 };
2146
2146
2147 /**
2147 /**
2148 * Failure callback for restoring a notebook to a checkpoint.
2148 * Failure callback for restoring a notebook to a checkpoint.
2149 *
2149 *
2150 * @method restore_checkpoint_error
2150 * @method restore_checkpoint_error
2151 * @param {jqXHR} xhr jQuery Ajax object
2151 * @param {jqXHR} xhr jQuery Ajax object
2152 * @param {String} status Description of response status
2152 * @param {String} status Description of response status
2153 * @param {String} error_msg HTTP error message
2153 * @param {String} error_msg HTTP error message
2154 */
2154 */
2155 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2155 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2156 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2156 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2157 };
2157 };
2158
2158
2159 /**
2159 /**
2160 * Delete a notebook checkpoint.
2160 * Delete a notebook checkpoint.
2161 *
2161 *
2162 * @method delete_checkpoint
2162 * @method delete_checkpoint
2163 * @param {String} checkpoint ID
2163 * @param {String} checkpoint ID
2164 */
2164 */
2165 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2165 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2166 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2166 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2167 var url = utils.url_join_encode(
2167 var url = utils.url_join_encode(
2168 this._baseProjectUrl,
2168 this._baseProjectUrl,
2169 'api/notebooks',
2169 'api/notebooks',
2170 this.notebookPath(),
2170 this.notebookPath(),
2171 this.notebook_name,
2171 this.notebook_name,
2172 'checkpoints',
2172 'checkpoints',
2173 checkpoint
2173 checkpoint
2174 );
2174 );
2175 $.ajax(url, {
2175 $.ajax(url, {
2176 type: 'DELETE',
2176 type: 'DELETE',
2177 success: $.proxy(this.delete_checkpoint_success, this),
2177 success: $.proxy(this.delete_checkpoint_success, this),
2178 error: $.proxy(this.delete_notebook_error,this)
2178 error: $.proxy(this.delete_notebook_error,this)
2179 });
2179 });
2180 };
2180 };
2181
2181
2182 /**
2182 /**
2183 * Success callback for deleting a notebook checkpoint
2183 * Success callback for deleting a notebook checkpoint
2184 *
2184 *
2185 * @method delete_checkpoint_success
2185 * @method delete_checkpoint_success
2186 * @param {Object} data (ignored, should be empty)
2186 * @param {Object} data (ignored, should be empty)
2187 * @param {String} status Description of response status
2187 * @param {String} status Description of response status
2188 * @param {jqXHR} xhr jQuery Ajax object
2188 * @param {jqXHR} xhr jQuery Ajax object
2189 */
2189 */
2190 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2190 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2191 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2191 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2192 this.load_notebook(this.notebook_name, this.notebook_path);
2192 this.load_notebook(this.notebook_name, this.notebook_path);
2193 };
2193 };
2194
2194
2195 /**
2195 /**
2196 * Failure callback for deleting a notebook checkpoint.
2196 * Failure callback for deleting a notebook checkpoint.
2197 *
2197 *
2198 * @method delete_checkpoint_error
2198 * @method delete_checkpoint_error
2199 * @param {jqXHR} xhr jQuery Ajax object
2199 * @param {jqXHR} xhr jQuery Ajax object
2200 * @param {String} status Description of response status
2200 * @param {String} status Description of response status
2201 * @param {String} error_msg HTTP error message
2201 * @param {String} error_msg HTTP error message
2202 */
2202 */
2203 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2203 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2204 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2204 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2205 };
2205 };
2206
2206
2207
2207
2208 IPython.Notebook = Notebook;
2208 IPython.Notebook = Notebook;
2209
2209
2210
2210
2211 return IPython;
2211 return IPython;
2212
2212
2213 }(IPython));
2213 }(IPython));
@@ -1,82 +1,82 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notification widget
9 // Notification widget
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16
16
17 var NotificationWidget = function (selector) {
17 var NotificationWidget = function (selector) {
18 this.selector = selector;
18 this.selector = selector;
19 this.timeout = null;
19 this.timeout = null;
20 this.busy = false;
20 this.busy = false;
21 if (this.selector !== undefined) {
21 if (this.selector !== undefined) {
22 this.element = $(selector);
22 this.element = $(selector);
23 this.style();
23 this.style();
24 }
24 }
25 this.element.button();
25 this.element.button();
26 this.element.hide();
26 this.element.hide();
27 var that = this;
27 var that = this;
28
28
29 };
29 };
30
30
31
31
32 NotificationWidget.prototype.style = function () {
32 NotificationWidget.prototype.style = function () {
33 this.element.addClass('notification_widget pull-right');
33 this.element.addClass('notification_widget pull-right');
34 this.element.addClass('border-box-sizing');
34 this.element.addClass('border-box-sizing');
35 };
35 };
36
36
37 // msg : message to display
37 // msg : message to display
38 // timeout : time in ms before diseapearing
38 // timeout : time in ms before diseapearing
39 //
39 //
40 // if timeout <= 0
40 // if timeout <= 0
41 // click_callback : function called if user click on notification
41 // click_callback : function called if user click on notification
42 // could return false to prevent the notification to be dismissed
42 // could return false to prevent the notification to be dismissed
43 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback) {
43 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback) {
44 var callback = click_callback || function() {return false;};
44 var callback = click_callback || function() {return false;};
45 var that = this;
45 var that = this;
46 this.element.html(msg);
46 this.element.text(msg);
47 this.element.fadeIn(100);
47 this.element.fadeIn(100);
48 if (this.timeout !== null) {
48 if (this.timeout !== null) {
49 clearTimeout(this.timeout);
49 clearTimeout(this.timeout);
50 this.timeout = null;
50 this.timeout = null;
51 }
51 }
52 if (timeout !== undefined && timeout >=0) {
52 if (timeout !== undefined && timeout >=0) {
53 this.timeout = setTimeout(function () {
53 this.timeout = setTimeout(function () {
54 that.element.fadeOut(100, function () {that.element.html('');});
54 that.element.fadeOut(100, function () {that.element.text('');});
55 that.timeout = null;
55 that.timeout = null;
56 }, timeout);
56 }, timeout);
57 } else {
57 } else {
58 this.element.click(function() {
58 this.element.click(function() {
59 if( callback() != false ) {
59 if( callback() != false ) {
60 that.element.fadeOut(100, function () {that.element.html('');});
60 that.element.fadeOut(100, function () {that.element.text('');});
61 that.element.unbind('click');
61 that.element.unbind('click');
62 }
62 }
63 if (that.timeout !== undefined) {
63 if (that.timeout !== undefined) {
64 that.timeout = undefined;
64 that.timeout = undefined;
65 clearTimeout(that.timeout);
65 clearTimeout(that.timeout);
66 }
66 }
67 });
67 });
68 }
68 }
69 };
69 };
70
70
71
71
72 NotificationWidget.prototype.get_message = function () {
72 NotificationWidget.prototype.get_message = function () {
73 return this.element.html();
73 return this.element.html();
74 };
74 };
75
75
76
76
77 IPython.NotificationWidget = NotificationWidget;
77 IPython.NotificationWidget = NotificationWidget;
78
78
79 return IPython;
79 return IPython;
80
80
81 }(IPython));
81 }(IPython));
82
82
@@ -1,819 +1,819 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // OutputArea
9 // OutputArea
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule OutputArea
15 * @submodule OutputArea
16 */
16 */
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 /**
22 /**
23 * @class OutputArea
23 * @class OutputArea
24 *
24 *
25 * @constructor
25 * @constructor
26 */
26 */
27
27
28 var OutputArea = function (selector, prompt_area) {
28 var OutputArea = function (selector, prompt_area) {
29 this.selector = selector;
29 this.selector = selector;
30 this.wrapper = $(selector);
30 this.wrapper = $(selector);
31 this.outputs = [];
31 this.outputs = [];
32 this.collapsed = false;
32 this.collapsed = false;
33 this.scrolled = false;
33 this.scrolled = false;
34 this.clear_queued = null;
34 this.clear_queued = null;
35 if (prompt_area === undefined) {
35 if (prompt_area === undefined) {
36 this.prompt_area = true;
36 this.prompt_area = true;
37 } else {
37 } else {
38 this.prompt_area = prompt_area;
38 this.prompt_area = prompt_area;
39 }
39 }
40 this.create_elements();
40 this.create_elements();
41 this.style();
41 this.style();
42 this.bind_events();
42 this.bind_events();
43 };
43 };
44
44
45 OutputArea.prototype.create_elements = function () {
45 OutputArea.prototype.create_elements = function () {
46 this.element = $("<div/>");
46 this.element = $("<div/>");
47 this.collapse_button = $("<div/>");
47 this.collapse_button = $("<div/>");
48 this.prompt_overlay = $("<div/>");
48 this.prompt_overlay = $("<div/>");
49 this.wrapper.append(this.prompt_overlay);
49 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.element);
50 this.wrapper.append(this.element);
51 this.wrapper.append(this.collapse_button);
51 this.wrapper.append(this.collapse_button);
52 };
52 };
53
53
54
54
55 OutputArea.prototype.style = function () {
55 OutputArea.prototype.style = function () {
56 this.collapse_button.hide();
56 this.collapse_button.hide();
57 this.prompt_overlay.hide();
57 this.prompt_overlay.hide();
58
58
59 this.wrapper.addClass('output_wrapper');
59 this.wrapper.addClass('output_wrapper');
60 this.element.addClass('output');
60 this.element.addClass('output');
61
61
62 this.collapse_button.addClass("btn output_collapsed");
62 this.collapse_button.addClass("btn output_collapsed");
63 this.collapse_button.attr('title', 'click to expand output');
63 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.html('. . .');
64 this.collapse_button.text('. . .');
65
65
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68
68
69 this.collapse();
69 this.collapse();
70 };
70 };
71
71
72 /**
72 /**
73 * Should the OutputArea scroll?
73 * Should the OutputArea scroll?
74 * Returns whether the height (in lines) exceeds a threshold.
74 * Returns whether the height (in lines) exceeds a threshold.
75 *
75 *
76 * @private
76 * @private
77 * @method _should_scroll
77 * @method _should_scroll
78 * @param [lines=100]{Integer}
78 * @param [lines=100]{Integer}
79 * @return {Bool}
79 * @return {Bool}
80 *
80 *
81 */
81 */
82 OutputArea.prototype._should_scroll = function (lines) {
82 OutputArea.prototype._should_scroll = function (lines) {
83 if (lines <=0 ){ return }
83 if (lines <=0 ){ return }
84 if (!lines) {
84 if (!lines) {
85 lines = 100;
85 lines = 100;
86 }
86 }
87 // line-height from http://stackoverflow.com/questions/1185151
87 // line-height from http://stackoverflow.com/questions/1185151
88 var fontSize = this.element.css('font-size');
88 var fontSize = this.element.css('font-size');
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90
90
91 return (this.element.height() > lines * lineHeight);
91 return (this.element.height() > lines * lineHeight);
92 };
92 };
93
93
94
94
95 OutputArea.prototype.bind_events = function () {
95 OutputArea.prototype.bind_events = function () {
96 var that = this;
96 var that = this;
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99
99
100 this.element.resize(function () {
100 this.element.resize(function () {
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 if ( IPython.utils.browser[0] === "Firefox" ) {
102 if ( IPython.utils.browser[0] === "Firefox" ) {
103 return;
103 return;
104 }
104 }
105 // maybe scroll output,
105 // maybe scroll output,
106 // if it's grown large enough and hasn't already been scrolled.
106 // if it's grown large enough and hasn't already been scrolled.
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 that.scroll_area();
108 that.scroll_area();
109 }
109 }
110 });
110 });
111 this.collapse_button.click(function () {
111 this.collapse_button.click(function () {
112 that.expand();
112 that.expand();
113 });
113 });
114 };
114 };
115
115
116
116
117 OutputArea.prototype.collapse = function () {
117 OutputArea.prototype.collapse = function () {
118 if (!this.collapsed) {
118 if (!this.collapsed) {
119 this.element.hide();
119 this.element.hide();
120 this.prompt_overlay.hide();
120 this.prompt_overlay.hide();
121 if (this.element.html()){
121 if (this.element.html()){
122 this.collapse_button.show();
122 this.collapse_button.show();
123 }
123 }
124 this.collapsed = true;
124 this.collapsed = true;
125 }
125 }
126 };
126 };
127
127
128
128
129 OutputArea.prototype.expand = function () {
129 OutputArea.prototype.expand = function () {
130 if (this.collapsed) {
130 if (this.collapsed) {
131 this.collapse_button.hide();
131 this.collapse_button.hide();
132 this.element.show();
132 this.element.show();
133 this.prompt_overlay.show();
133 this.prompt_overlay.show();
134 this.collapsed = false;
134 this.collapsed = false;
135 }
135 }
136 };
136 };
137
137
138
138
139 OutputArea.prototype.toggle_output = function () {
139 OutputArea.prototype.toggle_output = function () {
140 if (this.collapsed) {
140 if (this.collapsed) {
141 this.expand();
141 this.expand();
142 } else {
142 } else {
143 this.collapse();
143 this.collapse();
144 }
144 }
145 };
145 };
146
146
147
147
148 OutputArea.prototype.scroll_area = function () {
148 OutputArea.prototype.scroll_area = function () {
149 this.element.addClass('output_scroll');
149 this.element.addClass('output_scroll');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.scrolled = true;
151 this.scrolled = true;
152 };
152 };
153
153
154
154
155 OutputArea.prototype.unscroll_area = function () {
155 OutputArea.prototype.unscroll_area = function () {
156 this.element.removeClass('output_scroll');
156 this.element.removeClass('output_scroll');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.scrolled = false;
158 this.scrolled = false;
159 };
159 };
160
160
161 /**
161 /**
162 * Threshold to trigger autoscroll when the OutputArea is resized,
162 * Threshold to trigger autoscroll when the OutputArea is resized,
163 * typically when new outputs are added.
163 * typically when new outputs are added.
164 *
164 *
165 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
165 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
166 * unless it is < 0, in which case autoscroll will never be triggered
166 * unless it is < 0, in which case autoscroll will never be triggered
167 *
167 *
168 * @property auto_scroll_threshold
168 * @property auto_scroll_threshold
169 * @type Number
169 * @type Number
170 * @default 100
170 * @default 100
171 *
171 *
172 **/
172 **/
173 OutputArea.auto_scroll_threshold = 100;
173 OutputArea.auto_scroll_threshold = 100;
174
174
175
175
176 /**
176 /**
177 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
177 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
178 * shorter than this are never scrolled.
178 * shorter than this are never scrolled.
179 *
179 *
180 * @property minimum_scroll_threshold
180 * @property minimum_scroll_threshold
181 * @type Number
181 * @type Number
182 * @default 20
182 * @default 20
183 *
183 *
184 **/
184 **/
185 OutputArea.minimum_scroll_threshold = 20;
185 OutputArea.minimum_scroll_threshold = 20;
186
186
187
187
188 /**
188 /**
189 *
189 *
190 * Scroll OutputArea if height supperior than a threshold (in lines).
190 * Scroll OutputArea if height supperior than a threshold (in lines).
191 *
191 *
192 * Threshold is a maximum number of lines. If unspecified, defaults to
192 * Threshold is a maximum number of lines. If unspecified, defaults to
193 * OutputArea.minimum_scroll_threshold.
193 * OutputArea.minimum_scroll_threshold.
194 *
194 *
195 * Negative threshold will prevent the OutputArea from ever scrolling.
195 * Negative threshold will prevent the OutputArea from ever scrolling.
196 *
196 *
197 * @method scroll_if_long
197 * @method scroll_if_long
198 *
198 *
199 * @param [lines=20]{Number} Default to 20 if not set,
199 * @param [lines=20]{Number} Default to 20 if not set,
200 * behavior undefined for value of `0`.
200 * behavior undefined for value of `0`.
201 *
201 *
202 **/
202 **/
203 OutputArea.prototype.scroll_if_long = function (lines) {
203 OutputArea.prototype.scroll_if_long = function (lines) {
204 var n = lines | OutputArea.minimum_scroll_threshold;
204 var n = lines | OutputArea.minimum_scroll_threshold;
205 if(n <= 0){
205 if(n <= 0){
206 return
206 return
207 }
207 }
208
208
209 if (this._should_scroll(n)) {
209 if (this._should_scroll(n)) {
210 // only allow scrolling long-enough output
210 // only allow scrolling long-enough output
211 this.scroll_area();
211 this.scroll_area();
212 }
212 }
213 };
213 };
214
214
215
215
216 OutputArea.prototype.toggle_scroll = function () {
216 OutputArea.prototype.toggle_scroll = function () {
217 if (this.scrolled) {
217 if (this.scrolled) {
218 this.unscroll_area();
218 this.unscroll_area();
219 } else {
219 } else {
220 // only allow scrolling long-enough output
220 // only allow scrolling long-enough output
221 this.scroll_if_long();
221 this.scroll_if_long();
222 }
222 }
223 };
223 };
224
224
225
225
226 // typeset with MathJax if MathJax is available
226 // typeset with MathJax if MathJax is available
227 OutputArea.prototype.typeset = function () {
227 OutputArea.prototype.typeset = function () {
228 if (window.MathJax){
228 if (window.MathJax){
229 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
229 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
230 }
230 }
231 };
231 };
232
232
233
233
234 OutputArea.prototype.handle_output = function (msg) {
234 OutputArea.prototype.handle_output = function (msg) {
235 var json = {};
235 var json = {};
236 var msg_type = json.output_type = msg.header.msg_type;
236 var msg_type = json.output_type = msg.header.msg_type;
237 var content = msg.content;
237 var content = msg.content;
238 if (msg_type === "stream") {
238 if (msg_type === "stream") {
239 json.text = content.data;
239 json.text = content.data;
240 json.stream = content.name;
240 json.stream = content.name;
241 } else if (msg_type === "display_data") {
241 } else if (msg_type === "display_data") {
242 json = content.data;
242 json = content.data;
243 json.output_type = msg_type;
243 json.output_type = msg_type;
244 json.metadata = content.metadata;
244 json.metadata = content.metadata;
245 } else if (msg_type === "pyout") {
245 } else if (msg_type === "pyout") {
246 json = content.data;
246 json = content.data;
247 json.output_type = msg_type;
247 json.output_type = msg_type;
248 json.metadata = content.metadata;
248 json.metadata = content.metadata;
249 json.prompt_number = content.execution_count;
249 json.prompt_number = content.execution_count;
250 } else if (msg_type === "pyerr") {
250 } else if (msg_type === "pyerr") {
251 json.ename = content.ename;
251 json.ename = content.ename;
252 json.evalue = content.evalue;
252 json.evalue = content.evalue;
253 json.traceback = content.traceback;
253 json.traceback = content.traceback;
254 }
254 }
255 this.append_output(json);
255 this.append_output(json);
256 };
256 };
257
257
258 OutputArea.mime_map = {
258 OutputArea.mime_map = {
259 "text/plain" : "text",
259 "text/plain" : "text",
260 "text/html" : "html",
260 "text/html" : "html",
261 "image/svg+xml" : "svg",
261 "image/svg+xml" : "svg",
262 "image/png" : "png",
262 "image/png" : "png",
263 "image/jpeg" : "jpeg",
263 "image/jpeg" : "jpeg",
264 "text/latex" : "latex",
264 "text/latex" : "latex",
265 "application/json" : "json",
265 "application/json" : "json",
266 "application/javascript" : "javascript",
266 "application/javascript" : "javascript",
267 };
267 };
268
268
269 OutputArea.mime_map_r = {
269 OutputArea.mime_map_r = {
270 "text" : "text/plain",
270 "text" : "text/plain",
271 "html" : "text/html",
271 "html" : "text/html",
272 "svg" : "image/svg+xml",
272 "svg" : "image/svg+xml",
273 "png" : "image/png",
273 "png" : "image/png",
274 "jpeg" : "image/jpeg",
274 "jpeg" : "image/jpeg",
275 "latex" : "text/latex",
275 "latex" : "text/latex",
276 "json" : "application/json",
276 "json" : "application/json",
277 "javascript" : "application/javascript",
277 "javascript" : "application/javascript",
278 };
278 };
279
279
280 OutputArea.prototype.rename_keys = function (data, key_map) {
280 OutputArea.prototype.rename_keys = function (data, key_map) {
281 var remapped = {};
281 var remapped = {};
282 for (var key in data) {
282 for (var key in data) {
283 var new_key = key_map[key] || key;
283 var new_key = key_map[key] || key;
284 remapped[new_key] = data[key];
284 remapped[new_key] = data[key];
285 }
285 }
286 return remapped;
286 return remapped;
287 };
287 };
288
288
289
289
290 OutputArea.output_types = [
290 OutputArea.output_types = [
291 'application/javascript',
291 'application/javascript',
292 'text/html',
292 'text/html',
293 'text/latex',
293 'text/latex',
294 'image/svg+xml',
294 'image/svg+xml',
295 'image/png',
295 'image/png',
296 'image/jpeg',
296 'image/jpeg',
297 'text/plain'
297 'text/plain'
298 ];
298 ];
299
299
300 OutputArea.prototype.validate_output = function (json) {
300 OutputArea.prototype.validate_output = function (json) {
301 // scrub invalid outputs
301 // scrub invalid outputs
302 // TODO: right now everything is a string, but JSON really shouldn't be.
302 // TODO: right now everything is a string, but JSON really shouldn't be.
303 // nbformat 4 will fix that.
303 // nbformat 4 will fix that.
304 $.map(OutputArea.output_types, function(key){
304 $.map(OutputArea.output_types, function(key){
305 if (json[key] !== undefined && typeof json[key] !== 'string') {
305 if (json[key] !== undefined && typeof json[key] !== 'string') {
306 console.log("Invalid type for " + key, json[key]);
306 console.log("Invalid type for " + key, json[key]);
307 delete json[key];
307 delete json[key];
308 }
308 }
309 });
309 });
310 return json;
310 return json;
311 };
311 };
312
312
313 OutputArea.prototype.append_output = function (json) {
313 OutputArea.prototype.append_output = function (json) {
314 this.expand();
314 this.expand();
315 // Clear the output if clear is queued.
315 // Clear the output if clear is queued.
316 var needs_height_reset = false;
316 var needs_height_reset = false;
317 if (this.clear_queued) {
317 if (this.clear_queued) {
318 this.clear_output(false);
318 this.clear_output(false);
319 needs_height_reset = true;
319 needs_height_reset = true;
320 }
320 }
321
321
322 // validate output data types
322 // validate output data types
323 json = this.validate_output(json);
323 json = this.validate_output(json);
324
324
325 if (json.output_type === 'pyout') {
325 if (json.output_type === 'pyout') {
326 this.append_pyout(json);
326 this.append_pyout(json);
327 } else if (json.output_type === 'pyerr') {
327 } else if (json.output_type === 'pyerr') {
328 this.append_pyerr(json);
328 this.append_pyerr(json);
329 } else if (json.output_type === 'display_data') {
329 } else if (json.output_type === 'display_data') {
330 this.append_display_data(json);
330 this.append_display_data(json);
331 } else if (json.output_type === 'stream') {
331 } else if (json.output_type === 'stream') {
332 this.append_stream(json);
332 this.append_stream(json);
333 }
333 }
334 this.outputs.push(json);
334 this.outputs.push(json);
335
335
336 // Only reset the height to automatic if the height is currently
336 // Only reset the height to automatic if the height is currently
337 // fixed (done by wait=True flag on clear_output).
337 // fixed (done by wait=True flag on clear_output).
338 if (needs_height_reset) {
338 if (needs_height_reset) {
339 this.element.height('');
339 this.element.height('');
340 }
340 }
341
341
342 var that = this;
342 var that = this;
343 setTimeout(function(){that.element.trigger('resize');}, 100);
343 setTimeout(function(){that.element.trigger('resize');}, 100);
344 };
344 };
345
345
346
346
347 OutputArea.prototype.create_output_area = function () {
347 OutputArea.prototype.create_output_area = function () {
348 var oa = $("<div/>").addClass("output_area");
348 var oa = $("<div/>").addClass("output_area");
349 if (this.prompt_area) {
349 if (this.prompt_area) {
350 oa.append($('<div/>').addClass('prompt'));
350 oa.append($('<div/>').addClass('prompt'));
351 }
351 }
352 return oa;
352 return oa;
353 };
353 };
354
354
355
355
356 function _get_metadata_key(metadata, key, mime) {
356 function _get_metadata_key(metadata, key, mime) {
357 var mime_md = metadata[mime];
357 var mime_md = metadata[mime];
358 // mime-specific higher priority
358 // mime-specific higher priority
359 if (mime_md && mime_md[key] !== undefined) {
359 if (mime_md && mime_md[key] !== undefined) {
360 return mime_md[key];
360 return mime_md[key];
361 }
361 }
362 // fallback on global
362 // fallback on global
363 return metadata[key];
363 return metadata[key];
364 }
364 }
365
365
366 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
366 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
367 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
367 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
368 if (_get_metadata_key(md, 'isolated', mime)) {
368 if (_get_metadata_key(md, 'isolated', mime)) {
369 // Create an iframe to isolate the subarea from the rest of the
369 // Create an iframe to isolate the subarea from the rest of the
370 // document
370 // document
371 var iframe = $('<iframe/>').addClass('box-flex1');
371 var iframe = $('<iframe/>').addClass('box-flex1');
372 iframe.css({'height':1, 'width':'100%', 'display':'block'});
372 iframe.css({'height':1, 'width':'100%', 'display':'block'});
373 iframe.attr('frameborder', 0);
373 iframe.attr('frameborder', 0);
374 iframe.attr('scrolling', 'auto');
374 iframe.attr('scrolling', 'auto');
375
375
376 // Once the iframe is loaded, the subarea is dynamically inserted
376 // Once the iframe is loaded, the subarea is dynamically inserted
377 iframe.on('load', function() {
377 iframe.on('load', function() {
378 // Workaround needed by Firefox, to properly render svg inside
378 // Workaround needed by Firefox, to properly render svg inside
379 // iframes, see http://stackoverflow.com/questions/10177190/
379 // iframes, see http://stackoverflow.com/questions/10177190/
380 // svg-dynamically-added-to-iframe-does-not-render-correctly
380 // svg-dynamically-added-to-iframe-does-not-render-correctly
381 this.contentDocument.open();
381 this.contentDocument.open();
382
382
383 // Insert the subarea into the iframe
383 // Insert the subarea into the iframe
384 // We must directly write the html. When using Jquery's append
384 // We must directly write the html. When using Jquery's append
385 // method, javascript is evaluated in the parent document and
385 // method, javascript is evaluated in the parent document and
386 // not in the iframe document.
386 // not in the iframe document.
387 this.contentDocument.write(subarea.html());
387 this.contentDocument.write(subarea.html());
388
388
389 this.contentDocument.close();
389 this.contentDocument.close();
390
390
391 var body = this.contentDocument.body;
391 var body = this.contentDocument.body;
392 // Adjust the iframe height automatically
392 // Adjust the iframe height automatically
393 iframe.height(body.scrollHeight + 'px');
393 iframe.height(body.scrollHeight + 'px');
394 });
394 });
395
395
396 // Elements should be appended to the inner subarea and not to the
396 // Elements should be appended to the inner subarea and not to the
397 // iframe
397 // iframe
398 iframe.append = function(that) {
398 iframe.append = function(that) {
399 subarea.append(that);
399 subarea.append(that);
400 };
400 };
401
401
402 return iframe;
402 return iframe;
403 } else {
403 } else {
404 return subarea;
404 return subarea;
405 }
405 }
406 }
406 }
407
407
408
408
409 OutputArea.prototype._append_javascript_error = function (err, element) {
409 OutputArea.prototype._append_javascript_error = function (err, element) {
410 // display a message when a javascript error occurs in display output
410 // display a message when a javascript error occurs in display output
411 var msg = "Javascript error adding output!"
411 var msg = "Javascript error adding output!"
412 if ( element === undefined ) return;
412 if ( element === undefined ) return;
413 element.append(
413 element.append(
414 $('<div/>').html(msg + "<br/>" +
414 $('<div/>').html(msg + "<br/>" +
415 err.toString() +
415 err.toString() +
416 '<br/>See your browser Javascript console for more details.'
416 '<br/>See your browser Javascript console for more details.'
417 ).addClass('js-error')
417 ).addClass('js-error')
418 );
418 );
419 };
419 };
420
420
421 OutputArea.prototype._safe_append = function (toinsert) {
421 OutputArea.prototype._safe_append = function (toinsert) {
422 // safely append an item to the document
422 // safely append an item to the document
423 // this is an object created by user code,
423 // this is an object created by user code,
424 // and may have errors, which should not be raised
424 // and may have errors, which should not be raised
425 // under any circumstances.
425 // under any circumstances.
426 try {
426 try {
427 this.element.append(toinsert);
427 this.element.append(toinsert);
428 } catch(err) {
428 } catch(err) {
429 console.log(err);
429 console.log(err);
430 // Create an actual output_area and output_subarea, which creates
430 // Create an actual output_area and output_subarea, which creates
431 // the prompt area and the proper indentation.
431 // the prompt area and the proper indentation.
432 var toinsert = this.create_output_area();
432 var toinsert = this.create_output_area();
433 var subarea = $('<div/>').addClass('output_subarea');
433 var subarea = $('<div/>').addClass('output_subarea');
434 toinsert.append(subarea);
434 toinsert.append(subarea);
435 this._append_javascript_error(err, subarea);
435 this._append_javascript_error(err, subarea);
436 this.element.append(toinsert);
436 this.element.append(toinsert);
437 }
437 }
438 };
438 };
439
439
440
440
441 OutputArea.prototype.append_pyout = function (json) {
441 OutputArea.prototype.append_pyout = function (json) {
442 var n = json.prompt_number || ' ';
442 var n = json.prompt_number || ' ';
443 var toinsert = this.create_output_area();
443 var toinsert = this.create_output_area();
444 if (this.prompt_area) {
444 if (this.prompt_area) {
445 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
445 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
446 }
446 }
447 this.append_mime_type(json, toinsert);
447 this.append_mime_type(json, toinsert);
448 this._safe_append(toinsert);
448 this._safe_append(toinsert);
449 // If we just output latex, typeset it.
449 // If we just output latex, typeset it.
450 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
450 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
451 this.typeset();
451 this.typeset();
452 }
452 }
453 };
453 };
454
454
455
455
456 OutputArea.prototype.append_pyerr = function (json) {
456 OutputArea.prototype.append_pyerr = function (json) {
457 var tb = json.traceback;
457 var tb = json.traceback;
458 if (tb !== undefined && tb.length > 0) {
458 if (tb !== undefined && tb.length > 0) {
459 var s = '';
459 var s = '';
460 var len = tb.length;
460 var len = tb.length;
461 for (var i=0; i<len; i++) {
461 for (var i=0; i<len; i++) {
462 s = s + tb[i] + '\n';
462 s = s + tb[i] + '\n';
463 }
463 }
464 s = s + '\n';
464 s = s + '\n';
465 var toinsert = this.create_output_area();
465 var toinsert = this.create_output_area();
466 this.append_text(s, {}, toinsert);
466 this.append_text(s, {}, toinsert);
467 this._safe_append(toinsert);
467 this._safe_append(toinsert);
468 }
468 }
469 };
469 };
470
470
471
471
472 OutputArea.prototype.append_stream = function (json) {
472 OutputArea.prototype.append_stream = function (json) {
473 // temporary fix: if stream undefined (json file written prior to this patch),
473 // temporary fix: if stream undefined (json file written prior to this patch),
474 // default to most likely stdout:
474 // default to most likely stdout:
475 if (json.stream == undefined){
475 if (json.stream == undefined){
476 json.stream = 'stdout';
476 json.stream = 'stdout';
477 }
477 }
478 var text = json.text;
478 var text = json.text;
479 var subclass = "output_"+json.stream;
479 var subclass = "output_"+json.stream;
480 if (this.outputs.length > 0){
480 if (this.outputs.length > 0){
481 // have at least one output to consider
481 // have at least one output to consider
482 var last = this.outputs[this.outputs.length-1];
482 var last = this.outputs[this.outputs.length-1];
483 if (last.output_type == 'stream' && json.stream == last.stream){
483 if (last.output_type == 'stream' && json.stream == last.stream){
484 // latest output was in the same stream,
484 // latest output was in the same stream,
485 // so append directly into its pre tag
485 // so append directly into its pre tag
486 // escape ANSI & HTML specials:
486 // escape ANSI & HTML specials:
487 var pre = this.element.find('div.'+subclass).last().find('pre');
487 var pre = this.element.find('div.'+subclass).last().find('pre');
488 var html = utils.fixCarriageReturn(
488 var html = utils.fixCarriageReturn(
489 pre.html() + utils.fixConsole(text));
489 pre.html() + utils.fixConsole(text));
490 pre.html(html);
490 pre.html(html);
491 return;
491 return;
492 }
492 }
493 }
493 }
494
494
495 if (!text.replace("\r", "")) {
495 if (!text.replace("\r", "")) {
496 // text is nothing (empty string, \r, etc.)
496 // text is nothing (empty string, \r, etc.)
497 // so don't append any elements, which might add undesirable space
497 // so don't append any elements, which might add undesirable space
498 return;
498 return;
499 }
499 }
500
500
501 // If we got here, attach a new div
501 // If we got here, attach a new div
502 var toinsert = this.create_output_area();
502 var toinsert = this.create_output_area();
503 this.append_text(text, {}, toinsert, "output_stream "+subclass);
503 this.append_text(text, {}, toinsert, "output_stream "+subclass);
504 this._safe_append(toinsert);
504 this._safe_append(toinsert);
505 };
505 };
506
506
507
507
508 OutputArea.prototype.append_display_data = function (json) {
508 OutputArea.prototype.append_display_data = function (json) {
509 var toinsert = this.create_output_area();
509 var toinsert = this.create_output_area();
510 if (this.append_mime_type(json, toinsert)) {
510 if (this.append_mime_type(json, toinsert)) {
511 this._safe_append(toinsert);
511 this._safe_append(toinsert);
512 // If we just output latex, typeset it.
512 // If we just output latex, typeset it.
513 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
513 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
514 this.typeset();
514 this.typeset();
515 }
515 }
516 }
516 }
517 };
517 };
518
518
519 OutputArea.display_order = [
519 OutputArea.display_order = [
520 'application/javascript',
520 'application/javascript',
521 'text/html',
521 'text/html',
522 'text/latex',
522 'text/latex',
523 'image/svg+xml',
523 'image/svg+xml',
524 'image/png',
524 'image/png',
525 'image/jpeg',
525 'image/jpeg',
526 'text/plain'
526 'text/plain'
527 ];
527 ];
528
528
529 OutputArea.prototype.append_mime_type = function (json, element) {
529 OutputArea.prototype.append_mime_type = function (json, element) {
530
530
531 for (var type_i in OutputArea.display_order) {
531 for (var type_i in OutputArea.display_order) {
532 var type = OutputArea.display_order[type_i];
532 var type = OutputArea.display_order[type_i];
533 var append = OutputArea.append_map[type];
533 var append = OutputArea.append_map[type];
534 if ((json[type] !== undefined) && append) {
534 if ((json[type] !== undefined) && append) {
535 var md = json.metadata || {};
535 var md = json.metadata || {};
536 append.apply(this, [json[type], md, element]);
536 append.apply(this, [json[type], md, element]);
537 return true;
537 return true;
538 }
538 }
539 }
539 }
540 return false;
540 return false;
541 };
541 };
542
542
543
543
544 OutputArea.prototype.append_html = function (html, md, element) {
544 OutputArea.prototype.append_html = function (html, md, element) {
545 var type = 'text/html';
545 var type = 'text/html';
546 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
546 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
547 IPython.keyboard_manager.register_events(toinsert);
547 IPython.keyboard_manager.register_events(toinsert);
548 toinsert.append(html);
548 toinsert.append(html);
549 element.append(toinsert);
549 element.append(toinsert);
550 };
550 };
551
551
552
552
553 OutputArea.prototype.append_javascript = function (js, md, container) {
553 OutputArea.prototype.append_javascript = function (js, md, container) {
554 // We just eval the JS code, element appears in the local scope.
554 // We just eval the JS code, element appears in the local scope.
555 var type = 'application/javascript';
555 var type = 'application/javascript';
556 var element = this.create_output_subarea(md, "output_javascript", type);
556 var element = this.create_output_subarea(md, "output_javascript", type);
557 IPython.keyboard_manager.register_events(element);
557 IPython.keyboard_manager.register_events(element);
558 container.append(element);
558 container.append(element);
559 try {
559 try {
560 eval(js);
560 eval(js);
561 } catch(err) {
561 } catch(err) {
562 console.log(err);
562 console.log(err);
563 this._append_javascript_error(err, element);
563 this._append_javascript_error(err, element);
564 }
564 }
565 };
565 };
566
566
567
567
568 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
568 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
569 var type = 'text/plain';
569 var type = 'text/plain';
570 var toinsert = this.create_output_subarea(md, "output_text", type);
570 var toinsert = this.create_output_subarea(md, "output_text", type);
571 // escape ANSI & HTML specials in plaintext:
571 // escape ANSI & HTML specials in plaintext:
572 data = utils.fixConsole(data);
572 data = utils.fixConsole(data);
573 data = utils.fixCarriageReturn(data);
573 data = utils.fixCarriageReturn(data);
574 data = utils.autoLinkUrls(data);
574 data = utils.autoLinkUrls(data);
575 if (extra_class){
575 if (extra_class){
576 toinsert.addClass(extra_class);
576 toinsert.addClass(extra_class);
577 }
577 }
578 toinsert.append($("<pre/>").html(data));
578 toinsert.append($("<pre/>").html(data));
579 element.append(toinsert);
579 element.append(toinsert);
580 };
580 };
581
581
582
582
583 OutputArea.prototype.append_svg = function (svg, md, element) {
583 OutputArea.prototype.append_svg = function (svg, md, element) {
584 var type = 'image/svg+xml';
584 var type = 'image/svg+xml';
585 var toinsert = this.create_output_subarea(md, "output_svg", type);
585 var toinsert = this.create_output_subarea(md, "output_svg", type);
586 toinsert.append(svg);
586 toinsert.append(svg);
587 element.append(toinsert);
587 element.append(toinsert);
588 };
588 };
589
589
590
590
591 OutputArea.prototype._dblclick_to_reset_size = function (img) {
591 OutputArea.prototype._dblclick_to_reset_size = function (img) {
592 // schedule wrapping image in resizable after a delay,
592 // schedule wrapping image in resizable after a delay,
593 // so we don't end up calling resize on a zero-size object
593 // so we don't end up calling resize on a zero-size object
594 var that = this;
594 var that = this;
595 setTimeout(function () {
595 setTimeout(function () {
596 var h0 = img.height();
596 var h0 = img.height();
597 var w0 = img.width();
597 var w0 = img.width();
598 if (!(h0 && w0)) {
598 if (!(h0 && w0)) {
599 // zero size, schedule another timeout
599 // zero size, schedule another timeout
600 that._dblclick_to_reset_size(img);
600 that._dblclick_to_reset_size(img);
601 return;
601 return;
602 }
602 }
603 img.resizable({
603 img.resizable({
604 aspectRatio: true,
604 aspectRatio: true,
605 autoHide: true
605 autoHide: true
606 });
606 });
607 img.dblclick(function () {
607 img.dblclick(function () {
608 // resize wrapper & image together for some reason:
608 // resize wrapper & image together for some reason:
609 img.parent().height(h0);
609 img.parent().height(h0);
610 img.height(h0);
610 img.height(h0);
611 img.parent().width(w0);
611 img.parent().width(w0);
612 img.width(w0);
612 img.width(w0);
613 });
613 });
614 }, 250);
614 }, 250);
615 };
615 };
616
616
617
617
618 OutputArea.prototype.append_png = function (png, md, element) {
618 OutputArea.prototype.append_png = function (png, md, element) {
619 var type = 'image/png';
619 var type = 'image/png';
620 var toinsert = this.create_output_subarea(md, "output_png", type);
620 var toinsert = this.create_output_subarea(md, "output_png", type);
621 var img = $("<img/>");
621 var img = $("<img/>");
622 img[0].setAttribute('src','data:image/png;base64,'+png);
622 img[0].setAttribute('src','data:image/png;base64,'+png);
623 if (md['height']) {
623 if (md['height']) {
624 img[0].setAttribute('height', md['height']);
624 img[0].setAttribute('height', md['height']);
625 }
625 }
626 if (md['width']) {
626 if (md['width']) {
627 img[0].setAttribute('width', md['width']);
627 img[0].setAttribute('width', md['width']);
628 }
628 }
629 this._dblclick_to_reset_size(img);
629 this._dblclick_to_reset_size(img);
630 toinsert.append(img);
630 toinsert.append(img);
631 element.append(toinsert);
631 element.append(toinsert);
632 };
632 };
633
633
634
634
635 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
635 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
636 var type = 'image/jpeg';
636 var type = 'image/jpeg';
637 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
637 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
638 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
638 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
639 if (md['height']) {
639 if (md['height']) {
640 img.attr('height', md['height']);
640 img.attr('height', md['height']);
641 }
641 }
642 if (md['width']) {
642 if (md['width']) {
643 img.attr('width', md['width']);
643 img.attr('width', md['width']);
644 }
644 }
645 this._dblclick_to_reset_size(img);
645 this._dblclick_to_reset_size(img);
646 toinsert.append(img);
646 toinsert.append(img);
647 element.append(toinsert);
647 element.append(toinsert);
648 };
648 };
649
649
650
650
651 OutputArea.prototype.append_latex = function (latex, md, element) {
651 OutputArea.prototype.append_latex = function (latex, md, element) {
652 // This method cannot do the typesetting because the latex first has to
652 // This method cannot do the typesetting because the latex first has to
653 // be on the page.
653 // be on the page.
654 var type = 'text/latex';
654 var type = 'text/latex';
655 var toinsert = this.create_output_subarea(md, "output_latex", type);
655 var toinsert = this.create_output_subarea(md, "output_latex", type);
656 toinsert.append(latex);
656 toinsert.append(latex);
657 element.append(toinsert);
657 element.append(toinsert);
658 };
658 };
659
659
660 OutputArea.append_map = {
660 OutputArea.append_map = {
661 "text/plain" : OutputArea.prototype.append_text,
661 "text/plain" : OutputArea.prototype.append_text,
662 "text/html" : OutputArea.prototype.append_html,
662 "text/html" : OutputArea.prototype.append_html,
663 "image/svg+xml" : OutputArea.prototype.append_svg,
663 "image/svg+xml" : OutputArea.prototype.append_svg,
664 "image/png" : OutputArea.prototype.append_png,
664 "image/png" : OutputArea.prototype.append_png,
665 "image/jpeg" : OutputArea.prototype.append_jpeg,
665 "image/jpeg" : OutputArea.prototype.append_jpeg,
666 "text/latex" : OutputArea.prototype.append_latex,
666 "text/latex" : OutputArea.prototype.append_latex,
667 "application/json" : OutputArea.prototype.append_json,
667 "application/json" : OutputArea.prototype.append_json,
668 "application/javascript" : OutputArea.prototype.append_javascript,
668 "application/javascript" : OutputArea.prototype.append_javascript,
669 };
669 };
670
670
671 OutputArea.prototype.append_raw_input = function (msg) {
671 OutputArea.prototype.append_raw_input = function (msg) {
672 var that = this;
672 var that = this;
673 this.expand();
673 this.expand();
674 var content = msg.content;
674 var content = msg.content;
675 var area = this.create_output_area();
675 var area = this.create_output_area();
676
676
677 // disable any other raw_inputs, if they are left around
677 // disable any other raw_inputs, if they are left around
678 $("div.output_subarea.raw_input").remove();
678 $("div.output_subarea.raw_input").remove();
679
679
680 area.append(
680 area.append(
681 $("<div/>")
681 $("<div/>")
682 .addClass("box-flex1 output_subarea raw_input")
682 .addClass("box-flex1 output_subarea raw_input")
683 .append(
683 .append(
684 $("<span/>")
684 $("<span/>")
685 .addClass("input_prompt")
685 .addClass("input_prompt")
686 .text(content.prompt)
686 .text(content.prompt)
687 )
687 )
688 .append(
688 .append(
689 $("<input/>")
689 $("<input/>")
690 .addClass("raw_input")
690 .addClass("raw_input")
691 .attr('type', 'text')
691 .attr('type', 'text')
692 .attr("size", 47)
692 .attr("size", 47)
693 .keydown(function (event, ui) {
693 .keydown(function (event, ui) {
694 // make sure we submit on enter,
694 // make sure we submit on enter,
695 // and don't re-execute the *cell* on shift-enter
695 // and don't re-execute the *cell* on shift-enter
696 if (event.which === utils.keycodes.ENTER) {
696 if (event.which === utils.keycodes.ENTER) {
697 that._submit_raw_input();
697 that._submit_raw_input();
698 return false;
698 return false;
699 }
699 }
700 })
700 })
701 )
701 )
702 );
702 );
703
703
704 this.element.append(area);
704 this.element.append(area);
705 var raw_input = area.find('input.raw_input');
705 var raw_input = area.find('input.raw_input');
706 // Register events that enable/disable the keyboard manager while raw
706 // Register events that enable/disable the keyboard manager while raw
707 // input is focused.
707 // input is focused.
708 IPython.keyboard_manager.register_events(raw_input);
708 IPython.keyboard_manager.register_events(raw_input);
709 // Note, the following line used to read raw_input.focus().focus().
709 // Note, the following line used to read raw_input.focus().focus().
710 // This seemed to be needed otherwise only the cell would be focused.
710 // This seemed to be needed otherwise only the cell would be focused.
711 // But with the modal UI, this seems to work fine with one call to focus().
711 // But with the modal UI, this seems to work fine with one call to focus().
712 raw_input.focus();
712 raw_input.focus();
713 }
713 }
714
714
715 OutputArea.prototype._submit_raw_input = function (evt) {
715 OutputArea.prototype._submit_raw_input = function (evt) {
716 var container = this.element.find("div.raw_input");
716 var container = this.element.find("div.raw_input");
717 var theprompt = container.find("span.input_prompt");
717 var theprompt = container.find("span.input_prompt");
718 var theinput = container.find("input.raw_input");
718 var theinput = container.find("input.raw_input");
719 var value = theinput.val();
719 var value = theinput.val();
720 var content = {
720 var content = {
721 output_type : 'stream',
721 output_type : 'stream',
722 name : 'stdout',
722 name : 'stdout',
723 text : theprompt.text() + value + '\n'
723 text : theprompt.text() + value + '\n'
724 }
724 }
725 // remove form container
725 // remove form container
726 container.parent().remove();
726 container.parent().remove();
727 // replace with plaintext version in stdout
727 // replace with plaintext version in stdout
728 this.append_output(content, false);
728 this.append_output(content, false);
729 $([IPython.events]).trigger('send_input_reply.Kernel', value);
729 $([IPython.events]).trigger('send_input_reply.Kernel', value);
730 }
730 }
731
731
732
732
733 OutputArea.prototype.handle_clear_output = function (msg) {
733 OutputArea.prototype.handle_clear_output = function (msg) {
734 this.clear_output(msg.content.wait);
734 this.clear_output(msg.content.wait);
735 };
735 };
736
736
737
737
738 OutputArea.prototype.clear_output = function(wait) {
738 OutputArea.prototype.clear_output = function(wait) {
739 if (wait) {
739 if (wait) {
740
740
741 // If a clear is queued, clear before adding another to the queue.
741 // If a clear is queued, clear before adding another to the queue.
742 if (this.clear_queued) {
742 if (this.clear_queued) {
743 this.clear_output(false);
743 this.clear_output(false);
744 };
744 };
745
745
746 this.clear_queued = true;
746 this.clear_queued = true;
747 } else {
747 } else {
748
748
749 // Fix the output div's height if the clear_output is waiting for
749 // Fix the output div's height if the clear_output is waiting for
750 // new output (it is being used in an animation).
750 // new output (it is being used in an animation).
751 if (this.clear_queued) {
751 if (this.clear_queued) {
752 var height = this.element.height();
752 var height = this.element.height();
753 this.element.height(height);
753 this.element.height(height);
754 this.clear_queued = false;
754 this.clear_queued = false;
755 }
755 }
756
756
757 // clear all, no need for logic
757 // clear all, no need for logic
758 this.element.html("");
758 this.element.html("");
759 this.outputs = [];
759 this.outputs = [];
760 this.unscroll_area();
760 this.unscroll_area();
761 return;
761 return;
762 };
762 };
763 };
763 };
764
764
765
765
766 // JSON serialization
766 // JSON serialization
767
767
768 OutputArea.prototype.fromJSON = function (outputs) {
768 OutputArea.prototype.fromJSON = function (outputs) {
769 var len = outputs.length;
769 var len = outputs.length;
770 var data;
770 var data;
771
771
772 // We don't want to display javascript on load, so remove it from the
772 // We don't want to display javascript on load, so remove it from the
773 // display order for the duration of this function call, but be sure to
773 // display order for the duration of this function call, but be sure to
774 // put it back in there so incoming messages that contain javascript
774 // put it back in there so incoming messages that contain javascript
775 // representations get displayed
775 // representations get displayed
776 var js_index = OutputArea.display_order.indexOf('application/javascript');
776 var js_index = OutputArea.display_order.indexOf('application/javascript');
777 OutputArea.display_order.splice(js_index, 1);
777 OutputArea.display_order.splice(js_index, 1);
778
778
779 for (var i=0; i<len; i++) {
779 for (var i=0; i<len; i++) {
780 data = outputs[i];
780 data = outputs[i];
781 var msg_type = data.output_type;
781 var msg_type = data.output_type;
782 if (msg_type === "display_data" || msg_type === "pyout") {
782 if (msg_type === "display_data" || msg_type === "pyout") {
783 // convert short keys to mime keys
783 // convert short keys to mime keys
784 // TODO: remove mapping of short keys when we update to nbformat 4
784 // TODO: remove mapping of short keys when we update to nbformat 4
785 data = this.rename_keys(data, OutputArea.mime_map_r);
785 data = this.rename_keys(data, OutputArea.mime_map_r);
786 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
786 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
787 }
787 }
788
788
789 this.append_output(data);
789 this.append_output(data);
790 }
790 }
791
791
792 // reinsert javascript into display order, see note above
792 // reinsert javascript into display order, see note above
793 OutputArea.display_order.splice(js_index, 0, 'application/javascript');
793 OutputArea.display_order.splice(js_index, 0, 'application/javascript');
794 };
794 };
795
795
796
796
797 OutputArea.prototype.toJSON = function () {
797 OutputArea.prototype.toJSON = function () {
798 var outputs = [];
798 var outputs = [];
799 var len = this.outputs.length;
799 var len = this.outputs.length;
800 var data;
800 var data;
801 for (var i=0; i<len; i++) {
801 for (var i=0; i<len; i++) {
802 data = this.outputs[i];
802 data = this.outputs[i];
803 var msg_type = data.output_type;
803 var msg_type = data.output_type;
804 if (msg_type === "display_data" || msg_type === "pyout") {
804 if (msg_type === "display_data" || msg_type === "pyout") {
805 // convert mime keys to short keys
805 // convert mime keys to short keys
806 data = this.rename_keys(data, OutputArea.mime_map);
806 data = this.rename_keys(data, OutputArea.mime_map);
807 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
807 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
808 }
808 }
809 outputs[i] = data;
809 outputs[i] = data;
810 }
810 }
811 return outputs;
811 return outputs;
812 };
812 };
813
813
814
814
815 IPython.OutputArea = OutputArea;
815 IPython.OutputArea = OutputArea;
816
816
817 return IPython;
817 return IPython;
818
818
819 }(IPython));
819 }(IPython));
@@ -1,133 +1,133 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // QuickHelp button
9 // QuickHelp button
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var QuickHelp = function (selector) {
15 var QuickHelp = function (selector) {
16 };
16 };
17
17
18 QuickHelp.prototype.show_keyboard_shortcuts = function () {
18 QuickHelp.prototype.show_keyboard_shortcuts = function () {
19 // toggles display of keyboard shortcut dialog
19 // toggles display of keyboard shortcut dialog
20 var that = this;
20 var that = this;
21 if ( this.shortcut_dialog ){
21 if ( this.shortcut_dialog ){
22 // if dialog is already shown, close it
22 // if dialog is already shown, close it
23 $(this.shortcut_dialog).modal("toggle");
23 $(this.shortcut_dialog).modal("toggle");
24 return;
24 return;
25 }
25 }
26 var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
26 var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
27 var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
27 var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
28 var help, shortcut;
28 var help, shortcut;
29 var i, half, n;
29 var i, half, n;
30 var element = $('<div/>');
30 var element = $('<div/>');
31
31
32 // The documentation
32 // The documentation
33 var doc = $('<div/>').addClass('alert');
33 var doc = $('<div/>').addClass('alert');
34 doc.append(
34 doc.append(
35 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times')
35 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
36 ).append(
36 ).append(
37 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
37 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
38 'allow you the type code/text into a cell and is indicated by a green cell '+
38 'allow you the type code/text into a cell and is indicated by a green cell '+
39 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
39 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
40 'and is indicated by a grey cell border.'
40 'and is indicated by a grey cell border.'
41 )
41 )
42 element.append(doc);
42 element.append(doc);
43
43
44 // Command mode
44 // Command mode
45 var cmd_div = this.build_command_help();
45 var cmd_div = this.build_command_help();
46 element.append(cmd_div);
46 element.append(cmd_div);
47
47
48 // Edit mode
48 // Edit mode
49 var edit_div = this.build_edit_help();
49 var edit_div = this.build_edit_help();
50 element.append(edit_div);
50 element.append(edit_div);
51
51
52 this.shortcut_dialog = IPython.dialog.modal({
52 this.shortcut_dialog = IPython.dialog.modal({
53 title : "Keyboard shortcuts",
53 title : "Keyboard shortcuts",
54 body : element,
54 body : element,
55 destroy : false,
55 destroy : false,
56 buttons : {
56 buttons : {
57 Close : {}
57 Close : {}
58 }
58 }
59 });
59 });
60 };
60 };
61
61
62 QuickHelp.prototype.build_command_help = function () {
62 QuickHelp.prototype.build_command_help = function () {
63 var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
63 var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
64 var help, shortcut;
64 var help, shortcut;
65 var i, half, n;
65 var i, half, n;
66
66
67 // Command mode
67 // Command mode
68 var cmd_div = $('<div/>').append($('<h4>Command Mode (press ESC to enable)</h4>'));
68 var cmd_div = $('<div/>').append($('<h4>Command Mode (press ESC to enable)</h4>'));
69 var cmd_sub_div = $('<div/>').addClass('hbox');
69 var cmd_sub_div = $('<div/>').addClass('hbox');
70 var cmd_col1 = $('<div/>').addClass('box-flex0');
70 var cmd_col1 = $('<div/>').addClass('box-flex0');
71 var cmd_col2 = $('<div/>').addClass('box-flex0');
71 var cmd_col2 = $('<div/>').addClass('box-flex0');
72 n = command_shortcuts.length;
72 n = command_shortcuts.length;
73 half = ~~(n/2); // Truncate :)
73 half = ~~(n/2); // Truncate :)
74 for (i=0; i<half; i++) {
74 for (i=0; i<half; i++) {
75 help = command_shortcuts[i]['help'];
75 help = command_shortcuts[i]['help'];
76 shortcut = command_shortcuts[i]['shortcut'];
76 shortcut = command_shortcuts[i]['shortcut'];
77 cmd_col1.append($('<div>').addClass('quickhelp').
77 cmd_col1.append($('<div>').addClass('quickhelp').
78 append($('<span/>').addClass('shortcut_key').html(shortcut)).
78 append($('<span/>').addClass('shortcut_key').text(shortcut)).
79 append($('<span/>').addClass('shortcut_descr').html(' : ' + help))
79 append($('<span/>').addClass('shortcut_descr').text(' : ' + help))
80 );
80 );
81 };
81 };
82 for (i=half; i<n; i++) {
82 for (i=half; i<n; i++) {
83 help = command_shortcuts[i]['help'];
83 help = command_shortcuts[i]['help'];
84 shortcut = command_shortcuts[i]['shortcut'];
84 shortcut = command_shortcuts[i]['shortcut'];
85 cmd_col2.append($('<div>').addClass('quickhelp').
85 cmd_col2.append($('<div>').addClass('quickhelp').
86 append($('<span/>').addClass('shortcut_key').html(shortcut)).
86 append($('<span/>').addClass('shortcut_key').text(shortcut)).
87 append($('<span/>').addClass('shortcut_descr').html(' : ' + help))
87 append($('<span/>').addClass('shortcut_descr').text(' : ' + help))
88 );
88 );
89 };
89 };
90 cmd_sub_div.append(cmd_col1).append(cmd_col2);
90 cmd_sub_div.append(cmd_col1).append(cmd_col2);
91 cmd_div.append(cmd_sub_div);
91 cmd_div.append(cmd_sub_div);
92 return cmd_div;
92 return cmd_div;
93 }
93 }
94
94
95 QuickHelp.prototype.build_edit_help = function () {
95 QuickHelp.prototype.build_edit_help = function () {
96 var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
96 var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
97 var help, shortcut;
97 var help, shortcut;
98 var i, half, n;
98 var i, half, n;
99
99
100 // Edit mode
100 // Edit mode
101 var edit_div = $('<div/>').append($('<h4>Edit Mode (press ENTER to enable)</h4>'));
101 var edit_div = $('<div/>').append($('<h4>Edit Mode (press ENTER to enable)</h4>'));
102 var edit_sub_div = $('<div/>').addClass('hbox');
102 var edit_sub_div = $('<div/>').addClass('hbox');
103 var edit_col1 = $('<div/>').addClass('box-flex0');
103 var edit_col1 = $('<div/>').addClass('box-flex0');
104 var edit_col2 = $('<div/>').addClass('box-flex0');
104 var edit_col2 = $('<div/>').addClass('box-flex0');
105 n = edit_shortcuts.length;
105 n = edit_shortcuts.length;
106 half = ~~(n/2); // Truncate :)
106 half = ~~(n/2); // Truncate :)
107 for (i=0; i<half; i++) {
107 for (i=0; i<half; i++) {
108 help = edit_shortcuts[i]['help'];
108 help = edit_shortcuts[i]['help'];
109 shortcut = edit_shortcuts[i]['shortcut'];
109 shortcut = edit_shortcuts[i]['shortcut'];
110 edit_col1.append($('<div>').addClass('quickhelp').
110 edit_col1.append($('<div>').addClass('quickhelp').
111 append($('<span/>').addClass('shortcut_key').html(shortcut)).
111 append($('<span/>').addClass('shortcut_key').text(shortcut)).
112 append($('<span/>').addClass('shortcut_descr').html(' : ' + help))
112 append($('<span/>').addClass('shortcut_descr').text(' : ' + help))
113 );
113 );
114 };
114 };
115 for (i=half; i<n; i++) {
115 for (i=half; i<n; i++) {
116 help = edit_shortcuts[i]['help'];
116 help = edit_shortcuts[i]['help'];
117 shortcut = edit_shortcuts[i]['shortcut'];
117 shortcut = edit_shortcuts[i]['shortcut'];
118 edit_col2.append($('<div>').addClass('quickhelp').
118 edit_col2.append($('<div>').addClass('quickhelp').
119 append($('<span/>').addClass('shortcut_key').html(shortcut)).
119 append($('<span/>').addClass('shortcut_key').text(shortcut)).
120 append($('<span/>').addClass('shortcut_descr').html(' : ' + help))
120 append($('<span/>').addClass('shortcut_descr').text(' : ' + help))
121 );
121 );
122 };
122 };
123 edit_sub_div.append(edit_col1).append(edit_col2);
123 edit_sub_div.append(edit_col1).append(edit_col2);
124 edit_div.append(edit_sub_div);
124 edit_div.append(edit_sub_div);
125 return edit_div;
125 return edit_div;
126 }
126 }
127
127
128 // Set module variables
128 // Set module variables
129 IPython.QuickHelp = QuickHelp;
129 IPython.QuickHelp = QuickHelp;
130
130
131 return IPython;
131 return IPython;
132
132
133 }(IPython));
133 }(IPython));
@@ -1,173 +1,173 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // SaveWidget
9 // SaveWidget
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var SaveWidget = function (selector) {
17 var SaveWidget = function (selector) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 };
24 };
25
25
26
26
27 SaveWidget.prototype.style = function () {
27 SaveWidget.prototype.style = function () {
28 };
28 };
29
29
30
30
31 SaveWidget.prototype.bind_events = function () {
31 SaveWidget.prototype.bind_events = function () {
32 var that = this;
32 var that = this;
33 this.element.find('span#notebook_name').click(function () {
33 this.element.find('span#notebook_name').click(function () {
34 that.rename_notebook();
34 that.rename_notebook();
35 });
35 });
36 this.element.find('span#notebook_name').hover(function () {
36 this.element.find('span#notebook_name').hover(function () {
37 $(this).addClass("ui-state-hover");
37 $(this).addClass("ui-state-hover");
38 }, function () {
38 }, function () {
39 $(this).removeClass("ui-state-hover");
39 $(this).removeClass("ui-state-hover");
40 });
40 });
41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
42 that.update_notebook_name();
42 that.update_notebook_name();
43 that.update_document_title();
43 that.update_document_title();
44 });
44 });
45 $([IPython.events]).on('notebook_saved.Notebook', function () {
45 $([IPython.events]).on('notebook_saved.Notebook', function () {
46 that.update_notebook_name();
46 that.update_notebook_name();
47 that.update_document_title();
47 that.update_document_title();
48 });
48 });
49 $([IPython.events]).on('notebook_renamed.Notebook', function () {
49 $([IPython.events]).on('notebook_renamed.Notebook', function () {
50 that.update_notebook_name();
50 that.update_notebook_name();
51 that.update_document_title();
51 that.update_document_title();
52 that.update_address_bar();
52 that.update_address_bar();
53 });
53 });
54 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
54 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
55 that.set_save_status('Autosave Failed!');
55 that.set_save_status('Autosave Failed!');
56 });
56 });
57 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
57 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
58 that.set_last_checkpoint(data[0]);
58 that.set_last_checkpoint(data[0]);
59 });
59 });
60
60
61 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
61 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
62 that.set_last_checkpoint(data);
62 that.set_last_checkpoint(data);
63 });
63 });
64 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
64 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
65 that.set_autosaved(data.value);
65 that.set_autosaved(data.value);
66 });
66 });
67 };
67 };
68
68
69
69
70 SaveWidget.prototype.rename_notebook = function () {
70 SaveWidget.prototype.rename_notebook = function () {
71 var that = this;
71 var that = this;
72 var dialog = $('<div/>').append(
72 var dialog = $('<div/>').append(
73 $("<p/>").addClass("rename-message")
73 $("<p/>").addClass("rename-message")
74 .html('Enter a new notebook name:')
74 .text('Enter a new notebook name:')
75 ).append(
75 ).append(
76 $("<br/>")
76 $("<br/>")
77 ).append(
77 ).append(
78 $('<input/>').attr('type','text').attr('size','25')
78 $('<input/>').attr('type','text').attr('size','25')
79 .val(IPython.notebook.get_notebook_name())
79 .val(IPython.notebook.get_notebook_name())
80 );
80 );
81 IPython.dialog.modal({
81 IPython.dialog.modal({
82 title: "Rename Notebook",
82 title: "Rename Notebook",
83 body: dialog,
83 body: dialog,
84 buttons : {
84 buttons : {
85 "Cancel": {},
85 "Cancel": {},
86 "OK": {
86 "OK": {
87 class: "btn-primary",
87 class: "btn-primary",
88 click: function () {
88 click: function () {
89 var new_name = $(this).find('input').val();
89 var new_name = $(this).find('input').val();
90 if (!IPython.notebook.test_notebook_name(new_name)) {
90 if (!IPython.notebook.test_notebook_name(new_name)) {
91 $(this).find('.rename-message').html(
91 $(this).find('.rename-message').text(
92 "Invalid notebook name. Notebook names must "+
92 "Invalid notebook name. Notebook names must "+
93 "have 1 or more characters and can contain any characters " +
93 "have 1 or more characters and can contain any characters " +
94 "except :/\\. Please enter a new notebook name:"
94 "except :/\\. Please enter a new notebook name:"
95 );
95 );
96 return false;
96 return false;
97 } else {
97 } else {
98 IPython.notebook.rename(new_name);
98 IPython.notebook.rename(new_name);
99 }
99 }
100 }}
100 }}
101 },
101 },
102 open : function (event, ui) {
102 open : function (event, ui) {
103 var that = $(this);
103 var that = $(this);
104 // Upon ENTER, click the OK button.
104 // Upon ENTER, click the OK button.
105 that.find('input[type="text"]').keydown(function (event, ui) {
105 that.find('input[type="text"]').keydown(function (event, ui) {
106 if (event.which === utils.keycodes.ENTER) {
106 if (event.which === utils.keycodes.ENTER) {
107 that.find('.btn-primary').first().click();
107 that.find('.btn-primary').first().click();
108 return false;
108 return false;
109 }
109 }
110 });
110 });
111 that.find('input[type="text"]').focus().select();
111 that.find('input[type="text"]').focus().select();
112 }
112 }
113 });
113 });
114 }
114 }
115
115
116
116
117 SaveWidget.prototype.update_notebook_name = function () {
117 SaveWidget.prototype.update_notebook_name = function () {
118 var nbname = IPython.notebook.get_notebook_name();
118 var nbname = IPython.notebook.get_notebook_name();
119 this.element.find('span#notebook_name').html(nbname);
119 this.element.find('span#notebook_name').text(nbname);
120 };
120 };
121
121
122
122
123 SaveWidget.prototype.update_document_title = function () {
123 SaveWidget.prototype.update_document_title = function () {
124 var nbname = IPython.notebook.get_notebook_name();
124 var nbname = IPython.notebook.get_notebook_name();
125 document.title = nbname;
125 document.title = nbname;
126 };
126 };
127
127
128 SaveWidget.prototype.update_address_bar = function(){
128 SaveWidget.prototype.update_address_bar = function(){
129 var nbname = IPython.notebook.notebook_name;
129 var nbname = IPython.notebook.notebook_name;
130 var path = IPython.notebook.notebookPath();
130 var path = IPython.notebook.notebookPath();
131 var state = {path : utils.url_join_encode(path, nbname)};
131 var state = {path : utils.url_join_encode(path, nbname)};
132 window.history.replaceState(state, "", utils.url_join_encode(
132 window.history.replaceState(state, "", utils.url_join_encode(
133 "/notebooks",
133 "/notebooks",
134 path,
134 path,
135 nbname)
135 nbname)
136 );
136 );
137 }
137 }
138
138
139
139
140 SaveWidget.prototype.set_save_status = function (msg) {
140 SaveWidget.prototype.set_save_status = function (msg) {
141 this.element.find('span#autosave_status').html(msg);
141 this.element.find('span#autosave_status').text(msg);
142 }
142 }
143
143
144 SaveWidget.prototype.set_checkpoint_status = function (msg) {
144 SaveWidget.prototype.set_checkpoint_status = function (msg) {
145 this.element.find('span#checkpoint_status').html(msg);
145 this.element.find('span#checkpoint_status').text(msg);
146 }
146 }
147
147
148 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
148 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
149 if (!checkpoint) {
149 if (!checkpoint) {
150 this.set_checkpoint_status("");
150 this.set_checkpoint_status("");
151 return;
151 return;
152 }
152 }
153 var d = new Date(checkpoint.last_modified);
153 var d = new Date(checkpoint.last_modified);
154 this.set_checkpoint_status(
154 this.set_checkpoint_status(
155 "Last Checkpoint: " + d.format('mmm dd HH:MM')
155 "Last Checkpoint: " + d.format('mmm dd HH:MM')
156 );
156 );
157 }
157 }
158
158
159 SaveWidget.prototype.set_autosaved = function (dirty) {
159 SaveWidget.prototype.set_autosaved = function (dirty) {
160 if (dirty) {
160 if (dirty) {
161 this.set_save_status("(unsaved changes)");
161 this.set_save_status("(unsaved changes)");
162 } else {
162 } else {
163 this.set_save_status("(autosaved)");
163 this.set_save_status("(autosaved)");
164 }
164 }
165 };
165 };
166
166
167
167
168 IPython.SaveWidget = SaveWidget;
168 IPython.SaveWidget = SaveWidget;
169
169
170 return IPython;
170 return IPython;
171
171
172 }(IPython));
172 }(IPython));
173
173
@@ -1,199 +1,199 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // NotebookList
9 // NotebookList
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var ClusterList = function (selector) {
17 var ClusterList = function (selector) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 };
24 };
25
25
26 ClusterList.prototype.baseProjectUrl = function(){
26 ClusterList.prototype.baseProjectUrl = function(){
27 return this._baseProjectUrl || $('body').data('baseProjectUrl');
27 return this._baseProjectUrl || $('body').data('baseProjectUrl');
28 };
28 };
29
29
30 ClusterList.prototype.style = function () {
30 ClusterList.prototype.style = function () {
31 $('#cluster_list').addClass('list_container');
31 $('#cluster_list').addClass('list_container');
32 $('#cluster_toolbar').addClass('list_toolbar');
32 $('#cluster_toolbar').addClass('list_toolbar');
33 $('#cluster_list_info').addClass('toolbar_info');
33 $('#cluster_list_info').addClass('toolbar_info');
34 $('#cluster_buttons').addClass('toolbar_buttons');
34 $('#cluster_buttons').addClass('toolbar_buttons');
35 };
35 };
36
36
37
37
38 ClusterList.prototype.bind_events = function () {
38 ClusterList.prototype.bind_events = function () {
39 var that = this;
39 var that = this;
40 $('#refresh_cluster_list').click(function () {
40 $('#refresh_cluster_list').click(function () {
41 that.load_list();
41 that.load_list();
42 });
42 });
43 };
43 };
44
44
45
45
46 ClusterList.prototype.load_list = function () {
46 ClusterList.prototype.load_list = function () {
47 var settings = {
47 var settings = {
48 processData : false,
48 processData : false,
49 cache : false,
49 cache : false,
50 type : "GET",
50 type : "GET",
51 dataType : "json",
51 dataType : "json",
52 success : $.proxy(this.load_list_success, this)
52 success : $.proxy(this.load_list_success, this)
53 };
53 };
54 var url = utils.url_join_encode(this.baseProjectUrl(), 'clusters');
54 var url = utils.url_join_encode(this.baseProjectUrl(), 'clusters');
55 $.ajax(url, settings);
55 $.ajax(url, settings);
56 };
56 };
57
57
58
58
59 ClusterList.prototype.clear_list = function () {
59 ClusterList.prototype.clear_list = function () {
60 this.element.children('.list_item').remove();
60 this.element.children('.list_item').remove();
61 };
61 };
62
62
63 ClusterList.prototype.load_list_success = function (data, status, xhr) {
63 ClusterList.prototype.load_list_success = function (data, status, xhr) {
64 this.clear_list();
64 this.clear_list();
65 var len = data.length;
65 var len = data.length;
66 for (var i=0; i<len; i++) {
66 for (var i=0; i<len; i++) {
67 var element = $('<div/>');
67 var element = $('<div/>');
68 var item = new ClusterItem(element);
68 var item = new ClusterItem(element);
69 item.update_state(data[i]);
69 item.update_state(data[i]);
70 element.data('item', item);
70 element.data('item', item);
71 this.element.append(element);
71 this.element.append(element);
72 }
72 }
73 };
73 };
74
74
75
75
76 var ClusterItem = function (element) {
76 var ClusterItem = function (element) {
77 this.element = $(element);
77 this.element = $(element);
78 this.data = null;
78 this.data = null;
79 this.style();
79 this.style();
80 };
80 };
81
81
82 ClusterItem.prototype.baseProjectUrl = function(){
82 ClusterItem.prototype.baseProjectUrl = function(){
83 return this._baseProjectUrl || $('body').data('baseProjectUrl');
83 return this._baseProjectUrl || $('body').data('baseProjectUrl');
84 };
84 };
85
85
86
86
87 ClusterItem.prototype.style = function () {
87 ClusterItem.prototype.style = function () {
88 this.element.addClass('list_item').addClass("row-fluid");
88 this.element.addClass('list_item').addClass("row-fluid");
89 };
89 };
90
90
91 ClusterItem.prototype.update_state = function (data) {
91 ClusterItem.prototype.update_state = function (data) {
92 this.data = data;
92 this.data = data;
93 if (data.status === 'running') {
93 if (data.status === 'running') {
94 this.state_running();
94 this.state_running();
95 } else if (data.status === 'stopped') {
95 } else if (data.status === 'stopped') {
96 this.state_stopped();
96 this.state_stopped();
97 }
97 }
98 };
98 };
99
99
100
100
101 ClusterItem.prototype.state_stopped = function () {
101 ClusterItem.prototype.state_stopped = function () {
102 var that = this;
102 var that = this;
103 var profile_col = $('<span/>').addClass('profile_col span4').text(this.data.profile);
103 var profile_col = $('<span/>').addClass('profile_col span4').text(this.data.profile);
104 var status_col = $('<span/>').addClass('status_col span3').html('stopped');
104 var status_col = $('<span/>').addClass('status_col span3').text('stopped');
105 var engines_col = $('<span/>').addClass('engine_col span3');
105 var engines_col = $('<span/>').addClass('engine_col span3');
106 var input = $('<input/>').attr('type','number')
106 var input = $('<input/>').attr('type','number')
107 .attr('min',1)
107 .attr('min',1)
108 .attr('size',3)
108 .attr('size',3)
109 .addClass('engine_num_input');
109 .addClass('engine_num_input');
110 engines_col.append(input);
110 engines_col.append(input);
111 var start_button = $('<button/>').addClass("btn btn-mini").text("Start");
111 var start_button = $('<button/>').addClass("btn btn-mini").text("Start");
112 var action_col = $('<span/>').addClass('action_col span2').append(
112 var action_col = $('<span/>').addClass('action_col span2').append(
113 $("<span/>").addClass("item_buttons btn-group").append(
113 $("<span/>").addClass("item_buttons btn-group").append(
114 start_button
114 start_button
115 )
115 )
116 );
116 );
117 this.element.empty()
117 this.element.empty()
118 .append(profile_col)
118 .append(profile_col)
119 .append(status_col)
119 .append(status_col)
120 .append(engines_col)
120 .append(engines_col)
121 .append(action_col);
121 .append(action_col);
122 start_button.click(function (e) {
122 start_button.click(function (e) {
123 var n = that.element.find('.engine_num_input').val();
123 var n = that.element.find('.engine_num_input').val();
124 if (!/^\d+$/.test(n) && n.length>0) {
124 if (!/^\d+$/.test(n) && n.length>0) {
125 status_col.html('invalid engine #');
125 status_col.text('invalid engine #');
126 } else {
126 } else {
127 var settings = {
127 var settings = {
128 cache : false,
128 cache : false,
129 data : {n:n},
129 data : {n:n},
130 type : "POST",
130 type : "POST",
131 dataType : "json",
131 dataType : "json",
132 success : function (data, status, xhr) {
132 success : function (data, status, xhr) {
133 that.update_state(data);
133 that.update_state(data);
134 },
134 },
135 error : function (data, status, xhr) {
135 error : function (data, status, xhr) {
136 status_col.html("error starting cluster");
136 status_col.text("error starting cluster");
137 }
137 }
138 };
138 };
139 status_col.html('starting');
139 status_col.text('starting');
140 var url = utils.url_join_encode(
140 var url = utils.url_join_encode(
141 that.baseProjectUrl(),
141 that.baseProjectUrl(),
142 'clusters',
142 'clusters',
143 that.data.profile,
143 that.data.profile,
144 'start'
144 'start'
145 );
145 );
146 $.ajax(url, settings);
146 $.ajax(url, settings);
147 }
147 }
148 });
148 });
149 };
149 };
150
150
151
151
152 ClusterItem.prototype.state_running = function () {
152 ClusterItem.prototype.state_running = function () {
153 var that = this;
153 var that = this;
154 var profile_col = $('<span/>').addClass('profile_col span4').text(this.data.profile);
154 var profile_col = $('<span/>').addClass('profile_col span4').text(this.data.profile);
155 var status_col = $('<span/>').addClass('status_col span3').html('running');
155 var status_col = $('<span/>').addClass('status_col span3').text('running');
156 var engines_col = $('<span/>').addClass('engines_col span3').html(this.data.n);
156 var engines_col = $('<span/>').addClass('engines_col span3').text(this.data.n);
157 var stop_button = $('<button/>').addClass("btn btn-mini").text("Stop");
157 var stop_button = $('<button/>').addClass("btn btn-mini").text("Stop");
158 var action_col = $('<span/>').addClass('action_col span2').append(
158 var action_col = $('<span/>').addClass('action_col span2').append(
159 $("<span/>").addClass("item_buttons btn-group").append(
159 $("<span/>").addClass("item_buttons btn-group").append(
160 stop_button
160 stop_button
161 )
161 )
162 );
162 );
163 this.element.empty()
163 this.element.empty()
164 .append(profile_col)
164 .append(profile_col)
165 .append(status_col)
165 .append(status_col)
166 .append(engines_col)
166 .append(engines_col)
167 .append(action_col);
167 .append(action_col);
168 stop_button.click(function (e) {
168 stop_button.click(function (e) {
169 var settings = {
169 var settings = {
170 cache : false,
170 cache : false,
171 type : "POST",
171 type : "POST",
172 dataType : "json",
172 dataType : "json",
173 success : function (data, status, xhr) {
173 success : function (data, status, xhr) {
174 that.update_state(data);
174 that.update_state(data);
175 },
175 },
176 error : function (data, status, xhr) {
176 error : function (data, status, xhr) {
177 console.log('error',data);
177 console.log('error',data);
178 status_col.html("error stopping cluster");
178 status_col.text("error stopping cluster");
179 }
179 }
180 };
180 };
181 status_col.html('stopping');
181 status_col.text('stopping');
182 var url = utils.url_join_encode(
182 var url = utils.url_join_encode(
183 that.baseProjectUrl(),
183 that.baseProjectUrl(),
184 'clusters',
184 'clusters',
185 that.data.profile,
185 that.data.profile,
186 'stop'
186 'stop'
187 );
187 );
188 $.ajax(url, settings);
188 $.ajax(url, settings);
189 });
189 });
190 };
190 };
191
191
192
192
193 IPython.ClusterList = ClusterList;
193 IPython.ClusterList = ClusterList;
194 IPython.ClusterItem = ClusterItem;
194 IPython.ClusterItem = ClusterItem;
195
195
196 return IPython;
196 return IPython;
197
197
198 }(IPython));
198 }(IPython));
199
199
@@ -1,397 +1,397 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // NotebookList
9 // NotebookList
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var NotebookList = function (selector) {
17 var NotebookList = function (selector) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 this.notebooks_list = [];
24 this.notebooks_list = [];
25 this.sessions = {};
25 this.sessions = {};
26 };
26 };
27
27
28 NotebookList.prototype.baseProjectUrl = function () {
28 NotebookList.prototype.baseProjectUrl = function () {
29 return $('body').data('baseProjectUrl');
29 return $('body').data('baseProjectUrl');
30 };
30 };
31
31
32 NotebookList.prototype.notebookPath = function() {
32 NotebookList.prototype.notebookPath = function() {
33 return $('body').data('notebookPath');
33 return $('body').data('notebookPath');
34 };
34 };
35
35
36 NotebookList.prototype.style = function () {
36 NotebookList.prototype.style = function () {
37 $('#notebook_toolbar').addClass('list_toolbar');
37 $('#notebook_toolbar').addClass('list_toolbar');
38 $('#drag_info').addClass('toolbar_info');
38 $('#drag_info').addClass('toolbar_info');
39 $('#notebook_buttons').addClass('toolbar_buttons');
39 $('#notebook_buttons').addClass('toolbar_buttons');
40 $('#notebook_list_header').addClass('list_header');
40 $('#notebook_list_header').addClass('list_header');
41 this.element.addClass("list_container");
41 this.element.addClass("list_container");
42 };
42 };
43
43
44
44
45 NotebookList.prototype.bind_events = function () {
45 NotebookList.prototype.bind_events = function () {
46 var that = this;
46 var that = this;
47 $('#refresh_notebook_list').click(function () {
47 $('#refresh_notebook_list').click(function () {
48 that.load_list();
48 that.load_list();
49 });
49 });
50 this.element.bind('dragover', function () {
50 this.element.bind('dragover', function () {
51 return false;
51 return false;
52 });
52 });
53 this.element.bind('drop', function(event){
53 this.element.bind('drop', function(event){
54 that.handelFilesUpload(event,'drop');
54 that.handelFilesUpload(event,'drop');
55 return false;
55 return false;
56 });
56 });
57 };
57 };
58
58
59 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
59 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
60 var that = this;
60 var that = this;
61 var files;
61 var files;
62 if(dropOrForm =='drop'){
62 if(dropOrForm =='drop'){
63 files = event.originalEvent.dataTransfer.files;
63 files = event.originalEvent.dataTransfer.files;
64 } else
64 } else
65 {
65 {
66 files = event.originalEvent.target.files;
66 files = event.originalEvent.target.files;
67 }
67 }
68 for (var i = 0; i < files.length; i++) {
68 for (var i = 0; i < files.length; i++) {
69 var f = files[i];
69 var f = files[i];
70 var reader = new FileReader();
70 var reader = new FileReader();
71 reader.readAsText(f);
71 reader.readAsText(f);
72 var name_and_ext = utils.splitext(f.name);
72 var name_and_ext = utils.splitext(f.name);
73 var nbname = name_and_ext[0];
73 var nbname = name_and_ext[0];
74 var file_ext = name_and_ext[1];
74 var file_ext = name_and_ext[1];
75 if (file_ext === '.ipynb') {
75 if (file_ext === '.ipynb') {
76 var item = that.new_notebook_item(0);
76 var item = that.new_notebook_item(0);
77 that.add_name_input(nbname, item);
77 that.add_name_input(nbname, item);
78 // Store the notebook item in the reader so we can use it later
78 // Store the notebook item in the reader so we can use it later
79 // to know which item it belongs to.
79 // to know which item it belongs to.
80 $(reader).data('item', item);
80 $(reader).data('item', item);
81 reader.onload = function (event) {
81 reader.onload = function (event) {
82 var nbitem = $(event.target).data('item');
82 var nbitem = $(event.target).data('item');
83 that.add_notebook_data(event.target.result, nbitem);
83 that.add_notebook_data(event.target.result, nbitem);
84 that.add_upload_button(nbitem);
84 that.add_upload_button(nbitem);
85 };
85 };
86 } else {
86 } else {
87 var dialog = 'Uploaded notebooks must be .ipynb files';
87 var dialog = 'Uploaded notebooks must be .ipynb files';
88 IPython.dialog.modal({
88 IPython.dialog.modal({
89 title : 'Invalid file type',
89 title : 'Invalid file type',
90 body : dialog,
90 body : dialog,
91 buttons : {'OK' : {'class' : 'btn-primary'}}
91 buttons : {'OK' : {'class' : 'btn-primary'}}
92 });
92 });
93 }
93 }
94 }
94 }
95 return false;
95 return false;
96 };
96 };
97
97
98 NotebookList.prototype.clear_list = function () {
98 NotebookList.prototype.clear_list = function () {
99 this.element.children('.list_item').remove();
99 this.element.children('.list_item').remove();
100 };
100 };
101
101
102 NotebookList.prototype.load_sessions = function(){
102 NotebookList.prototype.load_sessions = function(){
103 var that = this;
103 var that = this;
104 var settings = {
104 var settings = {
105 processData : false,
105 processData : false,
106 cache : false,
106 cache : false,
107 type : "GET",
107 type : "GET",
108 dataType : "json",
108 dataType : "json",
109 success : $.proxy(that.sessions_loaded, this)
109 success : $.proxy(that.sessions_loaded, this)
110 };
110 };
111 var url = this.baseProjectUrl() + 'api/sessions';
111 var url = this.baseProjectUrl() + 'api/sessions';
112 $.ajax(url,settings);
112 $.ajax(url,settings);
113 };
113 };
114
114
115
115
116 NotebookList.prototype.sessions_loaded = function(data){
116 NotebookList.prototype.sessions_loaded = function(data){
117 this.sessions = {};
117 this.sessions = {};
118 var len = data.length;
118 var len = data.length;
119 if (len > 0) {
119 if (len > 0) {
120 for (var i=0; i<len; i++) {
120 for (var i=0; i<len; i++) {
121 var nb_path;
121 var nb_path;
122 if (!data[i].notebook.path) {
122 if (!data[i].notebook.path) {
123 nb_path = data[i].notebook.name;
123 nb_path = data[i].notebook.name;
124 }
124 }
125 else {
125 else {
126 nb_path = utils.url_path_join(
126 nb_path = utils.url_path_join(
127 data[i].notebook.path,
127 data[i].notebook.path,
128 data[i].notebook.name
128 data[i].notebook.name
129 );
129 );
130 }
130 }
131 this.sessions[nb_path] = data[i].id;
131 this.sessions[nb_path] = data[i].id;
132 }
132 }
133 }
133 }
134 this.load_list();
134 this.load_list();
135 };
135 };
136
136
137 NotebookList.prototype.load_list = function () {
137 NotebookList.prototype.load_list = function () {
138 var that = this;
138 var that = this;
139 var settings = {
139 var settings = {
140 processData : false,
140 processData : false,
141 cache : false,
141 cache : false,
142 type : "GET",
142 type : "GET",
143 dataType : "json",
143 dataType : "json",
144 success : $.proxy(this.list_loaded, this),
144 success : $.proxy(this.list_loaded, this),
145 error : $.proxy( function(){
145 error : $.proxy( function(){
146 that.list_loaded([], null, null, {msg:"Error connecting to server."});
146 that.list_loaded([], null, null, {msg:"Error connecting to server."});
147 },this)
147 },this)
148 };
148 };
149
149
150 var url = utils.url_join_encode(
150 var url = utils.url_join_encode(
151 this.baseProjectUrl(),
151 this.baseProjectUrl(),
152 'api',
152 'api',
153 'notebooks',
153 'notebooks',
154 this.notebookPath()
154 this.notebookPath()
155 );
155 );
156 $.ajax(url, settings);
156 $.ajax(url, settings);
157 };
157 };
158
158
159
159
160 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
160 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
161 var message = 'Notebook list empty.';
161 var message = 'Notebook list empty.';
162 if (param !== undefined && param.msg) {
162 if (param !== undefined && param.msg) {
163 message = param.msg;
163 message = param.msg;
164 }
164 }
165 var len = data.length;
165 var len = data.length;
166 this.clear_list();
166 this.clear_list();
167 if (len === 0) {
167 if (len === 0) {
168 $(this.new_notebook_item(0))
168 $(this.new_notebook_item(0))
169 .append(
169 .append(
170 $('<div style="margin:auto;text-align:center;color:grey"/>')
170 $('<div style="margin:auto;text-align:center;color:grey"/>')
171 .text(message)
171 .text(message)
172 );
172 );
173 }
173 }
174 for (var i=0; i<len; i++) {
174 for (var i=0; i<len; i++) {
175 var name = data[i].name;
175 var name = data[i].name;
176 var path = this.notebookPath();
176 var path = this.notebookPath();
177 var nbname = utils.splitext(name)[0];
177 var nbname = utils.splitext(name)[0];
178 var item = this.new_notebook_item(i);
178 var item = this.new_notebook_item(i);
179 this.add_link(path, nbname, item);
179 this.add_link(path, nbname, item);
180 name = utils.url_path_join(path, name);
180 name = utils.url_path_join(path, name);
181 if(this.sessions[name] === undefined){
181 if(this.sessions[name] === undefined){
182 this.add_delete_button(item);
182 this.add_delete_button(item);
183 } else {
183 } else {
184 this.add_shutdown_button(item,this.sessions[name]);
184 this.add_shutdown_button(item,this.sessions[name]);
185 }
185 }
186 }
186 }
187 };
187 };
188
188
189
189
190 NotebookList.prototype.new_notebook_item = function (index) {
190 NotebookList.prototype.new_notebook_item = function (index) {
191 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
191 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
192 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
192 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
193 // item.css('border-top-style','none');
193 // item.css('border-top-style','none');
194 item.append($("<div/>").addClass("span12").append(
194 item.append($("<div/>").addClass("span12").append(
195 $("<a/>").addClass("item_link").append(
195 $("<a/>").addClass("item_link").append(
196 $("<span/>").addClass("item_name")
196 $("<span/>").addClass("item_name")
197 )
197 )
198 ).append(
198 ).append(
199 $('<div/>').addClass("item_buttons btn-group pull-right")
199 $('<div/>').addClass("item_buttons btn-group pull-right")
200 ));
200 ));
201
201
202 if (index === -1) {
202 if (index === -1) {
203 this.element.append(item);
203 this.element.append(item);
204 } else {
204 } else {
205 this.element.children().eq(index).after(item);
205 this.element.children().eq(index).after(item);
206 }
206 }
207 return item;
207 return item;
208 };
208 };
209
209
210
210
211 NotebookList.prototype.add_link = function (path, nbname, item) {
211 NotebookList.prototype.add_link = function (path, nbname, item) {
212 item.data('nbname', nbname);
212 item.data('nbname', nbname);
213 item.data('path', path);
213 item.data('path', path);
214 item.find(".item_name").text(nbname);
214 item.find(".item_name").text(nbname);
215 item.find("a.item_link")
215 item.find("a.item_link")
216 .attr('href',
216 .attr('href',
217 utils.url_join_encode(
217 utils.url_join_encode(
218 this.baseProjectUrl(),
218 this.baseProjectUrl(),
219 "notebooks",
219 "notebooks",
220 path,
220 path,
221 nbname + ".ipynb"
221 nbname + ".ipynb"
222 )
222 )
223 ).attr('target','_blank');
223 ).attr('target','_blank');
224 };
224 };
225
225
226
226
227 NotebookList.prototype.add_name_input = function (nbname, item) {
227 NotebookList.prototype.add_name_input = function (nbname, item) {
228 item.data('nbname', nbname);
228 item.data('nbname', nbname);
229 item.find(".item_name").empty().append(
229 item.find(".item_name").empty().append(
230 $('<input/>')
230 $('<input/>')
231 .addClass("nbname_input")
231 .addClass("nbname_input")
232 .attr('value', nbname)
232 .attr('value', nbname)
233 .attr('size', '30')
233 .attr('size', '30')
234 .attr('type', 'text')
234 .attr('type', 'text')
235 );
235 );
236 };
236 };
237
237
238
238
239 NotebookList.prototype.add_notebook_data = function (data, item) {
239 NotebookList.prototype.add_notebook_data = function (data, item) {
240 item.data('nbdata', data);
240 item.data('nbdata', data);
241 };
241 };
242
242
243
243
244 NotebookList.prototype.add_shutdown_button = function (item, session) {
244 NotebookList.prototype.add_shutdown_button = function (item, session) {
245 var that = this;
245 var that = this;
246 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini").
246 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini").
247 click(function (e) {
247 click(function (e) {
248 var settings = {
248 var settings = {
249 processData : false,
249 processData : false,
250 cache : false,
250 cache : false,
251 type : "DELETE",
251 type : "DELETE",
252 dataType : "json",
252 dataType : "json",
253 success : function () {
253 success : function () {
254 that.load_sessions();
254 that.load_sessions();
255 }
255 }
256 };
256 };
257 var url = utils.url_join_encode(
257 var url = utils.url_join_encode(
258 that.baseProjectUrl(),
258 that.baseProjectUrl(),
259 'api/sessions',
259 'api/sessions',
260 session
260 session
261 );
261 );
262 $.ajax(url, settings);
262 $.ajax(url, settings);
263 return false;
263 return false;
264 });
264 });
265 // var new_buttons = item.find('a'); // shutdown_button;
265 // var new_buttons = item.find('a'); // shutdown_button;
266 item.find(".item_buttons").html("").append(shutdown_button);
266 item.find(".item_buttons").text("").append(shutdown_button);
267 };
267 };
268
268
269 NotebookList.prototype.add_delete_button = function (item) {
269 NotebookList.prototype.add_delete_button = function (item) {
270 var new_buttons = $('<span/>').addClass("btn-group pull-right");
270 var new_buttons = $('<span/>').addClass("btn-group pull-right");
271 var notebooklist = this;
271 var notebooklist = this;
272 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
272 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
273 click(function (e) {
273 click(function (e) {
274 // $(this) is the button that was clicked.
274 // $(this) is the button that was clicked.
275 var that = $(this);
275 var that = $(this);
276 // We use the nbname and notebook_id from the parent notebook_item element's
276 // We use the nbname and notebook_id from the parent notebook_item element's
277 // data because the outer scopes values change as we iterate through the loop.
277 // data because the outer scopes values change as we iterate through the loop.
278 var parent_item = that.parents('div.list_item');
278 var parent_item = that.parents('div.list_item');
279 var nbname = parent_item.data('nbname');
279 var nbname = parent_item.data('nbname');
280 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
280 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
281 IPython.dialog.modal({
281 IPython.dialog.modal({
282 title : "Delete notebook",
282 title : "Delete notebook",
283 body : message,
283 body : message,
284 buttons : {
284 buttons : {
285 Delete : {
285 Delete : {
286 class: "btn-danger",
286 class: "btn-danger",
287 click: function() {
287 click: function() {
288 var settings = {
288 var settings = {
289 processData : false,
289 processData : false,
290 cache : false,
290 cache : false,
291 type : "DELETE",
291 type : "DELETE",
292 dataType : "json",
292 dataType : "json",
293 success : function (data, status, xhr) {
293 success : function (data, status, xhr) {
294 parent_item.remove();
294 parent_item.remove();
295 }
295 }
296 };
296 };
297 var url = utils.url_join_encode(
297 var url = utils.url_join_encode(
298 notebooklist.baseProjectUrl(),
298 notebooklist.baseProjectUrl(),
299 'api/notebooks',
299 'api/notebooks',
300 notebooklist.notebookPath(),
300 notebooklist.notebookPath(),
301 nbname + '.ipynb'
301 nbname + '.ipynb'
302 );
302 );
303 $.ajax(url, settings);
303 $.ajax(url, settings);
304 }
304 }
305 },
305 },
306 Cancel : {}
306 Cancel : {}
307 }
307 }
308 });
308 });
309 return false;
309 return false;
310 });
310 });
311 item.find(".item_buttons").html("").append(delete_button);
311 item.find(".item_buttons").text("").append(delete_button);
312 };
312 };
313
313
314
314
315 NotebookList.prototype.add_upload_button = function (item) {
315 NotebookList.prototype.add_upload_button = function (item) {
316 var that = this;
316 var that = this;
317 var upload_button = $('<button/>').text("Upload")
317 var upload_button = $('<button/>').text("Upload")
318 .addClass('btn btn-primary btn-mini upload_button')
318 .addClass('btn btn-primary btn-mini upload_button')
319 .click(function (e) {
319 .click(function (e) {
320 var nbname = item.find('.item_name > input').val();
320 var nbname = item.find('.item_name > input').val();
321 var nbdata = item.data('nbdata');
321 var nbdata = item.data('nbdata');
322 var content_type = 'application/json';
322 var content_type = 'application/json';
323 var model = {
323 var model = {
324 content : JSON.parse(nbdata),
324 content : JSON.parse(nbdata),
325 };
325 };
326 var settings = {
326 var settings = {
327 processData : false,
327 processData : false,
328 cache : false,
328 cache : false,
329 type : 'PUT',
329 type : 'PUT',
330 dataType : 'json',
330 dataType : 'json',
331 data : JSON.stringify(model),
331 data : JSON.stringify(model),
332 headers : {'Content-Type': content_type},
332 headers : {'Content-Type': content_type},
333 success : function (data, status, xhr) {
333 success : function (data, status, xhr) {
334 that.add_link(data, nbname, item);
334 that.add_link(data, nbname, item);
335 that.add_delete_button(item);
335 that.add_delete_button(item);
336 },
336 },
337 error : function (data, status, xhr) {
337 error : function (data, status, xhr) {
338 console.log(data, status);
338 console.log(data, status);
339 }
339 }
340 };
340 };
341
341
342 var url = utils.url_join_encode(
342 var url = utils.url_join_encode(
343 that.baseProjectUrl(),
343 that.baseProjectUrl(),
344 'api/notebooks',
344 'api/notebooks',
345 that.notebookPath(),
345 that.notebookPath(),
346 nbname + '.ipynb'
346 nbname + '.ipynb'
347 );
347 );
348 $.ajax(url, settings);
348 $.ajax(url, settings);
349 return false;
349 return false;
350 });
350 });
351 var cancel_button = $('<button/>').text("Cancel")
351 var cancel_button = $('<button/>').text("Cancel")
352 .addClass("btn btn-mini")
352 .addClass("btn btn-mini")
353 .click(function (e) {
353 .click(function (e) {
354 console.log('cancel click');
354 console.log('cancel click');
355 item.remove();
355 item.remove();
356 return false;
356 return false;
357 });
357 });
358 item.find(".item_buttons").empty()
358 item.find(".item_buttons").empty()
359 .append(upload_button)
359 .append(upload_button)
360 .append(cancel_button);
360 .append(cancel_button);
361 };
361 };
362
362
363
363
364 NotebookList.prototype.new_notebook = function(){
364 NotebookList.prototype.new_notebook = function(){
365 var path = this.notebookPath();
365 var path = this.notebookPath();
366 var base_project_url = this.baseProjectUrl();
366 var base_project_url = this.baseProjectUrl();
367 var settings = {
367 var settings = {
368 processData : false,
368 processData : false,
369 cache : false,
369 cache : false,
370 type : "POST",
370 type : "POST",
371 dataType : "json",
371 dataType : "json",
372 async : false,
372 async : false,
373 success : function (data, status, xhr) {
373 success : function (data, status, xhr) {
374 var notebook_name = data.name;
374 var notebook_name = data.name;
375 window.open(
375 window.open(
376 utils.url_join_encode(
376 utils.url_join_encode(
377 base_project_url,
377 base_project_url,
378 'notebooks',
378 'notebooks',
379 path,
379 path,
380 notebook_name),
380 notebook_name),
381 '_blank'
381 '_blank'
382 );
382 );
383 }
383 }
384 };
384 };
385 var url = utils.url_join_encode(
385 var url = utils.url_join_encode(
386 base_project_url,
386 base_project_url,
387 'api/notebooks',
387 'api/notebooks',
388 path
388 path
389 );
389 );
390 $.ajax(url, settings);
390 $.ajax(url, settings);
391 };
391 };
392
392
393 IPython.NotebookList = NotebookList;
393 IPython.NotebookList = NotebookList;
394
394
395 return IPython;
395 return IPython;
396
396
397 }(IPython));
397 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now