##// END OF EJS Templates
Progress...
Jonathan Frederic -
Show More
@@ -1,126 +1,116 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2014 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Utilities
5 'base/js/namespace',
10 //============================================================================
6 'components/jquery/jquery.min'
11 IPython.namespace('IPython.security');
7 ], function(IPython, $) {
12
13 IPython.security = (function (IPython) {
14 "use strict";
8 "use strict";
15
16 var utils = IPython.utils;
17
9
18 var noop = function (x) { return x; };
10 var noop = function (x) { return x; };
19
11
20 var caja;
12 var caja;
21 if (window && window.html) {
13 if (window && window.html) {
22 caja = window.html;
14 caja = window.html;
23 caja.html4 = window.html4;
15 caja.html4 = window.html4;
24 caja.sanitizeStylesheet = window.sanitizeStylesheet;
16 caja.sanitizeStylesheet = window.sanitizeStylesheet;
25 }
17 }
26
18
27 var sanitizeAttribs = function (tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) {
19 var sanitizeAttribs = function (tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) {
28 // add trusting data-attributes to the default sanitizeAttribs from caja
20 // add trusting data-attributes to the default sanitizeAttribs from caja
29 // this function is mostly copied from the caja source
21 // this function is mostly copied from the caja source
30 var ATTRIBS = caja.html4.ATTRIBS;
22 var ATTRIBS = caja.html4.ATTRIBS;
31 for (var i = 0; i < attribs.length; i += 2) {
23 for (var i = 0; i < attribs.length; i += 2) {
32 var attribName = attribs[i];
24 var attribName = attribs[i];
33 if (attribName.substr(0,5) == 'data-') {
25 if (attribName.substr(0,5) == 'data-') {
34 var attribKey = '*::' + attribName;
26 var attribKey = '*::' + attribName;
35 if (!ATTRIBS.hasOwnProperty(attribKey)) {
27 if (!ATTRIBS.hasOwnProperty(attribKey)) {
36 ATTRIBS[attribKey] = 0;
28 ATTRIBS[attribKey] = 0;
37 }
29 }
38 }
30 }
39 }
31 }
40 return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger);
32 return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger);
41 };
33 };
42
34
43 var sanitize_css = function (css, tagPolicy) {
35 var sanitize_css = function (css, tagPolicy) {
44 // sanitize CSS
36 // sanitize CSS
45 // like sanitize_html, but for CSS
37 // like sanitize_html, but for CSS
46 // called by sanitize_stylesheets
38 // called by sanitize_stylesheets
47 return caja.sanitizeStylesheet(
39 return caja.sanitizeStylesheet(
48 window.location.pathname,
40 window.location.pathname,
49 css,
41 css,
50 {
42 {
51 containerClass: null,
43 containerClass: null,
52 idSuffix: '',
44 idSuffix: '',
53 tagPolicy: tagPolicy,
45 tagPolicy: tagPolicy,
54 virtualizeAttrName: noop
46 virtualizeAttrName: noop
55 },
47 },
56 noop
48 noop
57 );
49 );
58 };
50 };
59
51
60 var sanitize_stylesheets = function (html, tagPolicy) {
52 var sanitize_stylesheets = function (html, tagPolicy) {
61 // sanitize just the css in style tags in a block of html
53 // sanitize just the css in style tags in a block of html
62 // called by sanitize_html, if allow_css is true
54 // called by sanitize_html, if allow_css is true
63 var h = $("<div/>").append(html);
55 var h = $("<div/>").append(html);
64 var style_tags = h.find("style");
56 var style_tags = h.find("style");
65 if (!style_tags.length) {
57 if (!style_tags.length) {
66 // no style tags to sanitize
58 // no style tags to sanitize
67 return html;
59 return html;
68 }
60 }
69 style_tags.each(function(i, style) {
61 style_tags.each(function(i, style) {
70 style.innerHTML = sanitize_css(style.innerHTML, tagPolicy);
62 style.innerHTML = sanitize_css(style.innerHTML, tagPolicy);
71 });
63 });
72 return h.html();
64 return h.html();
73 };
65 };
74
66
75 var sanitize_html = function (html, allow_css) {
67 var sanitize_html = function (html, allow_css) {
76 // sanitize HTML
68 // sanitize HTML
77 // if allow_css is true (default: false), CSS is sanitized as well.
69 // if allow_css is true (default: false), CSS is sanitized as well.
78 // otherwise, CSS elements and attributes are simply removed.
70 // otherwise, CSS elements and attributes are simply removed.
79 var html4 = caja.html4;
71 var html4 = caja.html4;
80
72
81 if (allow_css) {
73 if (allow_css) {
82 // allow sanitization of style tags,
74 // allow sanitization of style tags,
83 // not just scrubbing
75 // not just scrubbing
84 html4.ELEMENTS.style &= ~html4.eflags.UNSAFE;
76 html4.ELEMENTS.style &= ~html4.eflags.UNSAFE;
85 html4.ATTRIBS.style = html4.atype.STYLE;
77 html4.ATTRIBS.style = html4.atype.STYLE;
86 } else {
78 } else {
87 // scrub all CSS
79 // scrub all CSS
88 html4.ELEMENTS.style |= html4.eflags.UNSAFE;
80 html4.ELEMENTS.style |= html4.eflags.UNSAFE;
89 html4.ATTRIBS.style = html4.atype.SCRIPT;
81 html4.ATTRIBS.style = html4.atype.SCRIPT;
90 }
82 }
91
83
92 var record_messages = function (msg, opts) {
84 var record_messages = function (msg, opts) {
93 console.log("HTML Sanitizer", msg, opts);
85 console.log("HTML Sanitizer", msg, opts);
94 };
86 };
95
87
96 var policy = function (tagName, attribs) {
88 var policy = function (tagName, attribs) {
97 if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) {
89 if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) {
98 return {
90 return {
99 'attribs': sanitizeAttribs(tagName, attribs,
91 'attribs': sanitizeAttribs(tagName, attribs,
100 noop, noop, record_messages)
92 noop, noop, record_messages)
101 };
93 };
102 } else {
94 } else {
103 record_messages(tagName + " removed", {
95 record_messages(tagName + " removed", {
104 change: "removed",
96 change: "removed",
105 tagName: tagName
97 tagName: tagName
106 });
98 });
107 }
99 }
108 };
100 };
109
101
110 var sanitized = caja.sanitizeWithPolicy(html, policy);
102 var sanitized = caja.sanitizeWithPolicy(html, policy);
111
103
112 if (allow_css) {
104 if (allow_css) {
113 // sanitize style tags as stylesheets
105 // sanitize style tags as stylesheets
114 sanitized = sanitize_stylesheets(result.sanitized, policy);
106 sanitized = sanitize_stylesheets(result.sanitized, policy);
115 }
107 }
116
108
117 return sanitized;
109 return sanitized;
118 };
110 };
119
111
120 return {
112 return {
121 caja: caja,
113 caja: caja,
122 sanitize_html: sanitize_html
114 sanitize_html: sanitize_html
123 };
115 };
124
116 });
125 }(IPython));
126
@@ -1,556 +1,557 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'components/jquery/jquery.min',
6 'components/jquery/jquery.min',
7 ], function(IPython, $){
7 ], function(IPython, $){
8 "use strict";
8 "use strict";
9
9
10 IPython.load_extensions = function () {
10 IPython.load_extensions = function () {
11 // load one or more IPython notebook extensions with requirejs
11 // load one or more IPython notebook extensions with requirejs
12
12
13 var extensions = [];
13 var extensions = [];
14 var extension_names = arguments;
14 var extension_names = arguments;
15 for (var i = 0; i < extension_names.length; i++) {
15 for (var i = 0; i < extension_names.length; i++) {
16 extensions.push("nbextensions/" + arguments[i]);
16 extensions.push("nbextensions/" + arguments[i]);
17 }
17 }
18
18
19 require(extensions,
19 require(extensions,
20 function () {
20 function () {
21 for (var i = 0; i < arguments.length; i++) {
21 for (var i = 0; i < arguments.length; i++) {
22 var ext = arguments[i];
22 var ext = arguments[i];
23 var ext_name = extension_names[i];
23 var ext_name = extension_names[i];
24 // success callback
24 // success callback
25 console.log("Loaded extension: " + ext_name);
25 console.log("Loaded extension: " + ext_name);
26 if (ext && ext.load_ipython_extension !== undefined) {
26 if (ext && ext.load_ipython_extension !== undefined) {
27 ext.load_ipython_extension();
27 ext.load_ipython_extension();
28 }
28 }
29 }
29 }
30 },
30 },
31 function (err) {
31 function (err) {
32 // failure callback
32 // failure callback
33 console.log("Failed to load extension(s):", err.requireModules, err);
33 console.log("Failed to load extension(s):", err.requireModules, err);
34 }
34 }
35 );
35 );
36 };
36 };
37
37
38 //============================================================================
38 //============================================================================
39 // Cross-browser RegEx Split
39 // Cross-browser RegEx Split
40 //============================================================================
40 //============================================================================
41
41
42 // This code has been MODIFIED from the code licensed below to not replace the
42 // This code has been MODIFIED from the code licensed below to not replace the
43 // default browser split. The license is reproduced here.
43 // default browser split. The license is reproduced here.
44
44
45 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
45 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
46 /*!
46 /*!
47 * Cross-Browser Split 1.1.1
47 * Cross-Browser Split 1.1.1
48 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
48 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
49 * Available under the MIT License
49 * Available under the MIT License
50 * ECMAScript compliant, uniform cross-browser split method
50 * ECMAScript compliant, uniform cross-browser split method
51 */
51 */
52
52
53 /**
53 /**
54 * Splits a string into an array of strings using a regex or string
54 * Splits a string into an array of strings using a regex or string
55 * separator. Matches of the separator are not included in the result array.
55 * separator. Matches of the separator are not included in the result array.
56 * However, if `separator` is a regex that contains capturing groups,
56 * However, if `separator` is a regex that contains capturing groups,
57 * backreferences are spliced into the result each time `separator` is
57 * backreferences are spliced into the result each time `separator` is
58 * matched. Fixes browser bugs compared to the native
58 * matched. Fixes browser bugs compared to the native
59 * `String.prototype.split` and can be used reliably cross-browser.
59 * `String.prototype.split` and can be used reliably cross-browser.
60 * @param {String} str String to split.
60 * @param {String} str String to split.
61 * @param {RegExp|String} separator Regex or string to use for separating
61 * @param {RegExp|String} separator Regex or string to use for separating
62 * the string.
62 * the string.
63 * @param {Number} [limit] Maximum number of items to include in the result
63 * @param {Number} [limit] Maximum number of items to include in the result
64 * array.
64 * array.
65 * @returns {Array} Array of substrings.
65 * @returns {Array} Array of substrings.
66 * @example
66 * @example
67 *
67 *
68 * // Basic use
68 * // Basic use
69 * regex_split('a b c d', ' ');
69 * regex_split('a b c d', ' ');
70 * // -> ['a', 'b', 'c', 'd']
70 * // -> ['a', 'b', 'c', 'd']
71 *
71 *
72 * // With limit
72 * // With limit
73 * regex_split('a b c d', ' ', 2);
73 * regex_split('a b c d', ' ', 2);
74 * // -> ['a', 'b']
74 * // -> ['a', 'b']
75 *
75 *
76 * // Backreferences in result array
76 * // Backreferences in result array
77 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
77 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
78 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
78 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
79 */
79 */
80 var regex_split = function (str, separator, limit) {
80 var regex_split = function (str, separator, limit) {
81 // If `separator` is not a regex, use `split`
81 // If `separator` is not a regex, use `split`
82 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
82 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
83 return split.call(str, separator, limit);
83 return split.call(str, separator, limit);
84 }
84 }
85 var output = [],
85 var output = [],
86 flags = (separator.ignoreCase ? "i" : "") +
86 flags = (separator.ignoreCase ? "i" : "") +
87 (separator.multiline ? "m" : "") +
87 (separator.multiline ? "m" : "") +
88 (separator.extended ? "x" : "") + // Proposed for ES6
88 (separator.extended ? "x" : "") + // Proposed for ES6
89 (separator.sticky ? "y" : ""), // Firefox 3+
89 (separator.sticky ? "y" : ""), // Firefox 3+
90 lastLastIndex = 0,
90 lastLastIndex = 0,
91 // Make `global` and avoid `lastIndex` issues by working with a copy
91 // Make `global` and avoid `lastIndex` issues by working with a copy
92 separator = new RegExp(separator.source, flags + "g"),
92 separator = new RegExp(separator.source, flags + "g"),
93 separator2, match, lastIndex, lastLength;
93 separator2, match, lastIndex, lastLength;
94 str += ""; // Type-convert
94 str += ""; // Type-convert
95
95
96 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
96 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
97 if (!compliantExecNpcg) {
97 if (!compliantExecNpcg) {
98 // Doesn't need flags gy, but they don't hurt
98 // Doesn't need flags gy, but they don't hurt
99 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
99 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
100 }
100 }
101 /* Values for `limit`, per the spec:
101 /* Values for `limit`, per the spec:
102 * If undefined: 4294967295 // Math.pow(2, 32) - 1
102 * If undefined: 4294967295 // Math.pow(2, 32) - 1
103 * If 0, Infinity, or NaN: 0
103 * If 0, Infinity, or NaN: 0
104 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
104 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
105 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
105 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
106 * If other: Type-convert, then use the above rules
106 * If other: Type-convert, then use the above rules
107 */
107 */
108 limit = typeof(limit) === "undefined" ?
108 limit = typeof(limit) === "undefined" ?
109 -1 >>> 0 : // Math.pow(2, 32) - 1
109 -1 >>> 0 : // Math.pow(2, 32) - 1
110 limit >>> 0; // ToUint32(limit)
110 limit >>> 0; // ToUint32(limit)
111 while (match = separator.exec(str)) {
111 while (match = separator.exec(str)) {
112 // `separator.lastIndex` is not reliable cross-browser
112 // `separator.lastIndex` is not reliable cross-browser
113 lastIndex = match.index + match[0].length;
113 lastIndex = match.index + match[0].length;
114 if (lastIndex > lastLastIndex) {
114 if (lastIndex > lastLastIndex) {
115 output.push(str.slice(lastLastIndex, match.index));
115 output.push(str.slice(lastLastIndex, match.index));
116 // Fix browsers whose `exec` methods don't consistently return `undefined` for
116 // Fix browsers whose `exec` methods don't consistently return `undefined` for
117 // nonparticipating capturing groups
117 // nonparticipating capturing groups
118 if (!compliantExecNpcg && match.length > 1) {
118 if (!compliantExecNpcg && match.length > 1) {
119 match[0].replace(separator2, function () {
119 match[0].replace(separator2, function () {
120 for (var i = 1; i < arguments.length - 2; i++) {
120 for (var i = 1; i < arguments.length - 2; i++) {
121 if (typeof(arguments[i]) === "undefined") {
121 if (typeof(arguments[i]) === "undefined") {
122 match[i] = undefined;
122 match[i] = undefined;
123 }
123 }
124 }
124 }
125 });
125 });
126 }
126 }
127 if (match.length > 1 && match.index < str.length) {
127 if (match.length > 1 && match.index < str.length) {
128 Array.prototype.push.apply(output, match.slice(1));
128 Array.prototype.push.apply(output, match.slice(1));
129 }
129 }
130 lastLength = match[0].length;
130 lastLength = match[0].length;
131 lastLastIndex = lastIndex;
131 lastLastIndex = lastIndex;
132 if (output.length >= limit) {
132 if (output.length >= limit) {
133 break;
133 break;
134 }
134 }
135 }
135 }
136 if (separator.lastIndex === match.index) {
136 if (separator.lastIndex === match.index) {
137 separator.lastIndex++; // Avoid an infinite loop
137 separator.lastIndex++; // Avoid an infinite loop
138 }
138 }
139 }
139 }
140 if (lastLastIndex === str.length) {
140 if (lastLastIndex === str.length) {
141 if (lastLength || !separator.test("")) {
141 if (lastLength || !separator.test("")) {
142 output.push("");
142 output.push("");
143 }
143 }
144 } else {
144 } else {
145 output.push(str.slice(lastLastIndex));
145 output.push(str.slice(lastLastIndex));
146 }
146 }
147 return output.length > limit ? output.slice(0, limit) : output;
147 return output.length > limit ? output.slice(0, limit) : output;
148 };
148 };
149
149
150 //============================================================================
150 //============================================================================
151 // End contributed Cross-browser RegEx Split
151 // End contributed Cross-browser RegEx Split
152 //============================================================================
152 //============================================================================
153
153
154
154
155 var uuid = function () {
155 var uuid = function () {
156 // http://www.ietf.org/rfc/rfc4122.txt
156 // http://www.ietf.org/rfc/rfc4122.txt
157 var s = [];
157 var s = [];
158 var hexDigits = "0123456789ABCDEF";
158 var hexDigits = "0123456789ABCDEF";
159 for (var i = 0; i < 32; i++) {
159 for (var i = 0; i < 32; i++) {
160 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
160 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
161 }
161 }
162 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
162 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
163 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
163 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
164
164
165 var uuid = s.join("");
165 var uuid = s.join("");
166 return uuid;
166 return uuid;
167 };
167 };
168
168
169
169
170 //Fix raw text to parse correctly in crazy XML
170 //Fix raw text to parse correctly in crazy XML
171 function xmlencode(string) {
171 function xmlencode(string) {
172 return string.replace(/\&/g,'&'+'amp;')
172 return string.replace(/\&/g,'&'+'amp;')
173 .replace(/</g,'&'+'lt;')
173 .replace(/</g,'&'+'lt;')
174 .replace(/>/g,'&'+'gt;')
174 .replace(/>/g,'&'+'gt;')
175 .replace(/\'/g,'&'+'apos;')
175 .replace(/\'/g,'&'+'apos;')
176 .replace(/\"/g,'&'+'quot;')
176 .replace(/\"/g,'&'+'quot;')
177 .replace(/`/g,'&'+'#96;');
177 .replace(/`/g,'&'+'#96;');
178 }
178 }
179
179
180
180
181 //Map from terminal commands to CSS classes
181 //Map from terminal commands to CSS classes
182 var ansi_colormap = {
182 var ansi_colormap = {
183 "01":"ansibold",
183 "01":"ansibold",
184
184
185 "30":"ansiblack",
185 "30":"ansiblack",
186 "31":"ansired",
186 "31":"ansired",
187 "32":"ansigreen",
187 "32":"ansigreen",
188 "33":"ansiyellow",
188 "33":"ansiyellow",
189 "34":"ansiblue",
189 "34":"ansiblue",
190 "35":"ansipurple",
190 "35":"ansipurple",
191 "36":"ansicyan",
191 "36":"ansicyan",
192 "37":"ansigray",
192 "37":"ansigray",
193
193
194 "40":"ansibgblack",
194 "40":"ansibgblack",
195 "41":"ansibgred",
195 "41":"ansibgred",
196 "42":"ansibggreen",
196 "42":"ansibggreen",
197 "43":"ansibgyellow",
197 "43":"ansibgyellow",
198 "44":"ansibgblue",
198 "44":"ansibgblue",
199 "45":"ansibgpurple",
199 "45":"ansibgpurple",
200 "46":"ansibgcyan",
200 "46":"ansibgcyan",
201 "47":"ansibggray"
201 "47":"ansibggray"
202 };
202 };
203
203
204 function _process_numbers(attrs, numbers) {
204 function _process_numbers(attrs, numbers) {
205 // process ansi escapes
205 // process ansi escapes
206 var n = numbers.shift();
206 var n = numbers.shift();
207 if (ansi_colormap[n]) {
207 if (ansi_colormap[n]) {
208 if ( ! attrs["class"] ) {
208 if ( ! attrs["class"] ) {
209 attrs["class"] = ansi_colormap[n];
209 attrs["class"] = ansi_colormap[n];
210 } else {
210 } else {
211 attrs["class"] += " " + ansi_colormap[n];
211 attrs["class"] += " " + ansi_colormap[n];
212 }
212 }
213 } else if (n == "38" || n == "48") {
213 } else if (n == "38" || n == "48") {
214 // VT100 256 color or 24 bit RGB
214 // VT100 256 color or 24 bit RGB
215 if (numbers.length < 2) {
215 if (numbers.length < 2) {
216 console.log("Not enough fields for VT100 color", numbers);
216 console.log("Not enough fields for VT100 color", numbers);
217 return;
217 return;
218 }
218 }
219
219
220 var index_or_rgb = numbers.shift();
220 var index_or_rgb = numbers.shift();
221 var r,g,b;
221 var r,g,b;
222 if (index_or_rgb == "5") {
222 if (index_or_rgb == "5") {
223 // 256 color
223 // 256 color
224 var idx = parseInt(numbers.shift());
224 var idx = parseInt(numbers.shift());
225 if (idx < 16) {
225 if (idx < 16) {
226 // indexed ANSI
226 // indexed ANSI
227 // ignore bright / non-bright distinction
227 // ignore bright / non-bright distinction
228 idx = idx % 8;
228 idx = idx % 8;
229 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
229 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
230 if ( ! attrs["class"] ) {
230 if ( ! attrs["class"] ) {
231 attrs["class"] = ansiclass;
231 attrs["class"] = ansiclass;
232 } else {
232 } else {
233 attrs["class"] += " " + ansiclass;
233 attrs["class"] += " " + ansiclass;
234 }
234 }
235 return;
235 return;
236 } else if (idx < 232) {
236 } else if (idx < 232) {
237 // 216 color 6x6x6 RGB
237 // 216 color 6x6x6 RGB
238 idx = idx - 16;
238 idx = idx - 16;
239 b = idx % 6;
239 b = idx % 6;
240 g = Math.floor(idx / 6) % 6;
240 g = Math.floor(idx / 6) % 6;
241 r = Math.floor(idx / 36) % 6;
241 r = Math.floor(idx / 36) % 6;
242 // convert to rgb
242 // convert to rgb
243 r = (r * 51);
243 r = (r * 51);
244 g = (g * 51);
244 g = (g * 51);
245 b = (b * 51);
245 b = (b * 51);
246 } else {
246 } else {
247 // grayscale
247 // grayscale
248 idx = idx - 231;
248 idx = idx - 231;
249 // it's 1-24 and should *not* include black or white,
249 // it's 1-24 and should *not* include black or white,
250 // so a 26 point scale
250 // so a 26 point scale
251 r = g = b = Math.floor(idx * 256 / 26);
251 r = g = b = Math.floor(idx * 256 / 26);
252 }
252 }
253 } else if (index_or_rgb == "2") {
253 } else if (index_or_rgb == "2") {
254 // Simple 24 bit RGB
254 // Simple 24 bit RGB
255 if (numbers.length > 3) {
255 if (numbers.length > 3) {
256 console.log("Not enough fields for RGB", numbers);
256 console.log("Not enough fields for RGB", numbers);
257 return;
257 return;
258 }
258 }
259 r = numbers.shift();
259 r = numbers.shift();
260 g = numbers.shift();
260 g = numbers.shift();
261 b = numbers.shift();
261 b = numbers.shift();
262 } else {
262 } else {
263 console.log("unrecognized control", numbers);
263 console.log("unrecognized control", numbers);
264 return;
264 return;
265 }
265 }
266 if (r !== undefined) {
266 if (r !== undefined) {
267 // apply the rgb color
267 // apply the rgb color
268 var line;
268 var line;
269 if (n == "38") {
269 if (n == "38") {
270 line = "color: ";
270 line = "color: ";
271 } else {
271 } else {
272 line = "background-color: ";
272 line = "background-color: ";
273 }
273 }
274 line = line + "rgb(" + r + "," + g + "," + b + ");"
274 line = line + "rgb(" + r + "," + g + "," + b + ");"
275 if ( !attrs["style"] ) {
275 if ( !attrs["style"] ) {
276 attrs["style"] = line;
276 attrs["style"] = line;
277 } else {
277 } else {
278 attrs["style"] += " " + line;
278 attrs["style"] += " " + line;
279 }
279 }
280 }
280 }
281 }
281 }
282 }
282 }
283
283
284 function ansispan(str) {
284 function ansispan(str) {
285 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
285 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
286 // regular ansi escapes (using the table above)
286 // regular ansi escapes (using the table above)
287 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
287 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
288 if (!pattern) {
288 if (!pattern) {
289 // [(01|22|39|)m close spans
289 // [(01|22|39|)m close spans
290 return "</span>";
290 return "</span>";
291 }
291 }
292 // consume sequence of color escapes
292 // consume sequence of color escapes
293 var numbers = pattern.match(/\d+/g);
293 var numbers = pattern.match(/\d+/g);
294 var attrs = {};
294 var attrs = {};
295 while (numbers.length > 0) {
295 while (numbers.length > 0) {
296 _process_numbers(attrs, numbers);
296 _process_numbers(attrs, numbers);
297 }
297 }
298
298
299 var span = "<span ";
299 var span = "<span ";
300 for (var attr in attrs) {
300 for (var attr in attrs) {
301 var value = attrs[attr];
301 var value = attrs[attr];
302 span = span + " " + attr + '="' + attrs[attr] + '"';
302 span = span + " " + attr + '="' + attrs[attr] + '"';
303 }
303 }
304 return span + ">";
304 return span + ">";
305 });
305 });
306 };
306 };
307
307
308 // Transform ANSI color escape codes into HTML <span> tags with css
308 // Transform ANSI color escape codes into HTML <span> tags with css
309 // classes listed in the above ansi_colormap object. The actual color used
309 // classes listed in the above ansi_colormap object. The actual color used
310 // are set in the css file.
310 // are set in the css file.
311 function fixConsole(txt) {
311 function fixConsole(txt) {
312 txt = xmlencode(txt);
312 txt = xmlencode(txt);
313 var re = /\033\[([\dA-Fa-f;]*?)m/;
313 var re = /\033\[([\dA-Fa-f;]*?)m/;
314 var opened = false;
314 var opened = false;
315 var cmds = [];
315 var cmds = [];
316 var opener = "";
316 var opener = "";
317 var closer = "";
317 var closer = "";
318
318
319 // Strip all ANSI codes that are not color related. Matches
319 // Strip all ANSI codes that are not color related. Matches
320 // all ANSI codes that do not end with "m".
320 // all ANSI codes that do not end with "m".
321 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
321 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
322 txt = txt.replace(ignored_re, "");
322 txt = txt.replace(ignored_re, "");
323
323
324 // color ansi codes
324 // color ansi codes
325 txt = ansispan(txt);
325 txt = ansispan(txt);
326 return txt;
326 return txt;
327 }
327 }
328
328
329 // Remove chunks that should be overridden by the effect of
329 // Remove chunks that should be overridden by the effect of
330 // carriage return characters
330 // carriage return characters
331 function fixCarriageReturn(txt) {
331 function fixCarriageReturn(txt) {
332 var tmp = txt;
332 var tmp = txt;
333 do {
333 do {
334 txt = tmp;
334 txt = tmp;
335 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
335 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
336 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
336 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
337 } while (tmp.length < txt.length);
337 } while (tmp.length < txt.length);
338 return txt;
338 return txt;
339 }
339 }
340
340
341 // Locate any URLs and convert them to a anchor tag
341 // Locate any URLs and convert them to a anchor tag
342 function autoLinkUrls(txt) {
342 function autoLinkUrls(txt) {
343 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
343 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
344 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
344 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
345 }
345 }
346
346
347 var points_to_pixels = function (points) {
347 var points_to_pixels = function (points) {
348 // A reasonably good way of converting between points and pixels.
348 // A reasonably good way of converting between points and pixels.
349 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
349 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
350 $(body).append(test);
350 $(body).append(test);
351 var pixel_per_point = test.width()/10000;
351 var pixel_per_point = test.width()/10000;
352 test.remove();
352 test.remove();
353 return Math.floor(points*pixel_per_point);
353 return Math.floor(points*pixel_per_point);
354 };
354 };
355
355
356 var always_new = function (constructor) {
356 var always_new = function (constructor) {
357 // wrapper around contructor to avoid requiring `var a = new constructor()`
357 // wrapper around contructor to avoid requiring `var a = new constructor()`
358 // useful for passing constructors as callbacks,
358 // useful for passing constructors as callbacks,
359 // not for programmer laziness.
359 // not for programmer laziness.
360 // from http://programmers.stackexchange.com/questions/118798
360 // from http://programmers.stackexchange.com/questions/118798
361 return function () {
361 return function () {
362 var obj = Object.create(constructor.prototype);
362 var obj = Object.create(constructor.prototype);
363 constructor.apply(obj, arguments);
363 constructor.apply(obj, arguments);
364 return obj;
364 return obj;
365 };
365 };
366 };
366 };
367
367
368 var url_path_join = function () {
368 var url_path_join = function () {
369 // join a sequence of url components with '/'
369 // join a sequence of url components with '/'
370 var url = '';
370 var url = '';
371 for (var i = 0; i < arguments.length; i++) {
371 for (var i = 0; i < arguments.length; i++) {
372 if (arguments[i] === '') {
372 if (arguments[i] === '') {
373 continue;
373 continue;
374 }
374 }
375 if (url.length > 0 && url[url.length-1] != '/') {
375 if (url.length > 0 && url[url.length-1] != '/') {
376 url = url + '/' + arguments[i];
376 url = url + '/' + arguments[i];
377 } else {
377 } else {
378 url = url + arguments[i];
378 url = url + arguments[i];
379 }
379 }
380 }
380 }
381 url = url.replace(/\/\/+/, '/');
381 url = url.replace(/\/\/+/, '/');
382 return url;
382 return url;
383 };
383 };
384
384
385 var parse_url = function (url) {
385 var parse_url = function (url) {
386 // an `a` element with an href allows attr-access to the parsed segments of a URL
386 // an `a` element with an href allows attr-access to the parsed segments of a URL
387 // a = parse_url("http://localhost:8888/path/name#hash")
387 // a = parse_url("http://localhost:8888/path/name#hash")
388 // a.protocol = "http:"
388 // a.protocol = "http:"
389 // a.host = "localhost:8888"
389 // a.host = "localhost:8888"
390 // a.hostname = "localhost"
390 // a.hostname = "localhost"
391 // a.port = 8888
391 // a.port = 8888
392 // a.pathname = "/path/name"
392 // a.pathname = "/path/name"
393 // a.hash = "#hash"
393 // a.hash = "#hash"
394 var a = document.createElement("a");
394 var a = document.createElement("a");
395 a.href = url;
395 a.href = url;
396 return a;
396 return a;
397 };
397 };
398
398
399 var encode_uri_components = function (uri) {
399 var encode_uri_components = function (uri) {
400 // encode just the components of a multi-segment uri,
400 // encode just the components of a multi-segment uri,
401 // leaving '/' separators
401 // leaving '/' separators
402 return uri.split('/').map(encodeURIComponent).join('/');
402 return uri.split('/').map(encodeURIComponent).join('/');
403 };
403 };
404
404
405 var url_join_encode = function () {
405 var url_join_encode = function () {
406 // join a sequence of url components with '/',
406 // join a sequence of url components with '/',
407 // encoding each component with encodeURIComponent
407 // encoding each component with encodeURIComponent
408 return encode_uri_components(url_path_join.apply(null, arguments));
408 return encode_uri_components(url_path_join.apply(null, arguments));
409 };
409 };
410
410
411
411
412 var splitext = function (filename) {
412 var splitext = function (filename) {
413 // mimic Python os.path.splitext
413 // mimic Python os.path.splitext
414 // Returns ['base', '.ext']
414 // Returns ['base', '.ext']
415 var idx = filename.lastIndexOf('.');
415 var idx = filename.lastIndexOf('.');
416 if (idx > 0) {
416 if (idx > 0) {
417 return [filename.slice(0, idx), filename.slice(idx)];
417 return [filename.slice(0, idx), filename.slice(idx)];
418 } else {
418 } else {
419 return [filename, ''];
419 return [filename, ''];
420 }
420 }
421 };
421 };
422
422
423
423
424 var escape_html = function (text) {
424 var escape_html = function (text) {
425 // escape text to HTML
425 // escape text to HTML
426 return $("<div/>").text(text).html();
426 return $("<div/>").text(text).html();
427 };
427 };
428
428
429
429
430 var get_body_data = function(key) {
430 var get_body_data = function(key) {
431 // get a url-encoded item from body.data and decode it
431 // get a url-encoded item from body.data and decode it
432 // we should never have any encoded URLs anywhere else in code
432 // we should never have any encoded URLs anywhere else in code
433 // until we are building an actual request
433 // until we are building an actual request
434 return decodeURIComponent($('body').data(key));
434 return decodeURIComponent($('body').data(key));
435 };
435 };
436
436
437 var to_absolute_cursor_pos = function (cm, cursor) {
437 var to_absolute_cursor_pos = function (cm, cursor) {
438 // get the absolute cursor position from CodeMirror's col, ch
438 // get the absolute cursor position from CodeMirror's col, ch
439 if (!cursor) {
439 if (!cursor) {
440 cursor = cm.getCursor();
440 cursor = cm.getCursor();
441 }
441 }
442 var cursor_pos = cursor.ch;
442 var cursor_pos = cursor.ch;
443 for (var i = 0; i < cursor.line; i++) {
443 for (var i = 0; i < cursor.line; i++) {
444 cursor_pos += cm.getLine(i).length + 1;
444 cursor_pos += cm.getLine(i).length + 1;
445 }
445 }
446 return cursor_pos;
446 return cursor_pos;
447 };
447 };
448
448
449 var from_absolute_cursor_pos = function (cm, cursor_pos) {
449 var from_absolute_cursor_pos = function (cm, cursor_pos) {
450 // turn absolute cursor postion into CodeMirror col, ch cursor
450 // turn absolute cursor postion into CodeMirror col, ch cursor
451 var i, line;
451 var i, line;
452 var offset = 0;
452 var offset = 0;
453 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
453 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
454 if (offset + line.length < cursor_pos) {
454 if (offset + line.length < cursor_pos) {
455 offset += line.length + 1;
455 offset += line.length + 1;
456 } else {
456 } else {
457 return {
457 return {
458 line : i,
458 line : i,
459 ch : cursor_pos - offset,
459 ch : cursor_pos - offset,
460 };
460 };
461 }
461 }
462 }
462 }
463 // reached end, return endpoint
463 // reached end, return endpoint
464 return {
464 return {
465 ch : line.length - 1,
465 ch : line.length - 1,
466 line : i - 1,
466 line : i - 1,
467 };
467 };
468 };
468 };
469
469
470 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
470 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
471 var browser = (function() {
471 var browser = (function() {
472 if (typeof navigator === 'undefined') {
472 if (typeof navigator === 'undefined') {
473 // navigator undefined in node
473 // navigator undefined in node
474 return 'None';
474 return 'None';
475 }
475 }
476 var N= navigator.appName, ua= navigator.userAgent, tem;
476 var N= navigator.appName, ua= navigator.userAgent, tem;
477 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
477 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
478 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
478 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
479 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
479 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
480 return M;
480 return M;
481 })();
481 })();
482
482
483 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
483 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
484 var platform = (function () {
484 var platform = (function () {
485 if (typeof navigator === 'undefined') {
485 if (typeof navigator === 'undefined') {
486 // navigator undefined in node
486 // navigator undefined in node
487 return 'None';
487 return 'None';
488 }
488 }
489 var OSName="None";
489 var OSName="None";
490 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
490 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
491 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
491 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
492 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
492 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
493 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
493 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
494 return OSName;
494 return OSName;
495 })();
495 })();
496
496
497 var is_or_has = function (a, b) {
497 var is_or_has = function (a, b) {
498 // Is b a child of a or a itself?
498 // Is b a child of a or a itself?
499 return a.has(b).length !==0 || a.is(b);
499 return a.has(b).length !==0 || a.is(b);
500 };
500 };
501
501
502 var is_focused = function (e) {
502 var is_focused = function (e) {
503 // Is element e, or one of its children focused?
503 // Is element e, or one of its children focused?
504 e = $(e);
504 e = $(e);
505 var target = $(document.activeElement);
505 var target = $(document.activeElement);
506 if (target.length > 0) {
506 if (target.length > 0) {
507 if (is_or_has(e, target)) {
507 if (is_or_has(e, target)) {
508 return true;
508 return true;
509 } else {
509 } else {
510 return false;
510 return false;
511 }
511 }
512 } else {
512 } else {
513 return false;
513 return false;
514 }
514 }
515 };
515 };
516
516
517 var log_ajax_error = function (jqXHR, status, error) {
517 var log_ajax_error = function (jqXHR, status, error) {
518 // log ajax failures with informative messages
518 // log ajax failures with informative messages
519 var msg = "API request failed (" + jqXHR.status + "): ";
519 var msg = "API request failed (" + jqXHR.status + "): ";
520 console.log(jqXHR);
520 console.log(jqXHR);
521 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
521 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
522 msg += jqXHR.responseJSON.message;
522 msg += jqXHR.responseJSON.message;
523 } else {
523 } else {
524 msg += jqXHR.statusText;
524 msg += jqXHR.statusText;
525 }
525 }
526 console.log(msg);
526 console.log(msg);
527 };
527 };
528
528
529 var Utils = {
529 var Utils = {
530 regex_split : regex_split,
530 regex_split : regex_split,
531 uuid : uuid,
531 uuid : uuid,
532 fixConsole : fixConsole,
532 fixConsole : fixConsole,
533 fixCarriageReturn : fixCarriageReturn,
533 fixCarriageReturn : fixCarriageReturn,
534 autoLinkUrls : autoLinkUrls,
534 autoLinkUrls : autoLinkUrls,
535 points_to_pixels : points_to_pixels,
535 points_to_pixels : points_to_pixels,
536 get_body_data : get_body_data,
536 get_body_data : get_body_data,
537 parse_url : parse_url,
537 parse_url : parse_url,
538 url_path_join : url_path_join,
538 url_path_join : url_path_join,
539 url_join_encode : url_join_encode,
539 url_join_encode : url_join_encode,
540 encode_uri_components : encode_uri_components,
540 encode_uri_components : encode_uri_components,
541 splitext : splitext,
541 splitext : splitext,
542 escape_html : escape_html,
542 escape_html : escape_html,
543 always_new : always_new,
543 always_new : always_new,
544 to_absolute_cursor_pos : to_absolute_cursor_pos,
544 to_absolute_cursor_pos : to_absolute_cursor_pos,
545 from_absolute_cursor_pos : from_absolute_cursor_pos,
545 from_absolute_cursor_pos : from_absolute_cursor_pos,
546 browser : browser,
546 browser : browser,
547 platform: platform,
547 platform: platform,
548 is_or_has : is_or_has,
548 is_or_has : is_or_has,
549 is_focused : is_focused,
549 is_focused : is_focused,
550 log_ajax_error : log_ajax_error,
550 log_ajax_error : log_ajax_error,
551 };
551 };
552
552
553 // Register self in the global namespace for convenience.
553 // Backwards compatability.
554 IPython.Utils = Utils;
554 IPython.Utils = Utils;
555
555 return Utils;
556 return Utils;
556 });
557 });
@@ -1,605 +1,583 b''
1 "components/codemirror/lib/codemirror.js",
1 // Copyright (c) IPython Development Team.
2 // Set codemirror version.
2 // Distributed under the terms of the Modified BSD License.
3 // CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
3
4 "components/codemirror/addon/mode/loadmode.js",
4 define([
5 "components/codemirror/addon/mode/multiplex.js",
5 'base/js/namespace',
6 "components/codemirror/addon/mode/overlay.js",
6 'components/jquery/jquery.min',
7 "components/codemirror/addon/edit/matchbrackets.js",
7 'components/codemirror/lib/codemirror.js',
8 "components/codemirror/addon/edit/closebrackets.js",
8 'base/js/utils',
9 "components/codemirror/addon/comment/comment.js",
9
10 "components/codemirror/mode/htmlmixed/htmlmixed.js",
10 // Set codemirror version.
11 "components/codemirror/mode/xml/xml.js",
11 // CodeMirror.modeURL = '{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}';
12 "components/codemirror/mode/javascript/javascript.js",
12 'components/codemirror/addon/mode/loadmode.js',
13 "components/codemirror/mode/css/css.js",
13 'components/codemirror/addon/mode/multiplex.js',
14 "components/codemirror/mode/rst/rst.js",
14 'components/codemirror/addon/mode/overlay.js',
15 "components/codemirror/mode/markdown/markdown.js",
15 'components/codemirror/addon/edit/matchbrackets.js',
16 "components/codemirror/mode/python/python.js",
16 'components/codemirror/addon/edit/closebrackets.js',
17 "notebook/js/codemirror-ipython.js",
17 'components/codemirror/addon/comment/comment.js',
18 "notebook/js/codemirror-ipythongfm.js",
18 'components/codemirror/mode/htmlmixed/htmlmixed.js',
19
19 'components/codemirror/mode/xml/xml.js',
20
20 'components/codemirror/mode/javascript/javascript.js',
21 // monkey patch CM to be able to syntax highlight cell magics
21 'components/codemirror/mode/css/css.js',
22 // bug reported upstream,
22 'components/codemirror/mode/rst/rst.js',
23 // see https://github.com/marijnh/CodeMirror2/issues/670
23 'components/codemirror/mode/markdown/markdown.js',
24 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
24 'components/codemirror/mode/python/python.js',
25 console.log('patching CM for undefined indent');
25 'notebook/js/codemirror-ipython.js',
26 CodeMirror.modes.null = function() {
26 'notebook/js/codemirror-ipythongfm.js',
27 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
27 ], function(IPython, $, CodeMirror, Utils) {
28 };
29 }
30
31 CodeMirror.patchedGetMode = function(config, mode){
32 var cmmode = CodeMirror.getMode(config, mode);
33 if(cmmode.indent === null) {
34 console.log('patch mode "' , mode, '" on the fly');
35 cmmode.indent = function(){return 0;};
36 }
37 return cmmode;
38 };
39 // end monkey patching CodeMirror
40
41
42 "notebook/js/tooltip",
43 Tooltip,
44 tooltip = new Tooltip();
45 IPython.tooltip = tooltip;
46
47
48
49 //----------------------------------------------------------------------------
50 // Copyright (C) 2008-2011 The IPython Development Team
51 //
52 // Distributed under the terms of the BSD License. The full license is in
53 // the file COPYING, distributed as part of this software.
54 //----------------------------------------------------------------------------
55
56 //============================================================================
57 // Cell
58 //============================================================================
59 /**
60 * An extendable module that provide base functionnality to create cell for notebook.
61 * @module IPython
62 * @namespace IPython
63 * @submodule Cell
64 */
65
66 var IPython = (function (IPython) {
67 "use strict";
28 "use strict";
68
29
69 var utils = IPython.utils;
30 // monkey patch CM to be able to syntax highlight cell magics
70 var keycodes = IPython.keyboard.keycodes;
31 // bug reported upstream,
32 // see https://github.com/marijnh/CodeMirror2/issues/670
33 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
34 console.log('patching CM for undefined indent');
35 CodeMirror.modes.null = function() {
36 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
37 };
38 }
39
40 CodeMirror.patchedGetMode = function(config, mode){
41 var cmmode = CodeMirror.getMode(config, mode);
42 if(cmmode.indent === null) {
43 console.log('patch mode "' , mode, '" on the fly');
44 cmmode.indent = function(){return 0;};
45 }
46 return cmmode;
47 };
48 // end monkey patching CodeMirror
71
49
72 /**
50 /**
73 * The Base `Cell` class from which to inherit
51 * The Base `Cell` class from which to inherit
74 * @class Cell
52 * @class Cell
75 **/
53 **/
76
54
77 /*
55 /*
78 * @constructor
56 * @constructor
79 *
57 *
80 * @param {object|undefined} [options]
58 * @param {object|undefined} [options]
81 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
59 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
82 */
60 */
83 var Cell = function (options) {
61 var Cell = function (keyboard_manager) {
84
62 this.keyboard_manager = keyboard_manager;
85 options = this.mergeopt(Cell, options);
63 options = this.mergeopt(Cell, options);
86 // superclass default overwrite our default
64 // superclass default overwrite our default
87
65
88 this.placeholder = options.placeholder || '';
66 this.placeholder = options.placeholder || '';
89 this.read_only = options.cm_config.readOnly;
67 this.read_only = options.cm_config.readOnly;
90 this.selected = false;
68 this.selected = false;
91 this.rendered = false;
69 this.rendered = false;
92 this.mode = 'command';
70 this.mode = 'command';
93 this.metadata = {};
71 this.metadata = {};
94 // load this from metadata later ?
72 // load this from metadata later ?
95 this.user_highlight = 'auto';
73 this.user_highlight = 'auto';
96 this.cm_config = options.cm_config;
74 this.cm_config = options.cm_config;
97 this.cell_id = utils.uuid();
75 this.cell_id = Utils.uuid();
98 this._options = options;
76 this._options = options;
99
77
100 // For JS VM engines optimization, attributes should be all set (even
78 // For JS VM engines optimization, attributes should be all set (even
101 // to null) in the constructor, and if possible, if different subclass
79 // to null) in the constructor, and if possible, if different subclass
102 // have new attributes with same name, they should be created in the
80 // have new attributes with same name, they should be created in the
103 // same order. Easiest is to create and set to null in parent class.
81 // same order. Easiest is to create and set to null in parent class.
104
82
105 this.element = null;
83 this.element = null;
106 this.cell_type = this.cell_type || null;
84 this.cell_type = this.cell_type || null;
107 this.code_mirror = null;
85 this.code_mirror = null;
108
86
109 this.create_element();
87 this.create_element();
110 if (this.element !== null) {
88 if (this.element !== null) {
111 this.element.data("cell", this);
89 this.element.data("cell", this);
112 this.bind_events();
90 this.bind_events();
113 this.init_classes();
91 this.init_classes();
114 }
92 }
115 };
93 };
116
94
117 Cell.options_default = {
95 Cell.options_default = {
118 cm_config : {
96 cm_config : {
119 indentUnit : 4,
97 indentUnit : 4,
120 readOnly: false,
98 readOnly: false,
121 theme: "default",
99 theme: "default",
122 extraKeys: {
100 extraKeys: {
123 "Cmd-Right":"goLineRight",
101 "Cmd-Right":"goLineRight",
124 "End":"goLineRight",
102 "End":"goLineRight",
125 "Cmd-Left":"goLineLeft"
103 "Cmd-Left":"goLineLeft"
126 }
104 }
127 }
105 }
128 };
106 };
129
107
130 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
108 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
131 // by disabling drag/drop altogether on Safari
109 // by disabling drag/drop altogether on Safari
132 // https://github.com/marijnh/CodeMirror/issues/332
110 // https://github.com/marijnh/CodeMirror/issues/332
133 if (utils.browser[0] == "Safari") {
111 if (Utils.browser[0] == "Safari") {
134 Cell.options_default.cm_config.dragDrop = false;
112 Cell.options_default.cm_config.dragDrop = false;
135 }
113 }
136
114
137 Cell.prototype.mergeopt = function(_class, options, overwrite){
115 Cell.prototype.mergeopt = function(_class, options, overwrite){
138 options = options || {};
116 options = options || {};
139 overwrite = overwrite || {};
117 overwrite = overwrite || {};
140 return $.extend(true, {}, _class.options_default, options, overwrite);
118 return $.extend(true, {}, _class.options_default, options, overwrite);
141 };
119 };
142
120
143 /**
121 /**
144 * Empty. Subclasses must implement create_element.
122 * Empty. Subclasses must implement create_element.
145 * This should contain all the code to create the DOM element in notebook
123 * This should contain all the code to create the DOM element in notebook
146 * and will be called by Base Class constructor.
124 * and will be called by Base Class constructor.
147 * @method create_element
125 * @method create_element
148 */
126 */
149 Cell.prototype.create_element = function () {
127 Cell.prototype.create_element = function () {
150 };
128 };
151
129
152 Cell.prototype.init_classes = function () {
130 Cell.prototype.init_classes = function () {
153 // Call after this.element exists to initialize the css classes
131 // Call after this.element exists to initialize the css classes
154 // related to selected, rendered and mode.
132 // related to selected, rendered and mode.
155 if (this.selected) {
133 if (this.selected) {
156 this.element.addClass('selected');
134 this.element.addClass('selected');
157 } else {
135 } else {
158 this.element.addClass('unselected');
136 this.element.addClass('unselected');
159 }
137 }
160 if (this.rendered) {
138 if (this.rendered) {
161 this.element.addClass('rendered');
139 this.element.addClass('rendered');
162 } else {
140 } else {
163 this.element.addClass('unrendered');
141 this.element.addClass('unrendered');
164 }
142 }
165 if (this.mode === 'edit') {
143 if (this.mode === 'edit') {
166 this.element.addClass('edit_mode');
144 this.element.addClass('edit_mode');
167 } else {
145 } else {
168 this.element.addClass('command_mode');
146 this.element.addClass('command_mode');
169 }
147 }
170 };
148 };
171
149
172 /**
150 /**
173 * Subclasses can implement override bind_events.
151 * Subclasses can implement override bind_events.
174 * Be carefull to call the parent method when overwriting as it fires event.
152 * Be carefull to call the parent method when overwriting as it fires event.
175 * this will be triggerd after create_element in constructor.
153 * this will be triggerd after create_element in constructor.
176 * @method bind_events
154 * @method bind_events
177 */
155 */
178 Cell.prototype.bind_events = function () {
156 Cell.prototype.bind_events = function () {
179 var that = this;
157 var that = this;
180 // We trigger events so that Cell doesn't have to depend on Notebook.
158 // We trigger events so that Cell doesn't have to depend on Notebook.
181 that.element.click(function (event) {
159 that.element.click(function (event) {
182 if (!that.selected) {
160 if (!that.selected) {
183 $([IPython.events]).trigger('select.Cell', {'cell':that});
161 that.events.trigger('select.Cell', {'cell':that});
184 }
162 }
185 });
163 });
186 that.element.focusin(function (event) {
164 that.element.focusin(function (event) {
187 if (!that.selected) {
165 if (!that.selected) {
188 $([IPython.events]).trigger('select.Cell', {'cell':that});
166 that.events.trigger('select.Cell', {'cell':that});
189 }
167 }
190 });
168 });
191 if (this.code_mirror) {
169 if (this.code_mirror) {
192 this.code_mirror.on("change", function(cm, change) {
170 this.code_mirror.on("change", function(cm, change) {
193 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
171 that.events.trigger("set_dirty.Notebook", {value: true});
194 });
172 });
195 }
173 }
196 if (this.code_mirror) {
174 if (this.code_mirror) {
197 this.code_mirror.on('focus', function(cm, change) {
175 this.code_mirror.on('focus', function(cm, change) {
198 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
176 that.events.trigger('edit_mode.Cell', {cell: that});
199 });
177 });
200 }
178 }
201 if (this.code_mirror) {
179 if (this.code_mirror) {
202 this.code_mirror.on('blur', function(cm, change) {
180 this.code_mirror.on('blur', function(cm, change) {
203 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
181 that.events.trigger('command_mode.Cell', {cell: that});
204 });
182 });
205 }
183 }
206 };
184 };
207
185
208 /**
186 /**
209 * This method gets called in CodeMirror's onKeyDown/onKeyPress
187 * This method gets called in CodeMirror's onKeyDown/onKeyPress
210 * handlers and is used to provide custom key handling.
188 * handlers and is used to provide custom key handling.
211 *
189 *
212 * To have custom handling, subclasses should override this method, but still call it
190 * To have custom handling, subclasses should override this method, but still call it
213 * in order to process the Edit mode keyboard shortcuts.
191 * in order to process the Edit mode keyboard shortcuts.
214 *
192 *
215 * @method handle_codemirror_keyevent
193 * @method handle_codemirror_keyevent
216 * @param {CodeMirror} editor - The codemirror instance bound to the cell
194 * @param {CodeMirror} editor - The codemirror instance bound to the cell
217 * @param {event} event - key press event which either should or should not be handled by CodeMirror
195 * @param {event} event - key press event which either should or should not be handled by CodeMirror
218 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
196 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
219 */
197 */
220 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
198 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
221 var that = this;
199 var that = this;
222 var shortcuts = IPython.keyboard_manager.edit_shortcuts;
200 var shortcuts = this.keyboard_manager.edit_shortcuts;
223
201
224 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
202 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
225 // manager will handle it
203 // manager will handle it
226 if (shortcuts.handles(event)) { return true; }
204 if (shortcuts.handles(event)) { return true; }
227
205
228 return false;
206 return false;
229 };
207 };
230
208
231
209
232 /**
210 /**
233 * Triger typsetting of math by mathjax on current cell element
211 * Triger typsetting of math by mathjax on current cell element
234 * @method typeset
212 * @method typeset
235 */
213 */
236 Cell.prototype.typeset = function () {
214 Cell.prototype.typeset = function () {
237 if (window.MathJax) {
215 if (window.MathJax) {
238 var cell_math = this.element.get(0);
216 var cell_math = this.element.get(0);
239 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
217 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
240 }
218 }
241 };
219 };
242
220
243 /**
221 /**
244 * handle cell level logic when a cell is selected
222 * handle cell level logic when a cell is selected
245 * @method select
223 * @method select
246 * @return is the action being taken
224 * @return is the action being taken
247 */
225 */
248 Cell.prototype.select = function () {
226 Cell.prototype.select = function () {
249 if (!this.selected) {
227 if (!this.selected) {
250 this.element.addClass('selected');
228 this.element.addClass('selected');
251 this.element.removeClass('unselected');
229 this.element.removeClass('unselected');
252 this.selected = true;
230 this.selected = true;
253 return true;
231 return true;
254 } else {
232 } else {
255 return false;
233 return false;
256 }
234 }
257 };
235 };
258
236
259 /**
237 /**
260 * handle cell level logic when a cell is unselected
238 * handle cell level logic when a cell is unselected
261 * @method unselect
239 * @method unselect
262 * @return is the action being taken
240 * @return is the action being taken
263 */
241 */
264 Cell.prototype.unselect = function () {
242 Cell.prototype.unselect = function () {
265 if (this.selected) {
243 if (this.selected) {
266 this.element.addClass('unselected');
244 this.element.addClass('unselected');
267 this.element.removeClass('selected');
245 this.element.removeClass('selected');
268 this.selected = false;
246 this.selected = false;
269 return true;
247 return true;
270 } else {
248 } else {
271 return false;
249 return false;
272 }
250 }
273 };
251 };
274
252
275 /**
253 /**
276 * handle cell level logic when a cell is rendered
254 * handle cell level logic when a cell is rendered
277 * @method render
255 * @method render
278 * @return is the action being taken
256 * @return is the action being taken
279 */
257 */
280 Cell.prototype.render = function () {
258 Cell.prototype.render = function () {
281 if (!this.rendered) {
259 if (!this.rendered) {
282 this.element.addClass('rendered');
260 this.element.addClass('rendered');
283 this.element.removeClass('unrendered');
261 this.element.removeClass('unrendered');
284 this.rendered = true;
262 this.rendered = true;
285 return true;
263 return true;
286 } else {
264 } else {
287 return false;
265 return false;
288 }
266 }
289 };
267 };
290
268
291 /**
269 /**
292 * handle cell level logic when a cell is unrendered
270 * handle cell level logic when a cell is unrendered
293 * @method unrender
271 * @method unrender
294 * @return is the action being taken
272 * @return is the action being taken
295 */
273 */
296 Cell.prototype.unrender = function () {
274 Cell.prototype.unrender = function () {
297 if (this.rendered) {
275 if (this.rendered) {
298 this.element.addClass('unrendered');
276 this.element.addClass('unrendered');
299 this.element.removeClass('rendered');
277 this.element.removeClass('rendered');
300 this.rendered = false;
278 this.rendered = false;
301 return true;
279 return true;
302 } else {
280 } else {
303 return false;
281 return false;
304 }
282 }
305 };
283 };
306
284
307 /**
285 /**
308 * Delegates keyboard shortcut handling to either IPython keyboard
286 * Delegates keyboard shortcut handling to either IPython keyboard
309 * manager when in command mode, or CodeMirror when in edit mode
287 * manager when in command mode, or CodeMirror when in edit mode
310 *
288 *
311 * @method handle_keyevent
289 * @method handle_keyevent
312 * @param {CodeMirror} editor - The codemirror instance bound to the cell
290 * @param {CodeMirror} editor - The codemirror instance bound to the cell
313 * @param {event} - key event to be handled
291 * @param {event} - key event to be handled
314 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
292 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
315 */
293 */
316 Cell.prototype.handle_keyevent = function (editor, event) {
294 Cell.prototype.handle_keyevent = function (editor, event) {
317
295
318 // console.log('CM', this.mode, event.which, event.type)
296 // console.log('CM', this.mode, event.which, event.type)
319
297
320 if (this.mode === 'command') {
298 if (this.mode === 'command') {
321 return true;
299 return true;
322 } else if (this.mode === 'edit') {
300 } else if (this.mode === 'edit') {
323 return this.handle_codemirror_keyevent(editor, event);
301 return this.handle_codemirror_keyevent(editor, event);
324 }
302 }
325 };
303 };
326
304
327 /**
305 /**
328 * @method at_top
306 * @method at_top
329 * @return {Boolean}
307 * @return {Boolean}
330 */
308 */
331 Cell.prototype.at_top = function () {
309 Cell.prototype.at_top = function () {
332 var cm = this.code_mirror;
310 var cm = this.code_mirror;
333 var cursor = cm.getCursor();
311 var cursor = cm.getCursor();
334 if (cursor.line === 0 && cursor.ch === 0) {
312 if (cursor.line === 0 && cursor.ch === 0) {
335 return true;
313 return true;
336 }
314 }
337 return false;
315 return false;
338 };
316 };
339
317
340 /**
318 /**
341 * @method at_bottom
319 * @method at_bottom
342 * @return {Boolean}
320 * @return {Boolean}
343 * */
321 * */
344 Cell.prototype.at_bottom = function () {
322 Cell.prototype.at_bottom = function () {
345 var cm = this.code_mirror;
323 var cm = this.code_mirror;
346 var cursor = cm.getCursor();
324 var cursor = cm.getCursor();
347 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
325 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
348 return true;
326 return true;
349 }
327 }
350 return false;
328 return false;
351 };
329 };
352
330
353 /**
331 /**
354 * enter the command mode for the cell
332 * enter the command mode for the cell
355 * @method command_mode
333 * @method command_mode
356 * @return is the action being taken
334 * @return is the action being taken
357 */
335 */
358 Cell.prototype.command_mode = function () {
336 Cell.prototype.command_mode = function () {
359 if (this.mode !== 'command') {
337 if (this.mode !== 'command') {
360 this.element.addClass('command_mode');
338 this.element.addClass('command_mode');
361 this.element.removeClass('edit_mode');
339 this.element.removeClass('edit_mode');
362 this.mode = 'command';
340 this.mode = 'command';
363 return true;
341 return true;
364 } else {
342 } else {
365 return false;
343 return false;
366 }
344 }
367 };
345 };
368
346
369 /**
347 /**
370 * enter the edit mode for the cell
348 * enter the edit mode for the cell
371 * @method command_mode
349 * @method command_mode
372 * @return is the action being taken
350 * @return is the action being taken
373 */
351 */
374 Cell.prototype.edit_mode = function () {
352 Cell.prototype.edit_mode = function () {
375 if (this.mode !== 'edit') {
353 if (this.mode !== 'edit') {
376 this.element.addClass('edit_mode');
354 this.element.addClass('edit_mode');
377 this.element.removeClass('command_mode');
355 this.element.removeClass('command_mode');
378 this.mode = 'edit';
356 this.mode = 'edit';
379 return true;
357 return true;
380 } else {
358 } else {
381 return false;
359 return false;
382 }
360 }
383 };
361 };
384
362
385 /**
363 /**
386 * Focus the cell in the DOM sense
364 * Focus the cell in the DOM sense
387 * @method focus_cell
365 * @method focus_cell
388 */
366 */
389 Cell.prototype.focus_cell = function () {
367 Cell.prototype.focus_cell = function () {
390 this.element.focus();
368 this.element.focus();
391 };
369 };
392
370
393 /**
371 /**
394 * Focus the editor area so a user can type
372 * Focus the editor area so a user can type
395 *
373 *
396 * NOTE: If codemirror is focused via a mouse click event, you don't want to
374 * NOTE: If codemirror is focused via a mouse click event, you don't want to
397 * call this because it will cause a page jump.
375 * call this because it will cause a page jump.
398 * @method focus_editor
376 * @method focus_editor
399 */
377 */
400 Cell.prototype.focus_editor = function () {
378 Cell.prototype.focus_editor = function () {
401 this.refresh();
379 this.refresh();
402 this.code_mirror.focus();
380 this.code_mirror.focus();
403 };
381 };
404
382
405 /**
383 /**
406 * Refresh codemirror instance
384 * Refresh codemirror instance
407 * @method refresh
385 * @method refresh
408 */
386 */
409 Cell.prototype.refresh = function () {
387 Cell.prototype.refresh = function () {
410 this.code_mirror.refresh();
388 this.code_mirror.refresh();
411 };
389 };
412
390
413 /**
391 /**
414 * should be overritten by subclass
392 * should be overritten by subclass
415 * @method get_text
393 * @method get_text
416 */
394 */
417 Cell.prototype.get_text = function () {
395 Cell.prototype.get_text = function () {
418 };
396 };
419
397
420 /**
398 /**
421 * should be overritten by subclass
399 * should be overritten by subclass
422 * @method set_text
400 * @method set_text
423 * @param {string} text
401 * @param {string} text
424 */
402 */
425 Cell.prototype.set_text = function (text) {
403 Cell.prototype.set_text = function (text) {
426 };
404 };
427
405
428 /**
406 /**
429 * should be overritten by subclass
407 * should be overritten by subclass
430 * serialise cell to json.
408 * serialise cell to json.
431 * @method toJSON
409 * @method toJSON
432 **/
410 **/
433 Cell.prototype.toJSON = function () {
411 Cell.prototype.toJSON = function () {
434 var data = {};
412 var data = {};
435 data.metadata = this.metadata;
413 data.metadata = this.metadata;
436 data.cell_type = this.cell_type;
414 data.cell_type = this.cell_type;
437 return data;
415 return data;
438 };
416 };
439
417
440
418
441 /**
419 /**
442 * should be overritten by subclass
420 * should be overritten by subclass
443 * @method fromJSON
421 * @method fromJSON
444 **/
422 **/
445 Cell.prototype.fromJSON = function (data) {
423 Cell.prototype.fromJSON = function (data) {
446 if (data.metadata !== undefined) {
424 if (data.metadata !== undefined) {
447 this.metadata = data.metadata;
425 this.metadata = data.metadata;
448 }
426 }
449 this.celltoolbar.rebuild();
427 this.celltoolbar.rebuild();
450 };
428 };
451
429
452
430
453 /**
431 /**
454 * can the cell be split into two cells
432 * can the cell be split into two cells
455 * @method is_splittable
433 * @method is_splittable
456 **/
434 **/
457 Cell.prototype.is_splittable = function () {
435 Cell.prototype.is_splittable = function () {
458 return true;
436 return true;
459 };
437 };
460
438
461
439
462 /**
440 /**
463 * can the cell be merged with other cells
441 * can the cell be merged with other cells
464 * @method is_mergeable
442 * @method is_mergeable
465 **/
443 **/
466 Cell.prototype.is_mergeable = function () {
444 Cell.prototype.is_mergeable = function () {
467 return true;
445 return true;
468 };
446 };
469
447
470
448
471 /**
449 /**
472 * @return {String} - the text before the cursor
450 * @return {String} - the text before the cursor
473 * @method get_pre_cursor
451 * @method get_pre_cursor
474 **/
452 **/
475 Cell.prototype.get_pre_cursor = function () {
453 Cell.prototype.get_pre_cursor = function () {
476 var cursor = this.code_mirror.getCursor();
454 var cursor = this.code_mirror.getCursor();
477 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
455 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
478 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
456 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
479 return text;
457 return text;
480 };
458 };
481
459
482
460
483 /**
461 /**
484 * @return {String} - the text after the cursor
462 * @return {String} - the text after the cursor
485 * @method get_post_cursor
463 * @method get_post_cursor
486 **/
464 **/
487 Cell.prototype.get_post_cursor = function () {
465 Cell.prototype.get_post_cursor = function () {
488 var cursor = this.code_mirror.getCursor();
466 var cursor = this.code_mirror.getCursor();
489 var last_line_num = this.code_mirror.lineCount()-1;
467 var last_line_num = this.code_mirror.lineCount()-1;
490 var last_line_len = this.code_mirror.getLine(last_line_num).length;
468 var last_line_len = this.code_mirror.getLine(last_line_num).length;
491 var end = {line:last_line_num, ch:last_line_len};
469 var end = {line:last_line_num, ch:last_line_len};
492 var text = this.code_mirror.getRange(cursor, end);
470 var text = this.code_mirror.getRange(cursor, end);
493 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
471 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
494 return text;
472 return text;
495 };
473 };
496
474
497 /**
475 /**
498 * Show/Hide CodeMirror LineNumber
476 * Show/Hide CodeMirror LineNumber
499 * @method show_line_numbers
477 * @method show_line_numbers
500 *
478 *
501 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
479 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
502 **/
480 **/
503 Cell.prototype.show_line_numbers = function (value) {
481 Cell.prototype.show_line_numbers = function (value) {
504 this.code_mirror.setOption('lineNumbers', value);
482 this.code_mirror.setOption('lineNumbers', value);
505 this.code_mirror.refresh();
483 this.code_mirror.refresh();
506 };
484 };
507
485
508 /**
486 /**
509 * Toggle CodeMirror LineNumber
487 * Toggle CodeMirror LineNumber
510 * @method toggle_line_numbers
488 * @method toggle_line_numbers
511 **/
489 **/
512 Cell.prototype.toggle_line_numbers = function () {
490 Cell.prototype.toggle_line_numbers = function () {
513 var val = this.code_mirror.getOption('lineNumbers');
491 var val = this.code_mirror.getOption('lineNumbers');
514 this.show_line_numbers(!val);
492 this.show_line_numbers(!val);
515 };
493 };
516
494
517 /**
495 /**
518 * Force codemirror highlight mode
496 * Force codemirror highlight mode
519 * @method force_highlight
497 * @method force_highlight
520 * @param {object} - CodeMirror mode
498 * @param {object} - CodeMirror mode
521 **/
499 **/
522 Cell.prototype.force_highlight = function(mode) {
500 Cell.prototype.force_highlight = function(mode) {
523 this.user_highlight = mode;
501 this.user_highlight = mode;
524 this.auto_highlight();
502 this.auto_highlight();
525 };
503 };
526
504
527 /**
505 /**
528 * Try to autodetect cell highlight mode, or use selected mode
506 * Try to autodetect cell highlight mode, or use selected mode
529 * @methods _auto_highlight
507 * @methods _auto_highlight
530 * @private
508 * @private
531 * @param {String|object|undefined} - CodeMirror mode | 'auto'
509 * @param {String|object|undefined} - CodeMirror mode | 'auto'
532 **/
510 **/
533 Cell.prototype._auto_highlight = function (modes) {
511 Cell.prototype._auto_highlight = function (modes) {
534 //Here we handle manually selected modes
512 //Here we handle manually selected modes
535 var mode;
513 var mode;
536 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
514 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
537 {
515 {
538 mode = this.user_highlight;
516 mode = this.user_highlight;
539 CodeMirror.autoLoadMode(this.code_mirror, mode);
517 CodeMirror.autoLoadMode(this.code_mirror, mode);
540 this.code_mirror.setOption('mode', mode);
518 this.code_mirror.setOption('mode', mode);
541 return;
519 return;
542 }
520 }
543 var current_mode = this.code_mirror.getOption('mode', mode);
521 var current_mode = this.code_mirror.getOption('mode', mode);
544 var first_line = this.code_mirror.getLine(0);
522 var first_line = this.code_mirror.getLine(0);
545 // loop on every pairs
523 // loop on every pairs
546 for(mode in modes) {
524 for(mode in modes) {
547 var regs = modes[mode].reg;
525 var regs = modes[mode].reg;
548 // only one key every time but regexp can't be keys...
526 // only one key every time but regexp can't be keys...
549 for(var i=0; i<regs.length; i++) {
527 for(var i=0; i<regs.length; i++) {
550 // here we handle non magic_modes
528 // here we handle non magic_modes
551 if(first_line.match(regs[i]) !== null) {
529 if(first_line.match(regs[i]) !== null) {
552 if(current_mode == mode){
530 if(current_mode == mode){
553 return;
531 return;
554 }
532 }
555 if (mode.search('magic_') !== 0) {
533 if (mode.search('magic_') !== 0) {
556 this.code_mirror.setOption('mode', mode);
534 this.code_mirror.setOption('mode', mode);
557 CodeMirror.autoLoadMode(this.code_mirror, mode);
535 CodeMirror.autoLoadMode(this.code_mirror, mode);
558 return;
536 return;
559 }
537 }
560 var open = modes[mode].open || "%%";
538 var open = modes[mode].open || "%%";
561 var close = modes[mode].close || "%%end";
539 var close = modes[mode].close || "%%end";
562 var mmode = mode;
540 var mmode = mode;
563 mode = mmode.substr(6);
541 mode = mmode.substr(6);
564 if(current_mode == mode){
542 if(current_mode == mode){
565 return;
543 return;
566 }
544 }
567 CodeMirror.autoLoadMode(this.code_mirror, mode);
545 CodeMirror.autoLoadMode(this.code_mirror, mode);
568 // create on the fly a mode that swhitch between
546 // create on the fly a mode that swhitch between
569 // plain/text and smth else otherwise `%%` is
547 // plain/text and smth else otherwise `%%` is
570 // source of some highlight issues.
548 // source of some highlight issues.
571 // we use patchedGetMode to circumvent a bug in CM
549 // we use patchedGetMode to circumvent a bug in CM
572 CodeMirror.defineMode(mmode , function(config) {
550 CodeMirror.defineMode(mmode , function(config) {
573 return CodeMirror.multiplexingMode(
551 return CodeMirror.multiplexingMode(
574 CodeMirror.patchedGetMode(config, 'text/plain'),
552 CodeMirror.patchedGetMode(config, 'text/plain'),
575 // always set someting on close
553 // always set someting on close
576 {open: open, close: close,
554 {open: open, close: close,
577 mode: CodeMirror.patchedGetMode(config, mode),
555 mode: CodeMirror.patchedGetMode(config, mode),
578 delimStyle: "delimit"
556 delimStyle: "delimit"
579 }
557 }
580 );
558 );
581 });
559 });
582 this.code_mirror.setOption('mode', mmode);
560 this.code_mirror.setOption('mode', mmode);
583 return;
561 return;
584 }
562 }
585 }
563 }
586 }
564 }
587 // fallback on default
565 // fallback on default
588 var default_mode;
566 var default_mode;
589 try {
567 try {
590 default_mode = this._options.cm_config.mode;
568 default_mode = this._options.cm_config.mode;
591 } catch(e) {
569 } catch(e) {
592 default_mode = 'text/plain';
570 default_mode = 'text/plain';
593 }
571 }
594 if( current_mode === default_mode){
572 if( current_mode === default_mode){
595 return;
573 return;
596 }
574 }
597 this.code_mirror.setOption('mode', default_mode);
575 this.code_mirror.setOption('mode', default_mode);
598 };
576 };
599
577
578 // Backwards compatability.
600 IPython.Cell = Cell;
579 IPython.Cell = Cell;
601
580
602 return IPython;
581 return Cell;
603
604 }(IPython));
605
582
583 });
@@ -1,422 +1,413 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'components/jquery/jquery.min',
7
7 'notebook/js/textcell',
8 //============================================================================
8 ], function(IPython, $, TextCell) {
9 // CellToolbar
10 //============================================================================
11
12
13 /**
14 * A Module to control the per-cell toolbar.
15 * @module IPython
16 * @namespace IPython
17 * @submodule CellToolbar
18 */
19 var IPython = (function (IPython) {
20 "use strict";
9 "use strict";
21
10
22 /**
11 /**
23 * @constructor
12 * @constructor
24 * @class CellToolbar
13 * @class CellToolbar
25 * @param {The cell to attach the metadata UI to} cell
14 * @param {The cell to attach the metadata UI to} cell
26 */
15 */
27 var CellToolbar = function (cell) {
16 var CellToolbar = function (cell, events, notebook) {
28 CellToolbar._instances.push(this);
17 CellToolbar._instances.push(this);
18 this.notebook = notebook;
19 this.events = events;
29 this.cell = cell;
20 this.cell = cell;
30 this.create_element();
21 this.create_element();
31 this.rebuild();
22 this.rebuild();
32 return this;
23 return this;
33 };
24 };
34
25
35
26
36 CellToolbar.prototype.create_element = function () {
27 CellToolbar.prototype.create_element = function () {
37 this.inner_element = $('<div/>').addClass('celltoolbar')
28 this.inner_element = $('<div/>').addClass('celltoolbar');
38 this.element = $('<div/>').addClass('ctb_hideshow')
29 this.element = $('<div/>').addClass('ctb_hideshow')
39 .append(this.inner_element);
30 .append(this.inner_element);
40 };
31 };
41
32
42
33
43 // The default css style for the outer celltoolbar div
34 // The default css style for the outer celltoolbar div
44 // (ctb_hideshow) is display: none.
35 // (ctb_hideshow) is display: none.
45 // To show the cell toolbar, *both* of the following conditions must be met:
36 // To show the cell toolbar, *both* of the following conditions must be met:
46 // - A parent container has class `ctb_global_show`
37 // - A parent container has class `ctb_global_show`
47 // - The celltoolbar has the class `ctb_show`
38 // - The celltoolbar has the class `ctb_show`
48 // This allows global show/hide, as well as per-cell show/hide.
39 // This allows global show/hide, as well as per-cell show/hide.
49
40
50 CellToolbar.global_hide = function () {
41 CellToolbar.global_hide = function () {
51 $('body').removeClass('ctb_global_show');
42 $('body').removeClass('ctb_global_show');
52 };
43 };
53
44
54
45
55 CellToolbar.global_show = function () {
46 CellToolbar.global_show = function () {
56 $('body').addClass('ctb_global_show');
47 $('body').addClass('ctb_global_show');
57 };
48 };
58
49
59
50
60 CellToolbar.prototype.hide = function () {
51 CellToolbar.prototype.hide = function () {
61 this.element.removeClass('ctb_show');
52 this.element.removeClass('ctb_show');
62 };
53 };
63
54
64
55
65 CellToolbar.prototype.show = function () {
56 CellToolbar.prototype.show = function () {
66 this.element.addClass('ctb_show');
57 this.element.addClass('ctb_show');
67 };
58 };
68
59
69
60
70 /**
61 /**
71 * Class variable that should contain a dict of all available callback
62 * Class variable that should contain a dict of all available callback
72 * we need to think of wether or not we allow nested namespace
63 * we need to think of wether or not we allow nested namespace
73 * @property _callback_dict
64 * @property _callback_dict
74 * @private
65 * @private
75 * @static
66 * @static
76 * @type Dict
67 * @type Dict
77 */
68 */
78 CellToolbar._callback_dict = {};
69 CellToolbar._callback_dict = {};
79
70
80
71
81 /**
72 /**
82 * Class variable that should contain the reverse order list of the button
73 * Class variable that should contain the reverse order list of the button
83 * to add to the toolbar of each cell
74 * to add to the toolbar of each cell
84 * @property _ui_controls_list
75 * @property _ui_controls_list
85 * @private
76 * @private
86 * @static
77 * @static
87 * @type List
78 * @type List
88 */
79 */
89 CellToolbar._ui_controls_list = [];
80 CellToolbar._ui_controls_list = [];
90
81
91
82
92 /**
83 /**
93 * Class variable that should contain the CellToolbar instances for each
84 * Class variable that should contain the CellToolbar instances for each
94 * cell of the notebook
85 * cell of the notebook
95 *
86 *
96 * @private
87 * @private
97 * @property _instances
88 * @property _instances
98 * @static
89 * @static
99 * @type List
90 * @type List
100 */
91 */
101 CellToolbar._instances = [];
92 CellToolbar._instances = [];
102
93
103
94
104 /**
95 /**
105 * keep a list of all the available presets for the toolbar
96 * keep a list of all the available presets for the toolbar
106 * @private
97 * @private
107 * @property _presets
98 * @property _presets
108 * @static
99 * @static
109 * @type Dict
100 * @type Dict
110 */
101 */
111 CellToolbar._presets = {};
102 CellToolbar._presets = {};
112
103
113
104
114 // this is by design not a prototype.
105 // this is by design not a prototype.
115 /**
106 /**
116 * Register a callback to create an UI element in a cell toolbar.
107 * Register a callback to create an UI element in a cell toolbar.
117 * @method register_callback
108 * @method register_callback
118 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
109 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
119 * for easier sorting and avoid collision
110 * for easier sorting and avoid collision
120 * @param callback {function(div, cell)} callback that will be called to generate the ui element
111 * @param callback {function(div, cell)} callback that will be called to generate the ui element
121 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
112 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
122 * will be added only to cells of types in the list.
113 * will be added only to cells of types in the list.
123 *
114 *
124 *
115 *
125 * The callback will receive the following element :
116 * The callback will receive the following element :
126 *
117 *
127 * * a div in which to add element.
118 * * a div in which to add element.
128 * * the cell it is responsible from
119 * * the cell it is responsible from
129 *
120 *
130 * @example
121 * @example
131 *
122 *
132 * Example that create callback for a button that toggle between `true` and `false` label,
123 * Example that create callback for a button that toggle between `true` and `false` label,
133 * with the metadata under the key 'foo' to reflect the status of the button.
124 * with the metadata under the key 'foo' to reflect the status of the button.
134 *
125 *
135 * // first param reference to a DOM div
126 * // first param reference to a DOM div
136 * // second param reference to the cell.
127 * // second param reference to the cell.
137 * var toggle = function(div, cell) {
128 * var toggle = function(div, cell) {
138 * var button_container = $(div)
129 * var button_container = $(div)
139 *
130 *
140 * // let's create a button that show the current value of the metadata
131 * // let's create a button that show the current value of the metadata
141 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
132 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
142 *
133 *
143 * // On click, change the metadata value and update the button label
134 * // On click, change the metadata value and update the button label
144 * button.click(function(){
135 * button.click(function(){
145 * var v = cell.metadata.foo;
136 * var v = cell.metadata.foo;
146 * cell.metadata.foo = !v;
137 * cell.metadata.foo = !v;
147 * button.button("option", "label", String(!v));
138 * button.button("option", "label", String(!v));
148 * })
139 * })
149 *
140 *
150 * // add the button to the DOM div.
141 * // add the button to the DOM div.
151 * button_container.append(button);
142 * button_container.append(button);
152 * }
143 * }
153 *
144 *
154 * // now we register the callback under the name `foo` to give the
145 * // now we register the callback under the name `foo` to give the
155 * // user the ability to use it later
146 * // user the ability to use it later
156 * CellToolbar.register_callback('foo', toggle);
147 * CellToolbar.register_callback('foo', toggle);
157 */
148 */
158 CellToolbar.register_callback = function(name, callback, cell_types) {
149 CellToolbar.register_callback = function(name, callback, cell_types) {
159 // Overwrite if it already exists.
150 // Overwrite if it already exists.
160 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
151 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
161 };
152 };
162
153
163
154
164 /**
155 /**
165 * Register a preset of UI element in a cell toolbar.
156 * Register a preset of UI element in a cell toolbar.
166 * Not supported Yet.
157 * Not supported Yet.
167 * @method register_preset
158 * @method register_preset
168 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
159 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
169 * for easier sorting and avoid collision
160 * for easier sorting and avoid collision
170 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
161 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
171 * should correspond to a name of a registerd callback.
162 * should correspond to a name of a registerd callback.
172 *
163 *
173 * @private
164 * @private
174 * @example
165 * @example
175 *
166 *
176 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
167 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
177 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
168 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
178 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
169 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
179 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
170 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
180 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
171 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
181 *
172 *
182 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
173 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
183 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
174 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
184 */
175 */
185 CellToolbar.register_preset = function(name, preset_list) {
176 CellToolbar.register_preset = function(name, preset_list) {
186 CellToolbar._presets[name] = preset_list;
177 CellToolbar._presets[name] = preset_list;
187 $([IPython.events]).trigger('preset_added.CellToolbar', {name: name});
178 this.events.trigger('preset_added.CellToolbar', {name: name});
188 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
179 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
189 // In that case, activate the preset if needed.
180 // In that case, activate the preset if needed.
190 if (IPython.notebook && IPython.notebook.metadata && IPython.notebook.metadata.celltoolbar === name)
181 if (this.notebook && this.notebook.metadata && this.notebook.metadata.celltoolbar === name)
191 this.activate_preset(name);
182 this.activate_preset(name);
192 };
183 };
193
184
194
185
195 /**
186 /**
196 * List the names of the presets that are currently registered.
187 * List the names of the presets that are currently registered.
197 *
188 *
198 * @method list_presets
189 * @method list_presets
199 * @static
190 * @static
200 */
191 */
201 CellToolbar.list_presets = function() {
192 CellToolbar.list_presets = function() {
202 var keys = [];
193 var keys = [];
203 for (var k in CellToolbar._presets) {
194 for (var k in CellToolbar._presets) {
204 keys.push(k);
195 keys.push(k);
205 }
196 }
206 return keys;
197 return keys;
207 };
198 };
208
199
209
200
210 /**
201 /**
211 * Activate an UI preset from `register_preset`
202 * Activate an UI preset from `register_preset`
212 *
203 *
213 * This does not update the selection UI.
204 * This does not update the selection UI.
214 *
205 *
215 * @method activate_preset
206 * @method activate_preset
216 * @param preset_name {String} string corresponding to the preset name
207 * @param preset_name {String} string corresponding to the preset name
217 *
208 *
218 * @static
209 * @static
219 * @private
210 * @private
220 * @example
211 * @example
221 *
212 *
222 * CellToolbar.activate_preset('foo.foo_preset1');
213 * CellToolbar.activate_preset('foo.foo_preset1');
223 */
214 */
224 CellToolbar.activate_preset = function(preset_name){
215 CellToolbar.activate_preset = function(preset_name){
225 var preset = CellToolbar._presets[preset_name];
216 var preset = CellToolbar._presets[preset_name];
226
217
227 if(preset !== undefined){
218 if(preset !== undefined){
228 CellToolbar._ui_controls_list = preset;
219 CellToolbar._ui_controls_list = preset;
229 CellToolbar.rebuild_all();
220 CellToolbar.rebuild_all();
230 }
221 }
231
222
232 $([IPython.events]).trigger('preset_activated.CellToolbar', {name: preset_name});
223 this.events.trigger('preset_activated.CellToolbar', {name: preset_name});
233 };
224 };
234
225
235
226
236 /**
227 /**
237 * This should be called on the class and not on a instance as it will trigger
228 * This should be called on the class and not on a instance as it will trigger
238 * rebuild of all the instances.
229 * rebuild of all the instances.
239 * @method rebuild_all
230 * @method rebuild_all
240 * @static
231 * @static
241 *
232 *
242 */
233 */
243 CellToolbar.rebuild_all = function(){
234 CellToolbar.rebuild_all = function(){
244 for(var i=0; i < CellToolbar._instances.length; i++){
235 for(var i=0; i < CellToolbar._instances.length; i++){
245 CellToolbar._instances[i].rebuild();
236 CellToolbar._instances[i].rebuild();
246 }
237 }
247 };
238 };
248
239
249 /**
240 /**
250 * Rebuild all the button on the toolbar to update its state.
241 * Rebuild all the button on the toolbar to update its state.
251 * @method rebuild
242 * @method rebuild
252 */
243 */
253 CellToolbar.prototype.rebuild = function(){
244 CellToolbar.prototype.rebuild = function(){
254 // strip evrything from the div
245 // strip evrything from the div
255 // which is probably inner_element
246 // which is probably inner_element
256 // or this.element.
247 // or this.element.
257 this.inner_element.empty();
248 this.inner_element.empty();
258 this.ui_controls_list = [];
249 this.ui_controls_list = [];
259
250
260 var callbacks = CellToolbar._callback_dict;
251 var callbacks = CellToolbar._callback_dict;
261 var preset = CellToolbar._ui_controls_list;
252 var preset = CellToolbar._ui_controls_list;
262 // Yes we iterate on the class variable, not the instance one.
253 // Yes we iterate on the class variable, not the instance one.
263 for (var i=0; i < preset.length; i++) {
254 for (var i=0; i < preset.length; i++) {
264 var key = preset[i];
255 var key = preset[i];
265 var callback = callbacks[key];
256 var callback = callbacks[key];
266 if (!callback) continue;
257 if (!callback) continue;
267
258
268 if (typeof callback === 'object') {
259 if (typeof callback === 'object') {
269 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
260 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
270 callback = callback.callback;
261 callback = callback.callback;
271 }
262 }
272
263
273 var local_div = $('<div/>').addClass('button_container');
264 var local_div = $('<div/>').addClass('button_container');
274 try {
265 try {
275 callback(local_div, this.cell, this);
266 callback(local_div, this.cell, this);
276 this.ui_controls_list.push(key);
267 this.ui_controls_list.push(key);
277 } catch (e) {
268 } catch (e) {
278 console.log("Error in cell toolbar callback " + key, e);
269 console.log("Error in cell toolbar callback " + key, e);
279 continue;
270 continue;
280 }
271 }
281 // only append if callback succeeded.
272 // only append if callback succeeded.
282 this.inner_element.append(local_div);
273 this.inner_element.append(local_div);
283 }
274 }
284
275
285 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
276 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
286 if (!this.ui_controls_list.length || (this.cell instanceof IPython.TextCell && this.cell.rendered)) {
277 if (!this.ui_controls_list.length || (this.cell instanceof TextCell && this.cell.rendered)) {
287 this.hide();
278 this.hide();
288 } else {
279 } else {
289 this.show();
280 this.show();
290 }
281 }
291 };
282 };
292
283
293
284
294 /**
285 /**
295 */
286 */
296 CellToolbar.utils = {};
287 CellToolbar.utils = {};
297
288
298
289
299 /**
290 /**
300 * A utility function to generate bindings between a checkbox and cell/metadata
291 * A utility function to generate bindings between a checkbox and cell/metadata
301 * @method utils.checkbox_ui_generator
292 * @method utils.checkbox_ui_generator
302 * @static
293 * @static
303 *
294 *
304 * @param name {string} Label in front of the checkbox
295 * @param name {string} Label in front of the checkbox
305 * @param setter {function( cell, newValue )}
296 * @param setter {function( cell, newValue )}
306 * A setter method to set the newValue
297 * A setter method to set the newValue
307 * @param getter {function( cell )}
298 * @param getter {function( cell )}
308 * A getter methods which return the current value.
299 * A getter methods which return the current value.
309 *
300 *
310 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
301 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
311 *
302 *
312 * @example
303 * @example
313 *
304 *
314 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
305 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
315 *
306 *
316 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
307 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
317 * // setter
308 * // setter
318 * function(cell, value){
309 * function(cell, value){
319 * // we check that the slideshow namespace exist and create it if needed
310 * // we check that the slideshow namespace exist and create it if needed
320 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
311 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
321 * // set the value
312 * // set the value
322 * cell.metadata.slideshow.isSectionStart = value
313 * cell.metadata.slideshow.isSectionStart = value
323 * },
314 * },
324 * //geter
315 * //geter
325 * function(cell){ var ns = cell.metadata.slideshow;
316 * function(cell){ var ns = cell.metadata.slideshow;
326 * // if the slideshow namespace does not exist return `undefined`
317 * // if the slideshow namespace does not exist return `undefined`
327 * // (will be interpreted as `false` by checkbox) otherwise
318 * // (will be interpreted as `false` by checkbox) otherwise
328 * // return the value
319 * // return the value
329 * return (ns == undefined)? undefined: ns.isSectionStart
320 * return (ns == undefined)? undefined: ns.isSectionStart
330 * }
321 * }
331 * );
322 * );
332 *
323 *
333 * CellToolbar.register_callback('newSlide', newSlide);
324 * CellToolbar.register_callback('newSlide', newSlide);
334 *
325 *
335 */
326 */
336 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
327 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
337 return function(div, cell, celltoolbar) {
328 return function(div, cell, celltoolbar) {
338 var button_container = $(div);
329 var button_container = $(div);
339
330
340 var chkb = $('<input/>').attr('type', 'checkbox');
331 var chkb = $('<input/>').attr('type', 'checkbox');
341 var lbl = $('<label/>').append($('<span/>').text(name));
332 var lbl = $('<label/>').append($('<span/>').text(name));
342 lbl.append(chkb);
333 lbl.append(chkb);
343 chkb.attr("checked", getter(cell));
334 chkb.attr("checked", getter(cell));
344
335
345 chkb.click(function(){
336 chkb.click(function(){
346 var v = getter(cell);
337 var v = getter(cell);
347 setter(cell, !v);
338 setter(cell, !v);
348 chkb.attr("checked", !v);
339 chkb.attr("checked", !v);
349 });
340 });
350 button_container.append($('<div/>').append(lbl));
341 button_container.append($('<div/>').append(lbl));
351 };
342 };
352 };
343 };
353
344
354
345
355 /**
346 /**
356 * A utility function to generate bindings between a dropdown list cell
347 * A utility function to generate bindings between a dropdown list cell
357 * @method utils.select_ui_generator
348 * @method utils.select_ui_generator
358 * @static
349 * @static
359 *
350 *
360 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
351 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
361 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
352 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
362 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
353 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
363 * should not be "undefined" or behavior can be unexpected.
354 * should not be "undefined" or behavior can be unexpected.
364 * @param setter {function( cell, newValue )}
355 * @param setter {function( cell, newValue )}
365 * A setter method to set the newValue
356 * A setter method to set the newValue
366 * @param getter {function( cell )}
357 * @param getter {function( cell )}
367 * A getter methods which return the current value of the metadata.
358 * A getter methods which return the current value of the metadata.
368 * @param [label=""] {String} optionnal label for the dropdown menu
359 * @param [label=""] {String} optionnal label for the dropdown menu
369 *
360 *
370 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
361 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
371 *
362 *
372 * @example
363 * @example
373 *
364 *
374 * var select_type = CellToolbar.utils.select_ui_generator([
365 * var select_type = CellToolbar.utils.select_ui_generator([
375 * ["<None>" , "None" ],
366 * ["<None>" , "None" ],
376 * ["Header Slide" , "header_slide" ],
367 * ["Header Slide" , "header_slide" ],
377 * ["Slide" , "slide" ],
368 * ["Slide" , "slide" ],
378 * ["Fragment" , "fragment" ],
369 * ["Fragment" , "fragment" ],
379 * ["Skip" , "skip" ],
370 * ["Skip" , "skip" ],
380 * ],
371 * ],
381 * // setter
372 * // setter
382 * function(cell, value){
373 * function(cell, value){
383 * // we check that the slideshow namespace exist and create it if needed
374 * // we check that the slideshow namespace exist and create it if needed
384 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
375 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
385 * // set the value
376 * // set the value
386 * cell.metadata.slideshow.slide_type = value
377 * cell.metadata.slideshow.slide_type = value
387 * },
378 * },
388 * //geter
379 * //geter
389 * function(cell){ var ns = cell.metadata.slideshow;
380 * function(cell){ var ns = cell.metadata.slideshow;
390 * // if the slideshow namespace does not exist return `undefined`
381 * // if the slideshow namespace does not exist return `undefined`
391 * // (will be interpreted as `false` by checkbox) otherwise
382 * // (will be interpreted as `false` by checkbox) otherwise
392 * // return the value
383 * // return the value
393 * return (ns == undefined)? undefined: ns.slide_type
384 * return (ns == undefined)? undefined: ns.slide_type
394 * }
385 * }
395 * CellToolbar.register_callback('slideshow.select', select_type);
386 * CellToolbar.register_callback('slideshow.select', select_type);
396 *
387 *
397 */
388 */
398 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
389 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
399 label = label || "";
390 label = label || "";
400 return function(div, cell, celltoolbar) {
391 return function(div, cell, celltoolbar) {
401 var button_container = $(div);
392 var button_container = $(div);
402 var lbl = $("<label/>").append($('<span/>').text(label));
393 var lbl = $("<label/>").append($('<span/>').text(label));
403 var select = $('<select/>').addClass('ui-widget ui-widget-content');
394 var select = $('<select/>').addClass('ui-widget ui-widget-content');
404 for(var i=0; i < list_list.length; i++){
395 for(var i=0; i < list_list.length; i++){
405 var opt = $('<option/>')
396 var opt = $('<option/>')
406 .attr('value', list_list[i][1])
397 .attr('value', list_list[i][1])
407 .text(list_list[i][0]);
398 .text(list_list[i][0]);
408 select.append(opt);
399 select.append(opt);
409 }
400 }
410 select.val(getter(cell));
401 select.val(getter(cell));
411 select.change(function(){
402 select.change(function(){
412 setter(cell, select.val());
403 setter(cell, select.val());
413 });
404 });
414 button_container.append($('<div/>').append(lbl).append(select));
405 button_container.append($('<div/>').append(lbl).append(select));
415 };
406 };
416 };
407 };
417
408
418
409 // Backwards compatability.
419 IPython.CellToolbar = CellToolbar;
410 IPython.CellToolbar = CellToolbar;
420
411
421 return IPython;
412 return CellToolbar;
422 }(IPython));
413 });
@@ -1,517 +1,523 b''
1
2 "notebook/js/tooltip",
3 Tooltip,
4 tooltip = new Tooltip();
5 IPython.tooltip = tooltip;
6
1 //----------------------------------------------------------------------------
7 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
8 // Copyright (C) 2008-2011 The IPython Development Team
3 //
9 //
4 // Distributed under the terms of the BSD License. The full license is in
10 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
11 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
12 //----------------------------------------------------------------------------
7
13
8 //============================================================================
14 //============================================================================
9 // CodeCell
15 // CodeCell
10 //============================================================================
16 //============================================================================
11 /**
17 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
18 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
19 * @module IPython
14 * @namespace IPython
20 * @namespace IPython
15 * @submodule CodeCell
21 * @submodule CodeCell
16 */
22 */
17
23
18
24
19 /* local util for codemirror */
25 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
26 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21
27
22 /**
28 /**
23 *
29 *
24 * function to delete until previous non blanking space character
30 * function to delete until previous non blanking space character
25 * or first multiple of 4 tabstop.
31 * or first multiple of 4 tabstop.
26 * @private
32 * @private
27 */
33 */
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
34 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
35 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
36 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
37 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 var tabsize = cm.getOption('tabSize');
38 var tabsize = cm.getOption('tabSize');
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
39 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
40 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 var select = cm.getRange(from,cur);
41 var select = cm.getRange(from,cur);
36 if( select.match(/^\ +$/) !== null){
42 if( select.match(/^\ +$/) !== null){
37 cm.replaceRange("",from,cur);
43 cm.replaceRange("",from,cur);
38 } else {
44 } else {
39 cm.deleteH(-1,"char");
45 cm.deleteH(-1,"char");
40 }
46 }
41 };
47 };
42
48
43
49
44 var IPython = (function (IPython) {
50 var IPython = (function (IPython) {
45 "use strict";
51 "use strict";
46
52
47 var utils = IPython.utils;
53 var utils = IPython.utils;
48 var keycodes = IPython.keyboard.keycodes;
54 var keycodes = IPython.keyboard.keycodes;
49
55
50 /**
56 /**
51 * A Cell conceived to write code.
57 * A Cell conceived to write code.
52 *
58 *
53 * The kernel doesn't have to be set at creation time, in that case
59 * The kernel doesn't have to be set at creation time, in that case
54 * it will be null and set_kernel has to be called later.
60 * it will be null and set_kernel has to be called later.
55 * @class CodeCell
61 * @class CodeCell
56 * @extends IPython.Cell
62 * @extends IPython.Cell
57 *
63 *
58 * @constructor
64 * @constructor
59 * @param {Object|null} kernel
65 * @param {Object|null} kernel
60 * @param {object|undefined} [options]
66 * @param {object|undefined} [options]
61 * @param [options.cm_config] {object} config to pass to CodeMirror
67 * @param [options.cm_config] {object} config to pass to CodeMirror
62 */
68 */
63 var CodeCell = function (kernel, options) {
69 var CodeCell = function (kernel, options) {
64 this.kernel = kernel || null;
70 this.kernel = kernel || null;
65 this.collapsed = false;
71 this.collapsed = false;
66
72
67 // create all attributed in constructor function
73 // create all attributed in constructor function
68 // even if null for V8 VM optimisation
74 // even if null for V8 VM optimisation
69 this.input_prompt_number = null;
75 this.input_prompt_number = null;
70 this.celltoolbar = null;
76 this.celltoolbar = null;
71 this.output_area = null;
77 this.output_area = null;
72 this.last_msg_id = null;
78 this.last_msg_id = null;
73 this.completer = null;
79 this.completer = null;
74
80
75
81
76 var cm_overwrite_options = {
82 var cm_overwrite_options = {
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
83 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 };
84 };
79
85
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
86 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81
87
82 IPython.Cell.apply(this,[options]);
88 IPython.Cell.apply(this,[options]);
83
89
84 // Attributes we want to override in this subclass.
90 // Attributes we want to override in this subclass.
85 this.cell_type = "code";
91 this.cell_type = "code";
86
92
87 var that = this;
93 var that = this;
88 this.element.focusout(
94 this.element.focusout(
89 function() { that.auto_highlight(); }
95 function() { that.auto_highlight(); }
90 );
96 );
91 };
97 };
92
98
93 CodeCell.options_default = {
99 CodeCell.options_default = {
94 cm_config : {
100 cm_config : {
95 extraKeys: {
101 extraKeys: {
96 "Tab" : "indentMore",
102 "Tab" : "indentMore",
97 "Shift-Tab" : "indentLess",
103 "Shift-Tab" : "indentLess",
98 "Backspace" : "delSpaceToPrevTabStop",
104 "Backspace" : "delSpaceToPrevTabStop",
99 "Cmd-/" : "toggleComment",
105 "Cmd-/" : "toggleComment",
100 "Ctrl-/" : "toggleComment"
106 "Ctrl-/" : "toggleComment"
101 },
107 },
102 mode: 'ipython',
108 mode: 'ipython',
103 theme: 'ipython',
109 theme: 'ipython',
104 matchBrackets: true,
110 matchBrackets: true,
105 // don't auto-close strings because of CodeMirror #2385
111 // don't auto-close strings because of CodeMirror #2385
106 autoCloseBrackets: "()[]{}"
112 autoCloseBrackets: "()[]{}"
107 }
113 }
108 };
114 };
109
115
110 CodeCell.msg_cells = {};
116 CodeCell.msg_cells = {};
111
117
112 CodeCell.prototype = new IPython.Cell();
118 CodeCell.prototype = new IPython.Cell();
113
119
114 /**
120 /**
115 * @method auto_highlight
121 * @method auto_highlight
116 */
122 */
117 CodeCell.prototype.auto_highlight = function () {
123 CodeCell.prototype.auto_highlight = function () {
118 this._auto_highlight(IPython.config.cell_magic_highlight);
124 this._auto_highlight(IPython.config.cell_magic_highlight);
119 };
125 };
120
126
121 /** @method create_element */
127 /** @method create_element */
122 CodeCell.prototype.create_element = function () {
128 CodeCell.prototype.create_element = function () {
123 IPython.Cell.prototype.create_element.apply(this, arguments);
129 IPython.Cell.prototype.create_element.apply(this, arguments);
124
130
125 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
131 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
126 cell.attr('tabindex','2');
132 cell.attr('tabindex','2');
127
133
128 var input = $('<div></div>').addClass('input');
134 var input = $('<div></div>').addClass('input');
129 var prompt = $('<div/>').addClass('prompt input_prompt');
135 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var inner_cell = $('<div/>').addClass('inner_cell');
136 var inner_cell = $('<div/>').addClass('inner_cell');
131 this.celltoolbar = new IPython.CellToolbar(this);
137 this.celltoolbar = new IPython.CellToolbar(this);
132 inner_cell.append(this.celltoolbar.element);
138 inner_cell.append(this.celltoolbar.element);
133 var input_area = $('<div/>').addClass('input_area');
139 var input_area = $('<div/>').addClass('input_area');
134 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
140 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
135 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
141 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
136 inner_cell.append(input_area);
142 inner_cell.append(input_area);
137 input.append(prompt).append(inner_cell);
143 input.append(prompt).append(inner_cell);
138
144
139 var widget_area = $('<div/>')
145 var widget_area = $('<div/>')
140 .addClass('widget-area')
146 .addClass('widget-area')
141 .hide();
147 .hide();
142 this.widget_area = widget_area;
148 this.widget_area = widget_area;
143 var widget_prompt = $('<div/>')
149 var widget_prompt = $('<div/>')
144 .addClass('prompt')
150 .addClass('prompt')
145 .appendTo(widget_area);
151 .appendTo(widget_area);
146 var widget_subarea = $('<div/>')
152 var widget_subarea = $('<div/>')
147 .addClass('widget-subarea')
153 .addClass('widget-subarea')
148 .appendTo(widget_area);
154 .appendTo(widget_area);
149 this.widget_subarea = widget_subarea;
155 this.widget_subarea = widget_subarea;
150 var widget_clear_buton = $('<button />')
156 var widget_clear_buton = $('<button />')
151 .addClass('close')
157 .addClass('close')
152 .html('&times;')
158 .html('&times;')
153 .click(function() {
159 .click(function() {
154 widget_area.slideUp('', function(){ widget_subarea.html(''); });
160 widget_area.slideUp('', function(){ widget_subarea.html(''); });
155 })
161 })
156 .appendTo(widget_prompt);
162 .appendTo(widget_prompt);
157
163
158 var output = $('<div></div>');
164 var output = $('<div></div>');
159 cell.append(input).append(widget_area).append(output);
165 cell.append(input).append(widget_area).append(output);
160 this.element = cell;
166 this.element = cell;
161 this.output_area = new IPython.OutputArea(output, true);
167 this.output_area = new IPython.OutputArea(output, true);
162 this.completer = new IPython.Completer(this);
168 this.completer = new IPython.Completer(this);
163 };
169 };
164
170
165 /** @method bind_events */
171 /** @method bind_events */
166 CodeCell.prototype.bind_events = function () {
172 CodeCell.prototype.bind_events = function () {
167 IPython.Cell.prototype.bind_events.apply(this);
173 IPython.Cell.prototype.bind_events.apply(this);
168 var that = this;
174 var that = this;
169
175
170 this.element.focusout(
176 this.element.focusout(
171 function() { that.auto_highlight(); }
177 function() { that.auto_highlight(); }
172 );
178 );
173 };
179 };
174
180
175
181
176 /**
182 /**
177 * This method gets called in CodeMirror's onKeyDown/onKeyPress
183 * This method gets called in CodeMirror's onKeyDown/onKeyPress
178 * handlers and is used to provide custom key handling. Its return
184 * handlers and is used to provide custom key handling. Its return
179 * value is used to determine if CodeMirror should ignore the event:
185 * value is used to determine if CodeMirror should ignore the event:
180 * true = ignore, false = don't ignore.
186 * true = ignore, false = don't ignore.
181 * @method handle_codemirror_keyevent
187 * @method handle_codemirror_keyevent
182 */
188 */
183 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
189 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
184
190
185 var that = this;
191 var that = this;
186 // whatever key is pressed, first, cancel the tooltip request before
192 // whatever key is pressed, first, cancel the tooltip request before
187 // they are sent, and remove tooltip if any, except for tab again
193 // they are sent, and remove tooltip if any, except for tab again
188 var tooltip_closed = null;
194 var tooltip_closed = null;
189 if (event.type === 'keydown' && event.which != keycodes.tab ) {
195 if (event.type === 'keydown' && event.which != keycodes.tab ) {
190 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
196 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
191 }
197 }
192
198
193 var cur = editor.getCursor();
199 var cur = editor.getCursor();
194 if (event.keyCode === keycodes.enter){
200 if (event.keyCode === keycodes.enter){
195 this.auto_highlight();
201 this.auto_highlight();
196 }
202 }
197
203
198 if (event.which === keycodes.down && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
204 if (event.which === keycodes.down && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
199 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
205 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
200 // browser and keyboard layout !
206 // browser and keyboard layout !
201 // Pressing '(' , request tooltip, don't forget to reappend it
207 // Pressing '(' , request tooltip, don't forget to reappend it
202 // The second argument says to hide the tooltip if the docstring
208 // The second argument says to hide the tooltip if the docstring
203 // is actually empty
209 // is actually empty
204 IPython.tooltip.pending(that, true);
210 IPython.tooltip.pending(that, true);
205 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
211 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
206 // If tooltip is active, cancel it. The call to
212 // If tooltip is active, cancel it. The call to
207 // remove_and_cancel_tooltip above doesn't pass, force=true.
213 // remove_and_cancel_tooltip above doesn't pass, force=true.
208 // Because of this it won't actually close the tooltip
214 // Because of this it won't actually close the tooltip
209 // if it is in sticky mode. Thus, we have to check again if it is open
215 // if it is in sticky mode. Thus, we have to check again if it is open
210 // and close it with force=true.
216 // and close it with force=true.
211 if (!IPython.tooltip._hidden) {
217 if (!IPython.tooltip._hidden) {
212 IPython.tooltip.remove_and_cancel_tooltip(true);
218 IPython.tooltip.remove_and_cancel_tooltip(true);
213 }
219 }
214 // If we closed the tooltip, don't let CM or the global handlers
220 // If we closed the tooltip, don't let CM or the global handlers
215 // handle this event.
221 // handle this event.
216 event.stop();
222 event.stop();
217 return true;
223 return true;
218 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
224 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
219 if (editor.somethingSelected()){
225 if (editor.somethingSelected()){
220 var anchor = editor.getCursor("anchor");
226 var anchor = editor.getCursor("anchor");
221 var head = editor.getCursor("head");
227 var head = editor.getCursor("head");
222 if( anchor.line != head.line){
228 if( anchor.line != head.line){
223 return false;
229 return false;
224 }
230 }
225 }
231 }
226 IPython.tooltip.request(that);
232 IPython.tooltip.request(that);
227 event.stop();
233 event.stop();
228 return true;
234 return true;
229 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
235 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
230 // Tab completion.
236 // Tab completion.
231 IPython.tooltip.remove_and_cancel_tooltip();
237 IPython.tooltip.remove_and_cancel_tooltip();
232 if (editor.somethingSelected()) {
238 if (editor.somethingSelected()) {
233 return false;
239 return false;
234 }
240 }
235 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
241 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
236 if (pre_cursor.trim() === "") {
242 if (pre_cursor.trim() === "") {
237 // Don't autocomplete if the part of the line before the cursor
243 // Don't autocomplete if the part of the line before the cursor
238 // is empty. In this case, let CodeMirror handle indentation.
244 // is empty. In this case, let CodeMirror handle indentation.
239 return false;
245 return false;
240 } else {
246 } else {
241 event.stop();
247 event.stop();
242 this.completer.startCompletion();
248 this.completer.startCompletion();
243 return true;
249 return true;
244 }
250 }
245 }
251 }
246
252
247 // keyboard event wasn't one of those unique to code cells, let's see
253 // keyboard event wasn't one of those unique to code cells, let's see
248 // if it's one of the generic ones (i.e. check edit mode shortcuts)
254 // if it's one of the generic ones (i.e. check edit mode shortcuts)
249 return IPython.Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
255 return IPython.Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
250 };
256 };
251
257
252 // Kernel related calls.
258 // Kernel related calls.
253
259
254 CodeCell.prototype.set_kernel = function (kernel) {
260 CodeCell.prototype.set_kernel = function (kernel) {
255 this.kernel = kernel;
261 this.kernel = kernel;
256 };
262 };
257
263
258 /**
264 /**
259 * Execute current code cell to the kernel
265 * Execute current code cell to the kernel
260 * @method execute
266 * @method execute
261 */
267 */
262 CodeCell.prototype.execute = function () {
268 CodeCell.prototype.execute = function () {
263 this.output_area.clear_output();
269 this.output_area.clear_output();
264
270
265 // Clear widget area
271 // Clear widget area
266 this.widget_subarea.html('');
272 this.widget_subarea.html('');
267 this.widget_subarea.height('');
273 this.widget_subarea.height('');
268 this.widget_area.height('');
274 this.widget_area.height('');
269 this.widget_area.hide();
275 this.widget_area.hide();
270
276
271 this.set_input_prompt('*');
277 this.set_input_prompt('*');
272 this.element.addClass("running");
278 this.element.addClass("running");
273 if (this.last_msg_id) {
279 if (this.last_msg_id) {
274 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
280 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
275 }
281 }
276 var callbacks = this.get_callbacks();
282 var callbacks = this.get_callbacks();
277
283
278 var old_msg_id = this.last_msg_id;
284 var old_msg_id = this.last_msg_id;
279 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
285 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
280 if (old_msg_id) {
286 if (old_msg_id) {
281 delete CodeCell.msg_cells[old_msg_id];
287 delete CodeCell.msg_cells[old_msg_id];
282 }
288 }
283 CodeCell.msg_cells[this.last_msg_id] = this;
289 CodeCell.msg_cells[this.last_msg_id] = this;
284 };
290 };
285
291
286 /**
292 /**
287 * Construct the default callbacks for
293 * Construct the default callbacks for
288 * @method get_callbacks
294 * @method get_callbacks
289 */
295 */
290 CodeCell.prototype.get_callbacks = function () {
296 CodeCell.prototype.get_callbacks = function () {
291 return {
297 return {
292 shell : {
298 shell : {
293 reply : $.proxy(this._handle_execute_reply, this),
299 reply : $.proxy(this._handle_execute_reply, this),
294 payload : {
300 payload : {
295 set_next_input : $.proxy(this._handle_set_next_input, this),
301 set_next_input : $.proxy(this._handle_set_next_input, this),
296 page : $.proxy(this._open_with_pager, this)
302 page : $.proxy(this._open_with_pager, this)
297 }
303 }
298 },
304 },
299 iopub : {
305 iopub : {
300 output : $.proxy(this.output_area.handle_output, this.output_area),
306 output : $.proxy(this.output_area.handle_output, this.output_area),
301 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
307 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
302 },
308 },
303 input : $.proxy(this._handle_input_request, this)
309 input : $.proxy(this._handle_input_request, this)
304 };
310 };
305 };
311 };
306
312
307 CodeCell.prototype._open_with_pager = function (payload) {
313 CodeCell.prototype._open_with_pager = function (payload) {
308 $([IPython.events]).trigger('open_with_text.Pager', payload);
314 $([IPython.events]).trigger('open_with_text.Pager', payload);
309 };
315 };
310
316
311 /**
317 /**
312 * @method _handle_execute_reply
318 * @method _handle_execute_reply
313 * @private
319 * @private
314 */
320 */
315 CodeCell.prototype._handle_execute_reply = function (msg) {
321 CodeCell.prototype._handle_execute_reply = function (msg) {
316 this.set_input_prompt(msg.content.execution_count);
322 this.set_input_prompt(msg.content.execution_count);
317 this.element.removeClass("running");
323 this.element.removeClass("running");
318 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
324 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
319 };
325 };
320
326
321 /**
327 /**
322 * @method _handle_set_next_input
328 * @method _handle_set_next_input
323 * @private
329 * @private
324 */
330 */
325 CodeCell.prototype._handle_set_next_input = function (payload) {
331 CodeCell.prototype._handle_set_next_input = function (payload) {
326 var data = {'cell': this, 'text': payload.text};
332 var data = {'cell': this, 'text': payload.text};
327 $([IPython.events]).trigger('set_next_input.Notebook', data);
333 $([IPython.events]).trigger('set_next_input.Notebook', data);
328 };
334 };
329
335
330 /**
336 /**
331 * @method _handle_input_request
337 * @method _handle_input_request
332 * @private
338 * @private
333 */
339 */
334 CodeCell.prototype._handle_input_request = function (msg) {
340 CodeCell.prototype._handle_input_request = function (msg) {
335 this.output_area.append_raw_input(msg);
341 this.output_area.append_raw_input(msg);
336 };
342 };
337
343
338
344
339 // Basic cell manipulation.
345 // Basic cell manipulation.
340
346
341 CodeCell.prototype.select = function () {
347 CodeCell.prototype.select = function () {
342 var cont = IPython.Cell.prototype.select.apply(this);
348 var cont = IPython.Cell.prototype.select.apply(this);
343 if (cont) {
349 if (cont) {
344 this.code_mirror.refresh();
350 this.code_mirror.refresh();
345 this.auto_highlight();
351 this.auto_highlight();
346 }
352 }
347 return cont;
353 return cont;
348 };
354 };
349
355
350 CodeCell.prototype.render = function () {
356 CodeCell.prototype.render = function () {
351 var cont = IPython.Cell.prototype.render.apply(this);
357 var cont = IPython.Cell.prototype.render.apply(this);
352 // Always execute, even if we are already in the rendered state
358 // Always execute, even if we are already in the rendered state
353 return cont;
359 return cont;
354 };
360 };
355
361
356 CodeCell.prototype.unrender = function () {
362 CodeCell.prototype.unrender = function () {
357 // CodeCell is always rendered
363 // CodeCell is always rendered
358 return false;
364 return false;
359 };
365 };
360
366
361 CodeCell.prototype.select_all = function () {
367 CodeCell.prototype.select_all = function () {
362 var start = {line: 0, ch: 0};
368 var start = {line: 0, ch: 0};
363 var nlines = this.code_mirror.lineCount();
369 var nlines = this.code_mirror.lineCount();
364 var last_line = this.code_mirror.getLine(nlines-1);
370 var last_line = this.code_mirror.getLine(nlines-1);
365 var end = {line: nlines-1, ch: last_line.length};
371 var end = {line: nlines-1, ch: last_line.length};
366 this.code_mirror.setSelection(start, end);
372 this.code_mirror.setSelection(start, end);
367 };
373 };
368
374
369
375
370 CodeCell.prototype.collapse_output = function () {
376 CodeCell.prototype.collapse_output = function () {
371 this.collapsed = true;
377 this.collapsed = true;
372 this.output_area.collapse();
378 this.output_area.collapse();
373 };
379 };
374
380
375
381
376 CodeCell.prototype.expand_output = function () {
382 CodeCell.prototype.expand_output = function () {
377 this.collapsed = false;
383 this.collapsed = false;
378 this.output_area.expand();
384 this.output_area.expand();
379 this.output_area.unscroll_area();
385 this.output_area.unscroll_area();
380 };
386 };
381
387
382 CodeCell.prototype.scroll_output = function () {
388 CodeCell.prototype.scroll_output = function () {
383 this.output_area.expand();
389 this.output_area.expand();
384 this.output_area.scroll_if_long();
390 this.output_area.scroll_if_long();
385 };
391 };
386
392
387 CodeCell.prototype.toggle_output = function () {
393 CodeCell.prototype.toggle_output = function () {
388 this.collapsed = Boolean(1 - this.collapsed);
394 this.collapsed = Boolean(1 - this.collapsed);
389 this.output_area.toggle_output();
395 this.output_area.toggle_output();
390 };
396 };
391
397
392 CodeCell.prototype.toggle_output_scroll = function () {
398 CodeCell.prototype.toggle_output_scroll = function () {
393 this.output_area.toggle_scroll();
399 this.output_area.toggle_scroll();
394 };
400 };
395
401
396
402
397 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
403 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
398 var ns;
404 var ns;
399 if (prompt_value === undefined) {
405 if (prompt_value === undefined) {
400 ns = "&nbsp;";
406 ns = "&nbsp;";
401 } else {
407 } else {
402 ns = encodeURIComponent(prompt_value);
408 ns = encodeURIComponent(prompt_value);
403 }
409 }
404 return 'In&nbsp;[' + ns + ']:';
410 return 'In&nbsp;[' + ns + ']:';
405 };
411 };
406
412
407 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
413 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
408 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
414 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
409 for(var i=1; i < lines_number; i++) {
415 for(var i=1; i < lines_number; i++) {
410 html.push(['...:']);
416 html.push(['...:']);
411 }
417 }
412 return html.join('<br/>');
418 return html.join('<br/>');
413 };
419 };
414
420
415 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
421 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
416
422
417
423
418 CodeCell.prototype.set_input_prompt = function (number) {
424 CodeCell.prototype.set_input_prompt = function (number) {
419 var nline = 1;
425 var nline = 1;
420 if (this.code_mirror !== undefined) {
426 if (this.code_mirror !== undefined) {
421 nline = this.code_mirror.lineCount();
427 nline = this.code_mirror.lineCount();
422 }
428 }
423 this.input_prompt_number = number;
429 this.input_prompt_number = number;
424 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
430 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
425 // This HTML call is okay because the user contents are escaped.
431 // This HTML call is okay because the user contents are escaped.
426 this.element.find('div.input_prompt').html(prompt_html);
432 this.element.find('div.input_prompt').html(prompt_html);
427 };
433 };
428
434
429
435
430 CodeCell.prototype.clear_input = function () {
436 CodeCell.prototype.clear_input = function () {
431 this.code_mirror.setValue('');
437 this.code_mirror.setValue('');
432 };
438 };
433
439
434
440
435 CodeCell.prototype.get_text = function () {
441 CodeCell.prototype.get_text = function () {
436 return this.code_mirror.getValue();
442 return this.code_mirror.getValue();
437 };
443 };
438
444
439
445
440 CodeCell.prototype.set_text = function (code) {
446 CodeCell.prototype.set_text = function (code) {
441 return this.code_mirror.setValue(code);
447 return this.code_mirror.setValue(code);
442 };
448 };
443
449
444
450
445 CodeCell.prototype.clear_output = function (wait) {
451 CodeCell.prototype.clear_output = function (wait) {
446 this.output_area.clear_output(wait);
452 this.output_area.clear_output(wait);
447 this.set_input_prompt();
453 this.set_input_prompt();
448 };
454 };
449
455
450
456
451 // JSON serialization
457 // JSON serialization
452
458
453 CodeCell.prototype.fromJSON = function (data) {
459 CodeCell.prototype.fromJSON = function (data) {
454 IPython.Cell.prototype.fromJSON.apply(this, arguments);
460 IPython.Cell.prototype.fromJSON.apply(this, arguments);
455 if (data.cell_type === 'code') {
461 if (data.cell_type === 'code') {
456 if (data.input !== undefined) {
462 if (data.input !== undefined) {
457 this.set_text(data.input);
463 this.set_text(data.input);
458 // make this value the starting point, so that we can only undo
464 // make this value the starting point, so that we can only undo
459 // to this state, instead of a blank cell
465 // to this state, instead of a blank cell
460 this.code_mirror.clearHistory();
466 this.code_mirror.clearHistory();
461 this.auto_highlight();
467 this.auto_highlight();
462 }
468 }
463 if (data.prompt_number !== undefined) {
469 if (data.prompt_number !== undefined) {
464 this.set_input_prompt(data.prompt_number);
470 this.set_input_prompt(data.prompt_number);
465 } else {
471 } else {
466 this.set_input_prompt();
472 this.set_input_prompt();
467 }
473 }
468 this.output_area.trusted = data.trusted || false;
474 this.output_area.trusted = data.trusted || false;
469 this.output_area.fromJSON(data.outputs);
475 this.output_area.fromJSON(data.outputs);
470 if (data.collapsed !== undefined) {
476 if (data.collapsed !== undefined) {
471 if (data.collapsed) {
477 if (data.collapsed) {
472 this.collapse_output();
478 this.collapse_output();
473 } else {
479 } else {
474 this.expand_output();
480 this.expand_output();
475 }
481 }
476 }
482 }
477 }
483 }
478 };
484 };
479
485
480
486
481 CodeCell.prototype.toJSON = function () {
487 CodeCell.prototype.toJSON = function () {
482 var data = IPython.Cell.prototype.toJSON.apply(this);
488 var data = IPython.Cell.prototype.toJSON.apply(this);
483 data.input = this.get_text();
489 data.input = this.get_text();
484 // is finite protect against undefined and '*' value
490 // is finite protect against undefined and '*' value
485 if (isFinite(this.input_prompt_number)) {
491 if (isFinite(this.input_prompt_number)) {
486 data.prompt_number = this.input_prompt_number;
492 data.prompt_number = this.input_prompt_number;
487 }
493 }
488 var outputs = this.output_area.toJSON();
494 var outputs = this.output_area.toJSON();
489 data.outputs = outputs;
495 data.outputs = outputs;
490 data.language = 'python';
496 data.language = 'python';
491 data.trusted = this.output_area.trusted;
497 data.trusted = this.output_area.trusted;
492 data.collapsed = this.collapsed;
498 data.collapsed = this.collapsed;
493 return data;
499 return data;
494 };
500 };
495
501
496 /**
502 /**
497 * handle cell level logic when a cell is unselected
503 * handle cell level logic when a cell is unselected
498 * @method unselect
504 * @method unselect
499 * @return is the action being taken
505 * @return is the action being taken
500 */
506 */
501 CodeCell.prototype.unselect = function () {
507 CodeCell.prototype.unselect = function () {
502 var cont = IPython.Cell.prototype.unselect.apply(this);
508 var cont = IPython.Cell.prototype.unselect.apply(this);
503 if (cont) {
509 if (cont) {
504 // When a code cell is usnelected, make sure that the corresponding
510 // When a code cell is usnelected, make sure that the corresponding
505 // tooltip and completer to that cell is closed.
511 // tooltip and completer to that cell is closed.
506 IPython.tooltip.remove_and_cancel_tooltip(true);
512 IPython.tooltip.remove_and_cancel_tooltip(true);
507 if (this.completer !== null) {
513 if (this.completer !== null) {
508 this.completer.close();
514 this.completer.close();
509 }
515 }
510 }
516 }
511 return cont;
517 return cont;
512 };
518 };
513
519
514 IPython.CodeCell = CodeCell;
520 IPython.CodeCell = CodeCell;
515
521
516 return IPython;
522 return IPython;
517 }(IPython));
523 }(IPython));
@@ -1,57 +1,57 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'components/jquery/jquery.min',
6 'components/jquery/jquery.min',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 var LayoutManager = function (pager) {
10 var LayoutManager = function () {
11 this.bind_events();
11 this.bind_events();
12 this.pager = pager;
13 };
12 };
14
13
15 LayoutManager.prototype.bind_events = function () {
14 LayoutManager.prototype.bind_events = function () {
16 $(window).resize($.proxy(this.do_resize,this));
15 $(window).resize($.proxy(this.do_resize,this));
17 };
16 };
18
17
19 LayoutManager.prototype.app_height = function() {
18 LayoutManager.prototype.app_height = function() {
20 var win = $(window);
19 var win = $(window);
21 var w = win.width();
20 var w = win.width();
22 var h = win.height();
21 var h = win.height();
23 var header_height;
22 var header_height;
24 if ($('div#header').css('display') === 'none') {
23 if ($('div#header').css('display') === 'none') {
25 header_height = 0;
24 header_height = 0;
26 } else {
25 } else {
27 header_height = $('div#header').outerHeight(true);
26 header_height = $('div#header').outerHeight(true);
28 }
27 }
29 var menubar_height;
28 var menubar_height;
30 if ($('div#menubar-container').css('display') === 'none') {
29 if ($('div#menubar-container').css('display') === 'none') {
31 menubar_height = 0;
30 menubar_height = 0;
32 } else {
31 } else {
33 menubar_height = $('div#menubar-container').outerHeight(true);
32 menubar_height = $('div#menubar-container').outerHeight(true);
34 }
33 }
35 return h-header_height-menubar_height; // content height
34 return h-header_height-menubar_height; // content height
36 };
35 };
37
36
38 LayoutManager.prototype.do_resize = function () {
37 LayoutManager.prototype.do_resize = function () {
39 var app_height = this.app_height(); // content height
38 var app_height = this.app_height(); // content height
40
39
41 $('#ipython-main-app').height(app_height); // content+padding+border height
40 $('#ipython-main-app').height(app_height); // content+padding+border height
42
41 if (this.pager) {
43 var pager_height = this.pager.percentage_height*app_height;
42 var pager_height = this.pager.percentage_height*app_height;
44 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
43 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
45 $('div#pager').outerHeight(pager_height);
44 $('div#pager').outerHeight(pager_height);
46 if (this.pager.expanded) {
45 if (this.pager.expanded) {
47 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
46 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
48 } else {
47 } else {
49 $('div#notebook').outerHeight(app_height-pager_splitter_height);
48 $('div#notebook').outerHeight(app_height-pager_splitter_height);
49 }
50 }
50 }
51 };
51 };
52
52
53 // Backwards compatability.
53 // Backwards compatability.
54 IPython.LayoutManager = LayoutManager;
54 IPython.LayoutManager = LayoutManager;
55
55
56 return LayoutManager;
56 return LayoutManager;
57 });
57 });
@@ -1,98 +1,101 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 var ipython = ipython || {};
4 var ipython = ipython || {};
5 require([
5 require([
6 'base/js/namespace',
6 'base/js/namespace',
7 'components/jquery/jquery.min',
7 'notebook/js/notebook',
8 'notebook/js/notebook',
8 'base/js/utils',
9 'base/js/utils',
9 'base/js/page',
10 'base/js/page',
10 'notebook/js/layoutmanager',
11 'notebook/js/layoutmanager',
11 'base/js/events',
12 'base/js/events',
12 'auth/js/loginwidget',
13 'auth/js/loginwidget',
13 'notebook/js/maintoolbar',
14 'notebook/js/maintoolbar',
14 'notebook/js/pager',
15 'notebook/js/pager',
15 'notebook/js/quickhelp',
16 'notebook/js/quickhelp',
16 'notebook/js/menubar',
17 'notebook/js/menubar',
17 'notebook/js/notificationarea',
18 'notebook/js/notificationarea',
18 ], function(
19 ], function(
19 IPython,
20 IPython,
21 $,
20 Notebook,
22 Notebook,
21 Utils,
23 Utils,
22 Page,
24 Page,
23 LayoutManager,
25 LayoutManager,
24 Events,
26 Events,
25 LoginWidget,
27 LoginWidget,
26 MainToolBar,
28 MainToolBar,
27 Pager,
29 Pager,
28 QuickHelp,
30 QuickHelp,
29 MenuBar,
31 MenuBar,
30 NotificationArea
32 NotificationArea
31 ) {
33 ) {
32 "use strict";
34 "use strict";
33
35
34 $('#ipython-main-app').addClass('border-box-sizing');
36 $('#ipython-main-app').addClass('border-box-sizing');
35 $('div#notebook_panel').addClass('border-box-sizing');
37 $('div#notebook_panel').addClass('border-box-sizing');
36
38
37 var opts = {
39 var opts = {
38 base_url : Utils.get_body_data("baseUrl"),
40 base_url : Utils.get_body_data("baseUrl"),
39 notebook_path : Utils.get_body_data("notebookPath"),
41 notebook_path : Utils.get_body_data("notebookPath"),
40 notebook_name : Utils.get_body_data('notebookName')
42 notebook_name : Utils.get_body_data('notebookName')
41 };
43 };
42
44
43 page = new Page();
45 page = new Page();
44 pager = new Pager('div#pager', 'div#pager_splitter');
46 layout_manager = new LayoutManager();
45 layout_manager = new LayoutManager(pager);
47 pager = new Pager('div#pager', 'div#pager_splitter', layout_manager);
46 events = new Events();
48 layout_manager.pager = pager;
49 events = $([new Events()]);
47 notebook = new Notebook('div#notebook', opts, events);
50 notebook = new Notebook('div#notebook', opts, events);
48 login_widget = new LoginWidget('span#login_widget', opts);
51 login_widget = new LoginWidget('span#login_widget', opts);
49 toolbar = new MainToolBar('#maintoolbar-container');
52 toolbar = new MainToolBar('#maintoolbar-container', notebook, events);
50 quick_help = new QuickHelp();
53 quick_help = new QuickHelp();
51 menubar = new MenuBar('#menubar', opts);
54 menubar = new MenuBar('#menubar', opts);
52
55
53 notification_area = new NotificationArea('#notification_area');
56 notification_area = new NotificationArea('#notification_area');
54 notification_area.init_notification_widgets();
57 notification_area.init_notification_widgets();
55
58
56 layout_manager.do_resize();
59 layout_manager.do_resize();
57
60
58 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
61 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
59 '<span id="test2" style="font-weight: bold;">x</span>'+
62 '<span id="test2" style="font-weight: bold;">x</span>'+
60 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
63 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
61 var nh = $('#test1').innerHeight();
64 var nh = $('#test1').innerHeight();
62 var bh = $('#test2').innerHeight();
65 var bh = $('#test2').innerHeight();
63 var ih = $('#test3').innerHeight();
66 var ih = $('#test3').innerHeight();
64 if(nh != bh || nh != ih) {
67 if(nh != bh || nh != ih) {
65 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
68 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
66 }
69 }
67 $('#fonttest').remove();
70 $('#fonttest').remove();
68
71
69 page.show();
72 page.show();
70
73
71 layout_manager.do_resize();
74 layout_manager.do_resize();
72 var first_load = function () {
75 var first_load = function () {
73 layout_manager.do_resize();
76 layout_manager.do_resize();
74 var hash = document.location.hash;
77 var hash = document.location.hash;
75 if (hash) {
78 if (hash) {
76 document.location.hash = '';
79 document.location.hash = '';
77 document.location.hash = hash;
80 document.location.hash = hash;
78 }
81 }
79 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
82 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
80 // only do this once
83 // only do this once
81 events.off('notebook_loaded.Notebook', first_load);
84 events.off('notebook_loaded.Notebook', first_load);
82 };
85 };
83
86
84 events.on('notebook_loaded.Notebook', first_load);
87 events.on('notebook_loaded.Notebook', first_load);
85 events.trigger('app_initialized.NotebookApp');
88 events.trigger('app_initialized.NotebookApp');
86 notebook.load_notebook(opts.notebook_name, opts.notebook_path);
89 notebook.load_notebook(opts.notebook_name, opts.notebook_path);
87
90
88 ipython.page = page;
91 ipython.page = page;
89 ipython.layout_manager = layout_manager;
92 ipython.layout_manager = layout_manager;
90 ipython.notebook = notebook;
93 ipython.notebook = notebook;
91 ipython.pager = pager;
94 ipython.pager = pager;
92 ipython.quick_help = quick_help;
95 ipython.quick_help = quick_help;
93 ipython.login_widget = login_widget;
96 ipython.login_widget = login_widget;
94 ipython.menubar = menubar;
97 ipython.menubar = menubar;
95 ipython.toolbar = toolbar;
98 ipython.toolbar = toolbar;
96 ipython.notification_area = notification_area;
99 ipython.notification_area = notification_area;
97 ipython.events = events;
100 ipython.events = events;
98 });
101 });
@@ -1,218 +1,220 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'components/jquery/jquery.min',
6 'components/jquery/jquery.min',
7 'notebook/js/toolbar',
7 'notebook/js/toolbar',
8 ], function(IPython, $, Toolbar) {
8 'notebook/js/celltoolbar',
9 ], function(IPython, $, Toolbar, CellToolbar) {
9 "use strict";
10 "use strict";
10
11
11 var MainToolBar = function (selector, notebook) {
12 var MainToolBar = function (selector, layout_manager, notebook, events) {
12 ToolBar.apply(this, arguments);
13 ToolBar.apply(this, arguments);
14 this.events = events;
13 this.notebook = notebook;
15 this.notebook = notebook;
14 this.construct();
16 this.construct();
15 this.add_celltype_list();
17 this.add_celltype_list();
16 this.add_celltoolbar_list();
18 this.add_celltoolbar_list();
17 this.bind_events();
19 this.bind_events();
18 };
20 };
19
21
20 MainToolBar.prototype = new ToolBar();
22 MainToolBar.prototype = new ToolBar();
21
23
22 MainToolBar.prototype.construct = function () {
24 MainToolBar.prototype.construct = function () {
23 this.add_buttons_group([
25 this.add_buttons_group([
24 {
26 {
25 id : 'save_b',
27 id : 'save_b',
26 label : 'Save and Checkpoint',
28 label : 'Save and Checkpoint',
27 icon : 'icon-save',
29 icon : 'icon-save',
28 callback : function () {
30 callback : function () {
29 this.notebook.save_checkpoint();
31 this.notebook.save_checkpoint();
30 }
32 }
31 }
33 }
32 ]);
34 ]);
33
35
34 this.add_buttons_group([
36 this.add_buttons_group([
35 {
37 {
36 id : 'insert_below_b',
38 id : 'insert_below_b',
37 label : 'Insert Cell Below',
39 label : 'Insert Cell Below',
38 icon : 'icon-plus-sign',
40 icon : 'icon-plus-sign',
39 callback : function () {
41 callback : function () {
40 this.notebook.insert_cell_below('code');
42 this.notebook.insert_cell_below('code');
41 this.notebook.select_next();
43 this.notebook.select_next();
42 this.notebook.focus_cell();
44 this.notebook.focus_cell();
43 }
45 }
44 }
46 }
45 ],'insert_above_below');
47 ],'insert_above_below');
46
48
47 this.add_buttons_group([
49 this.add_buttons_group([
48 {
50 {
49 id : 'cut_b',
51 id : 'cut_b',
50 label : 'Cut Cell',
52 label : 'Cut Cell',
51 icon : 'icon-cut',
53 icon : 'icon-cut',
52 callback : function () {
54 callback : function () {
53 this.notebook.cut_cell();
55 this.notebook.cut_cell();
54 }
56 }
55 },
57 },
56 {
58 {
57 id : 'copy_b',
59 id : 'copy_b',
58 label : 'Copy Cell',
60 label : 'Copy Cell',
59 icon : 'icon-copy',
61 icon : 'icon-copy',
60 callback : function () {
62 callback : function () {
61 this.notebook.copy_cell();
63 this.notebook.copy_cell();
62 }
64 }
63 },
65 },
64 {
66 {
65 id : 'paste_b',
67 id : 'paste_b',
66 label : 'Paste Cell Below',
68 label : 'Paste Cell Below',
67 icon : 'icon-paste',
69 icon : 'icon-paste',
68 callback : function () {
70 callback : function () {
69 this.notebook.paste_cell_below();
71 this.notebook.paste_cell_below();
70 }
72 }
71 }
73 }
72 ],'cut_copy_paste');
74 ],'cut_copy_paste');
73
75
74 this.add_buttons_group([
76 this.add_buttons_group([
75 {
77 {
76 id : 'move_up_b',
78 id : 'move_up_b',
77 label : 'Move Cell Up',
79 label : 'Move Cell Up',
78 icon : 'icon-arrow-up',
80 icon : 'icon-arrow-up',
79 callback : function () {
81 callback : function () {
80 this.notebook.move_cell_up();
82 this.notebook.move_cell_up();
81 }
83 }
82 },
84 },
83 {
85 {
84 id : 'move_down_b',
86 id : 'move_down_b',
85 label : 'Move Cell Down',
87 label : 'Move Cell Down',
86 icon : 'icon-arrow-down',
88 icon : 'icon-arrow-down',
87 callback : function () {
89 callback : function () {
88 this.notebook.move_cell_down();
90 this.notebook.move_cell_down();
89 }
91 }
90 }
92 }
91 ],'move_up_down');
93 ],'move_up_down');
92
94
93
95
94 this.add_buttons_group([
96 this.add_buttons_group([
95 {
97 {
96 id : 'run_b',
98 id : 'run_b',
97 label : 'Run Cell',
99 label : 'Run Cell',
98 icon : 'icon-play',
100 icon : 'icon-play',
99 callback : function () {
101 callback : function () {
100 // emulate default shift-enter behavior
102 // emulate default shift-enter behavior
101 this.notebook.execute_cell_and_select_below();
103 this.notebook.execute_cell_and_select_below();
102 }
104 }
103 },
105 },
104 {
106 {
105 id : 'interrupt_b',
107 id : 'interrupt_b',
106 label : 'Interrupt',
108 label : 'Interrupt',
107 icon : 'icon-stop',
109 icon : 'icon-stop',
108 callback : function () {
110 callback : function () {
109 this.notebook.session.interrupt_kernel();
111 this.notebook.session.interrupt_kernel();
110 }
112 }
111 },
113 },
112 {
114 {
113 id : 'repeat_b',
115 id : 'repeat_b',
114 label : 'Restart Kernel',
116 label : 'Restart Kernel',
115 icon : 'icon-repeat',
117 icon : 'icon-repeat',
116 callback : function () {
118 callback : function () {
117 this.notebook.restart_kernel();
119 this.notebook.restart_kernel();
118 }
120 }
119 }
121 }
120 ],'run_int');
122 ],'run_int');
121 };
123 };
122
124
123 MainToolBar.prototype.add_celltype_list = function () {
125 MainToolBar.prototype.add_celltype_list = function () {
124 this.element
126 this.element
125 .append($('<select/>')
127 .append($('<select/>')
126 .attr('id','cell_type')
128 .attr('id','cell_type')
127 .addClass('form-control select-xs')
129 .addClass('form-control select-xs')
128 // .addClass('ui-widget-content')
130 // .addClass('ui-widget-content')
129 .append($('<option/>').attr('value','code').text('Code'))
131 .append($('<option/>').attr('value','code').text('Code'))
130 .append($('<option/>').attr('value','markdown').text('Markdown'))
132 .append($('<option/>').attr('value','markdown').text('Markdown'))
131 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
133 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
132 .append($('<option/>').attr('value','heading1').text('Heading 1'))
134 .append($('<option/>').attr('value','heading1').text('Heading 1'))
133 .append($('<option/>').attr('value','heading2').text('Heading 2'))
135 .append($('<option/>').attr('value','heading2').text('Heading 2'))
134 .append($('<option/>').attr('value','heading3').text('Heading 3'))
136 .append($('<option/>').attr('value','heading3').text('Heading 3'))
135 .append($('<option/>').attr('value','heading4').text('Heading 4'))
137 .append($('<option/>').attr('value','heading4').text('Heading 4'))
136 .append($('<option/>').attr('value','heading5').text('Heading 5'))
138 .append($('<option/>').attr('value','heading5').text('Heading 5'))
137 .append($('<option/>').attr('value','heading6').text('Heading 6'))
139 .append($('<option/>').attr('value','heading6').text('Heading 6'))
138 );
140 );
139 };
141 };
140
142
141
143
142 MainToolBar.prototype.add_celltoolbar_list = function () {
144 MainToolBar.prototype.add_celltoolbar_list = function () {
143 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
145 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
144 var select = $('<select/>')
146 var select = $('<select/>')
145 // .addClass('ui-widget-content')
147 // .addClass('ui-widget-content')
146 .attr('id', 'ctb_select')
148 .attr('id', 'ctb_select')
147 .addClass('form-control select-xs')
149 .addClass('form-control select-xs')
148 .append($('<option/>').attr('value', '').text('None'));
150 .append($('<option/>').attr('value', '').text('None'));
149 this.element.append(label).append(select);
151 this.element.append(label).append(select);
150 select.change(function() {
152 select.change(function() {
151 var val = $(this).val();
153 var val = $(this).val();
152 if (val ==='') {
154 if (val ==='') {
153 IPython.CellToolbar.global_hide();
155 CellToolbar.global_hide();
154 delete this.notebook.metadata.celltoolbar;
156 delete this.notebook.metadata.celltoolbar;
155 } else {
157 } else {
156 IPython.CellToolbar.global_show();
158 CellToolbar.global_show();
157 IPython.CellToolbar.activate_preset(val);
159 CellToolbar.activate_preset(val);
158 this.notebook.metadata.celltoolbar = val;
160 this.notebook.metadata.celltoolbar = val;
159 }
161 }
160 });
162 });
161 // Setup the currently registered presets.
163 // Setup the currently registered presets.
162 var presets = IPython.CellToolbar.list_presets();
164 var presets = CellToolbar.list_presets();
163 for (var i=0; i<presets.length; i++) {
165 for (var i=0; i<presets.length; i++) {
164 var name = presets[i];
166 var name = presets[i];
165 select.append($('<option/>').attr('value', name).text(name));
167 select.append($('<option/>').attr('value', name).text(name));
166 }
168 }
167 // Setup future preset registrations.
169 // Setup future preset registrations.
168 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
170 this.events.on('preset_added.CellToolbar', function (event, data) {
169 var name = data.name;
171 var name = data.name;
170 select.append($('<option/>').attr('value', name).text(name));
172 select.append($('<option/>').attr('value', name).text(name));
171 });
173 });
172 // Update select value when a preset is activated.
174 // Update select value when a preset is activated.
173 $([IPython.events]).on('preset_activated.CellToolbar', function (event, data) {
175 this.events.on('preset_activated.CellToolbar', function (event, data) {
174 if (select.val() !== data.name)
176 if (select.val() !== data.name)
175 select.val(data.name);
177 select.val(data.name);
176 });
178 });
177 };
179 };
178
180
179
181
180 MainToolBar.prototype.bind_events = function () {
182 MainToolBar.prototype.bind_events = function () {
181 var that = this;
183 var that = this;
182
184
183 this.element.find('#cell_type').change(function () {
185 this.element.find('#cell_type').change(function () {
184 var cell_type = $(this).val();
186 var cell_type = $(this).val();
185 if (cell_type === 'code') {
187 if (cell_type === 'code') {
186 this.notebook.to_code();
188 this.notebook.to_code();
187 } else if (cell_type === 'markdown') {
189 } else if (cell_type === 'markdown') {
188 this.notebook.to_markdown();
190 this.notebook.to_markdown();
189 } else if (cell_type === 'raw') {
191 } else if (cell_type === 'raw') {
190 this.notebook.to_raw();
192 this.notebook.to_raw();
191 } else if (cell_type === 'heading1') {
193 } else if (cell_type === 'heading1') {
192 this.notebook.to_heading(undefined, 1);
194 this.notebook.to_heading(undefined, 1);
193 } else if (cell_type === 'heading2') {
195 } else if (cell_type === 'heading2') {
194 this.notebook.to_heading(undefined, 2);
196 this.notebook.to_heading(undefined, 2);
195 } else if (cell_type === 'heading3') {
197 } else if (cell_type === 'heading3') {
196 this.notebook.to_heading(undefined, 3);
198 this.notebook.to_heading(undefined, 3);
197 } else if (cell_type === 'heading4') {
199 } else if (cell_type === 'heading4') {
198 this.notebook.to_heading(undefined, 4);
200 this.notebook.to_heading(undefined, 4);
199 } else if (cell_type === 'heading5') {
201 } else if (cell_type === 'heading5') {
200 this.notebook.to_heading(undefined, 5);
202 this.notebook.to_heading(undefined, 5);
201 } else if (cell_type === 'heading6') {
203 } else if (cell_type === 'heading6') {
202 this.notebook.to_heading(undefined, 6);
204 this.notebook.to_heading(undefined, 6);
203 }
205 }
204 });
206 });
205 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
207 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
206 if (data.cell_type === 'heading') {
208 if (data.cell_type === 'heading') {
207 that.element.find('#cell_type').val(data.cell_type+data.level);
209 that.element.find('#cell_type').val(data.cell_type+data.level);
208 } else {
210 } else {
209 that.element.find('#cell_type').val(data.cell_type);
211 that.element.find('#cell_type').val(data.cell_type);
210 }
212 }
211 });
213 });
212 };
214 };
213
215
214 // Backwards compatability.
216 // Backwards compatability.
215 IPython.MainToolBar = MainToolBar;
217 IPython.MainToolBar = MainToolBar;
216
218
217 return MainToolBar;
219 return MainToolBar;
218 });
220 });
@@ -1,2487 +1,2487 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'components/jquery/jquery.min',
6 'components/jquery/jquery.min',
7 'base/js/utils',
7 'base/js/utils',
8 'notebook/js/keyboardmanager',
8 'notebook/js/keyboardmanager',
9 'notebook/js/savewidget',
9 'notebook/js/savewidget',
10 'base/js/events',
10 'base/js/events',
11 'base/js/dialog',
11 'base/js/dialog',
12 'notebook/js/textcell',
12 'notebook/js/textcell',
13 'notebook/js/codecell',
13 'notebook/js/codecell',
14 'services/sessions/js/session',
14 'services/sessions/js/session',
15 'notebook/js/celltoolbar',
15 'notebook/js/celltoolbar',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'components/jquery-ui/ui/minified/jquery-ui.min',
17 'components/jquery-ui/ui/minified/jquery-ui.min',
18 'components/bootstrap/js/bootstrap.min',
18 'components/bootstrap/js/bootstrap.min',
19 'components/marked/lib/marked',
19 'components/marked/lib/marked',
20 'widgets/js/init',
20 'widgets/js/init',
21 'notebook/js/mathjaxutils',
21 'notebook/js/mathjaxutils',
22 ], function (
22 ], function (
23 IPython,
23 IPython,
24 $,
24 $,
25 Utils,
25 Utils,
26 KeyboardManager,
26 KeyboardManager,
27 SaveWidget,
27 SaveWidget,
28 Events,
28 Events,
29 Dialog,
29 Dialog,
30 Cells,
30 Cells,
31 CodeCell,
31 CodeCell,
32 Session,
32 Session,
33 CellToolbar,
33 CellToolbar,
34 Keyboard,
34 Keyboard,
35 marked,
35 marked,
36 MathJax
36 MathJax
37 ) {
37 ) {
38
38
39 /**
39 /**
40 * A notebook contains and manages cells.
40 * A notebook contains and manages cells.
41 *
41 *
42 * @class Notebook
42 * @class Notebook
43 * @constructor
43 * @constructor
44 * @param {String} selector A jQuery selector for the notebook's DOM element
44 * @param {String} selector A jQuery selector for the notebook's DOM element
45 * @param {Object} [options] A config object
45 * @param {Object} [options] A config object
46 * @param {Object} [events] An events object
46 * @param {Object} [events] An events object
47 */
47 */
48 var Notebook = function (selector, options, events) {
48 var Notebook = function (selector, options, events) {
49 this.events = events || new Events();
49 this.events = events || $([new Events()]);
50 this.keyboard_manager = new KeyboardManager();
50 this.keyboard_manager = new KeyboardManager();
51 this.keyboard = new Keyboard();
51 this.keyboard = new Keyboard();
52 this.save_widget = new SaveWidget('span#save_widget');
52 this.save_widget = new SaveWidget('span#save_widget');
53
53
54 this.mathjaxutils = MathJax();
54 this.mathjaxutils = MathJax();
55 this.mathjaxutils.init();
55 this.mathjaxutils.init();
56
56
57
57
58 window.marked = window.marked || marked;
58 window.marked = window.marked || marked;
59 if (marked) {
59 if (marked) {
60 marked.setOptions({
60 marked.setOptions({
61 gfm : true,
61 gfm : true,
62 tables: true,
62 tables: true,
63 langPrefix: "language-",
63 langPrefix: "language-",
64 highlight: function(code, lang) {
64 highlight: function(code, lang) {
65 if (!lang) {
65 if (!lang) {
66 // no language, no highlight
66 // no language, no highlight
67 return code;
67 return code;
68 }
68 }
69 var highlighted;
69 var highlighted;
70 try {
70 try {
71 highlighted = hljs.highlight(lang, code, false);
71 highlighted = hljs.highlight(lang, code, false);
72 } catch(err) {
72 } catch(err) {
73 highlighted = hljs.highlightAuto(code);
73 highlighted = hljs.highlightAuto(code);
74 }
74 }
75 return highlighted.value;
75 return highlighted.value;
76 }
76 }
77 });
77 });
78 }
78 }
79
79
80 // Backwards compatability.
80 // Backwards compatability.
81 IPython.keyboard_manager = this.keyboard_manager;
81 IPython.keyboard_manager = this.keyboard_manager;
82 IPython.save_widget = this.save_widget;
82 IPython.save_widget = this.save_widget;
83 IPython.keyboard = this.keyboard;
83 IPython.keyboard = this.keyboard;
84
84
85 this.options = options = options || {};
85 this.options = options = options || {};
86 this.base_url = options.base_url;
86 this.base_url = options.base_url;
87 this.notebook_path = options.notebook_path;
87 this.notebook_path = options.notebook_path;
88 this.notebook_name = options.notebook_name;
88 this.notebook_name = options.notebook_name;
89 this.element = $(selector);
89 this.element = $(selector);
90 this.element.scroll();
90 this.element.scroll();
91 this.element.data("notebook", this);
91 this.element.data("notebook", this);
92 this.next_prompt_number = 1;
92 this.next_prompt_number = 1;
93 this.session = null;
93 this.session = null;
94 this.kernel = null;
94 this.kernel = null;
95 this.clipboard = null;
95 this.clipboard = null;
96 this.undelete_backup = null;
96 this.undelete_backup = null;
97 this.undelete_index = null;
97 this.undelete_index = null;
98 this.undelete_below = false;
98 this.undelete_below = false;
99 this.paste_enabled = false;
99 this.paste_enabled = false;
100 // It is important to start out in command mode to match the intial mode
100 // It is important to start out in command mode to match the intial mode
101 // of the KeyboardManager.
101 // of the KeyboardManager.
102 this.mode = 'command';
102 this.mode = 'command';
103 this.set_dirty(false);
103 this.set_dirty(false);
104 this.metadata = {};
104 this.metadata = {};
105 this._checkpoint_after_save = false;
105 this._checkpoint_after_save = false;
106 this.last_checkpoint = null;
106 this.last_checkpoint = null;
107 this.checkpoints = [];
107 this.checkpoints = [];
108 this.autosave_interval = 0;
108 this.autosave_interval = 0;
109 this.autosave_timer = null;
109 this.autosave_timer = null;
110 // autosave *at most* every two minutes
110 // autosave *at most* every two minutes
111 this.minimum_autosave_interval = 120000;
111 this.minimum_autosave_interval = 120000;
112 // single worksheet for now
112 // single worksheet for now
113 this.worksheet_metadata = {};
113 this.worksheet_metadata = {};
114 this.notebook_name_blacklist_re = /[\/\\:]/;
114 this.notebook_name_blacklist_re = /[\/\\:]/;
115 this.nbformat = 3; // Increment this when changing the nbformat
115 this.nbformat = 3; // Increment this when changing the nbformat
116 this.nbformat_minor = 0; // Increment this when changing the nbformat
116 this.nbformat_minor = 0; // Increment this when changing the nbformat
117 this.style();
117 this.style();
118 this.create_elements();
118 this.create_elements();
119 this.bind_events();
119 this.bind_events();
120 this.save_notebook = function() { // don't allow save until notebook_loaded
120 this.save_notebook = function() { // don't allow save until notebook_loaded
121 this.save_notebook_error(null, null, "Load failed, save is disabled");
121 this.save_notebook_error(null, null, "Load failed, save is disabled");
122 };
122 };
123 };
123 };
124
124
125 /**
125 /**
126 * Tweak the notebook's CSS style.
126 * Tweak the notebook's CSS style.
127 *
127 *
128 * @method style
128 * @method style
129 */
129 */
130 Notebook.prototype.style = function () {
130 Notebook.prototype.style = function () {
131 $('div#notebook').addClass('border-box-sizing');
131 $('div#notebook').addClass('border-box-sizing');
132 };
132 };
133
133
134 /**
134 /**
135 * Create an HTML and CSS representation of the notebook.
135 * Create an HTML and CSS representation of the notebook.
136 *
136 *
137 * @method create_elements
137 * @method create_elements
138 */
138 */
139 Notebook.prototype.create_elements = function () {
139 Notebook.prototype.create_elements = function () {
140 var that = this;
140 var that = this;
141 this.element.attr('tabindex','-1');
141 this.element.attr('tabindex','-1');
142 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
142 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
143 // We add this end_space div to the end of the notebook div to:
143 // We add this end_space div to the end of the notebook div to:
144 // i) provide a margin between the last cell and the end of the notebook
144 // i) provide a margin between the last cell and the end of the notebook
145 // ii) to prevent the div from scrolling up when the last cell is being
145 // ii) to prevent the div from scrolling up when the last cell is being
146 // edited, but is too low on the page, which browsers will do automatically.
146 // edited, but is too low on the page, which browsers will do automatically.
147 var end_space = $('<div/>').addClass('end_space');
147 var end_space = $('<div/>').addClass('end_space');
148 end_space.dblclick(function (e) {
148 end_space.dblclick(function (e) {
149 var ncells = that.ncells();
149 var ncells = that.ncells();
150 that.insert_cell_below('code',ncells-1);
150 that.insert_cell_below('code',ncells-1);
151 });
151 });
152 this.element.append(this.container);
152 this.element.append(this.container);
153 this.container.append(end_space);
153 this.container.append(end_space);
154 };
154 };
155
155
156 /**
156 /**
157 * Bind JavaScript events: key presses and custom IPython events.
157 * Bind JavaScript events: key presses and custom IPython events.
158 *
158 *
159 * @method bind_events
159 * @method bind_events
160 */
160 */
161 Notebook.prototype.bind_events = function () {
161 Notebook.prototype.bind_events = function () {
162 var that = this;
162 var that = this;
163
163
164 this.events.on('set_next_input.Notebook', function (event, data) {
164 this.events.on('set_next_input.Notebook', function (event, data) {
165 var index = that.find_cell_index(data.cell);
165 var index = that.find_cell_index(data.cell);
166 var new_cell = that.insert_cell_below('code',index);
166 var new_cell = that.insert_cell_below('code',index);
167 new_cell.set_text(data.text);
167 new_cell.set_text(data.text);
168 that.dirty = true;
168 that.dirty = true;
169 });
169 });
170
170
171 this.events.on('set_dirty.Notebook', function (event, data) {
171 this.events.on('set_dirty.Notebook', function (event, data) {
172 that.dirty = data.value;
172 that.dirty = data.value;
173 });
173 });
174
174
175 this.events.on('trust_changed.Notebook', function (event, data) {
175 this.events.on('trust_changed.Notebook', function (event, data) {
176 that.trusted = data.value;
176 that.trusted = data.value;
177 });
177 });
178
178
179 this.events.on('select.Cell', function (event, data) {
179 this.events.on('select.Cell', function (event, data) {
180 var index = that.find_cell_index(data.cell);
180 var index = that.find_cell_index(data.cell);
181 that.select(index);
181 that.select(index);
182 });
182 });
183
183
184 this.events.on('edit_mode.Cell', function (event, data) {
184 this.events.on('edit_mode.Cell', function (event, data) {
185 that.handle_edit_mode(data.cell);
185 that.handle_edit_mode(data.cell);
186 });
186 });
187
187
188 this.events.on('command_mode.Cell', function (event, data) {
188 this.events.on('command_mode.Cell', function (event, data) {
189 that.handle_command_mode(data.cell);
189 that.handle_command_mode(data.cell);
190 });
190 });
191
191
192 this.events.on('status_autorestarting.Kernel', function () {
192 this.events.on('status_autorestarting.Kernel', function () {
193 Dialog.modal({
193 Dialog.modal({
194 title: "Kernel Restarting",
194 title: "Kernel Restarting",
195 body: "The kernel appears to have died. It will restart automatically.",
195 body: "The kernel appears to have died. It will restart automatically.",
196 buttons: {
196 buttons: {
197 OK : {
197 OK : {
198 class : "btn-primary"
198 class : "btn-primary"
199 }
199 }
200 }
200 }
201 });
201 });
202 });
202 });
203
203
204 var collapse_time = function (time) {
204 var collapse_time = function (time) {
205 var app_height = $('#ipython-main-app').height(); // content height
205 var app_height = $('#ipython-main-app').height(); // content height
206 var splitter_height = $('div#pager_splitter').outerHeight(true);
206 var splitter_height = $('div#pager_splitter').outerHeight(true);
207 var new_height = app_height - splitter_height;
207 var new_height = app_height - splitter_height;
208 that.element.animate({height : new_height + 'px'}, time);
208 that.element.animate({height : new_height + 'px'}, time);
209 };
209 };
210
210
211 this.element.bind('collapse_pager', function (event, extrap) {
211 this.element.bind('collapse_pager', function (event, extrap) {
212 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
212 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
213 collapse_time(time);
213 collapse_time(time);
214 });
214 });
215
215
216 var expand_time = function (time) {
216 var expand_time = function (time) {
217 var app_height = $('#ipython-main-app').height(); // content height
217 var app_height = $('#ipython-main-app').height(); // content height
218 var splitter_height = $('div#pager_splitter').outerHeight(true);
218 var splitter_height = $('div#pager_splitter').outerHeight(true);
219 var pager_height = $('div#pager').outerHeight(true);
219 var pager_height = $('div#pager').outerHeight(true);
220 var new_height = app_height - pager_height - splitter_height;
220 var new_height = app_height - pager_height - splitter_height;
221 that.element.animate({height : new_height + 'px'}, time);
221 that.element.animate({height : new_height + 'px'}, time);
222 };
222 };
223
223
224 this.element.bind('expand_pager', function (event, extrap) {
224 this.element.bind('expand_pager', function (event, extrap) {
225 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
225 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
226 expand_time(time);
226 expand_time(time);
227 });
227 });
228
228
229 // Firefox 22 broke $(window).on("beforeunload")
229 // Firefox 22 broke $(window).on("beforeunload")
230 // I'm not sure why or how.
230 // I'm not sure why or how.
231 window.onbeforeunload = function (e) {
231 window.onbeforeunload = function (e) {
232 // TODO: Make killing the kernel configurable.
232 // TODO: Make killing the kernel configurable.
233 var kill_kernel = false;
233 var kill_kernel = false;
234 if (kill_kernel) {
234 if (kill_kernel) {
235 that.session.kill_kernel();
235 that.session.kill_kernel();
236 }
236 }
237 // if we are autosaving, trigger an autosave on nav-away.
237 // if we are autosaving, trigger an autosave on nav-away.
238 // still warn, because if we don't the autosave may fail.
238 // still warn, because if we don't the autosave may fail.
239 if (that.dirty) {
239 if (that.dirty) {
240 if ( that.autosave_interval ) {
240 if ( that.autosave_interval ) {
241 // schedule autosave in a timeout
241 // schedule autosave in a timeout
242 // this gives you a chance to forcefully discard changes
242 // this gives you a chance to forcefully discard changes
243 // by reloading the page if you *really* want to.
243 // by reloading the page if you *really* want to.
244 // the timer doesn't start until you *dismiss* the dialog.
244 // the timer doesn't start until you *dismiss* the dialog.
245 setTimeout(function () {
245 setTimeout(function () {
246 if (that.dirty) {
246 if (that.dirty) {
247 that.save_notebook();
247 that.save_notebook();
248 }
248 }
249 }, 1000);
249 }, 1000);
250 return "Autosave in progress, latest changes may be lost.";
250 return "Autosave in progress, latest changes may be lost.";
251 } else {
251 } else {
252 return "Unsaved changes will be lost.";
252 return "Unsaved changes will be lost.";
253 }
253 }
254 }
254 }
255 // Null is the *only* return value that will make the browser not
255 // Null is the *only* return value that will make the browser not
256 // pop up the "don't leave" dialog.
256 // pop up the "don't leave" dialog.
257 return null;
257 return null;
258 };
258 };
259 };
259 };
260
260
261 /**
261 /**
262 * Set the dirty flag, and trigger the set_dirty.Notebook event
262 * Set the dirty flag, and trigger the set_dirty.Notebook event
263 *
263 *
264 * @method set_dirty
264 * @method set_dirty
265 */
265 */
266 Notebook.prototype.set_dirty = function (value) {
266 Notebook.prototype.set_dirty = function (value) {
267 if (value === undefined) {
267 if (value === undefined) {
268 value = true;
268 value = true;
269 }
269 }
270 if (this.dirty == value) {
270 if (this.dirty == value) {
271 return;
271 return;
272 }
272 }
273 this.events.trigger('set_dirty.Notebook', {value: value});
273 this.events.trigger('set_dirty.Notebook', {value: value});
274 };
274 };
275
275
276 /**
276 /**
277 * Scroll the top of the page to a given cell.
277 * Scroll the top of the page to a given cell.
278 *
278 *
279 * @method scroll_to_cell
279 * @method scroll_to_cell
280 * @param {Number} cell_number An index of the cell to view
280 * @param {Number} cell_number An index of the cell to view
281 * @param {Number} time Animation time in milliseconds
281 * @param {Number} time Animation time in milliseconds
282 * @return {Number} Pixel offset from the top of the container
282 * @return {Number} Pixel offset from the top of the container
283 */
283 */
284 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
284 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
285 var cells = this.get_cells();
285 var cells = this.get_cells();
286 time = time || 0;
286 time = time || 0;
287 cell_number = Math.min(cells.length-1,cell_number);
287 cell_number = Math.min(cells.length-1,cell_number);
288 cell_number = Math.max(0 ,cell_number);
288 cell_number = Math.max(0 ,cell_number);
289 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
289 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
290 this.element.animate({scrollTop:scroll_value}, time);
290 this.element.animate({scrollTop:scroll_value}, time);
291 return scroll_value;
291 return scroll_value;
292 };
292 };
293
293
294 /**
294 /**
295 * Scroll to the bottom of the page.
295 * Scroll to the bottom of the page.
296 *
296 *
297 * @method scroll_to_bottom
297 * @method scroll_to_bottom
298 */
298 */
299 Notebook.prototype.scroll_to_bottom = function () {
299 Notebook.prototype.scroll_to_bottom = function () {
300 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
300 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
301 };
301 };
302
302
303 /**
303 /**
304 * Scroll to the top of the page.
304 * Scroll to the top of the page.
305 *
305 *
306 * @method scroll_to_top
306 * @method scroll_to_top
307 */
307 */
308 Notebook.prototype.scroll_to_top = function () {
308 Notebook.prototype.scroll_to_top = function () {
309 this.element.animate({scrollTop:0}, 0);
309 this.element.animate({scrollTop:0}, 0);
310 };
310 };
311
311
312 // Edit Notebook metadata
312 // Edit Notebook metadata
313
313
314 Notebook.prototype.edit_metadata = function () {
314 Notebook.prototype.edit_metadata = function () {
315 var that = this;
315 var that = this;
316 Dialog.edit_metadata(this.metadata, function (md) {
316 Dialog.edit_metadata(this.metadata, function (md) {
317 that.metadata = md;
317 that.metadata = md;
318 }, 'Notebook');
318 }, 'Notebook');
319 };
319 };
320
320
321 // Cell indexing, retrieval, etc.
321 // Cell indexing, retrieval, etc.
322
322
323 /**
323 /**
324 * Get all cell elements in the notebook.
324 * Get all cell elements in the notebook.
325 *
325 *
326 * @method get_cell_elements
326 * @method get_cell_elements
327 * @return {jQuery} A selector of all cell elements
327 * @return {jQuery} A selector of all cell elements
328 */
328 */
329 Notebook.prototype.get_cell_elements = function () {
329 Notebook.prototype.get_cell_elements = function () {
330 return this.container.children("div.cell");
330 return this.container.children("div.cell");
331 };
331 };
332
332
333 /**
333 /**
334 * Get a particular cell element.
334 * Get a particular cell element.
335 *
335 *
336 * @method get_cell_element
336 * @method get_cell_element
337 * @param {Number} index An index of a cell to select
337 * @param {Number} index An index of a cell to select
338 * @return {jQuery} A selector of the given cell.
338 * @return {jQuery} A selector of the given cell.
339 */
339 */
340 Notebook.prototype.get_cell_element = function (index) {
340 Notebook.prototype.get_cell_element = function (index) {
341 var result = null;
341 var result = null;
342 var e = this.get_cell_elements().eq(index);
342 var e = this.get_cell_elements().eq(index);
343 if (e.length !== 0) {
343 if (e.length !== 0) {
344 result = e;
344 result = e;
345 }
345 }
346 return result;
346 return result;
347 };
347 };
348
348
349 /**
349 /**
350 * Try to get a particular cell by msg_id.
350 * Try to get a particular cell by msg_id.
351 *
351 *
352 * @method get_msg_cell
352 * @method get_msg_cell
353 * @param {String} msg_id A message UUID
353 * @param {String} msg_id A message UUID
354 * @return {Cell} Cell or null if no cell was found.
354 * @return {Cell} Cell or null if no cell was found.
355 */
355 */
356 Notebook.prototype.get_msg_cell = function (msg_id) {
356 Notebook.prototype.get_msg_cell = function (msg_id) {
357 return CodeCell.msg_cells[msg_id] || null;
357 return CodeCell.msg_cells[msg_id] || null;
358 };
358 };
359
359
360 /**
360 /**
361 * Count the cells in this notebook.
361 * Count the cells in this notebook.
362 *
362 *
363 * @method ncells
363 * @method ncells
364 * @return {Number} The number of cells in this notebook
364 * @return {Number} The number of cells in this notebook
365 */
365 */
366 Notebook.prototype.ncells = function () {
366 Notebook.prototype.ncells = function () {
367 return this.get_cell_elements().length;
367 return this.get_cell_elements().length;
368 };
368 };
369
369
370 /**
370 /**
371 * Get all Cell objects in this notebook.
371 * Get all Cell objects in this notebook.
372 *
372 *
373 * @method get_cells
373 * @method get_cells
374 * @return {Array} This notebook's Cell objects
374 * @return {Array} This notebook's Cell objects
375 */
375 */
376 // TODO: we are often calling cells as cells()[i], which we should optimize
376 // TODO: we are often calling cells as cells()[i], which we should optimize
377 // to cells(i) or a new method.
377 // to cells(i) or a new method.
378 Notebook.prototype.get_cells = function () {
378 Notebook.prototype.get_cells = function () {
379 return this.get_cell_elements().toArray().map(function (e) {
379 return this.get_cell_elements().toArray().map(function (e) {
380 return $(e).data("cell");
380 return $(e).data("cell");
381 });
381 });
382 };
382 };
383
383
384 /**
384 /**
385 * Get a Cell object from this notebook.
385 * Get a Cell object from this notebook.
386 *
386 *
387 * @method get_cell
387 * @method get_cell
388 * @param {Number} index An index of a cell to retrieve
388 * @param {Number} index An index of a cell to retrieve
389 * @return {Cell} A particular cell
389 * @return {Cell} A particular cell
390 */
390 */
391 Notebook.prototype.get_cell = function (index) {
391 Notebook.prototype.get_cell = function (index) {
392 var result = null;
392 var result = null;
393 var ce = this.get_cell_element(index);
393 var ce = this.get_cell_element(index);
394 if (ce !== null) {
394 if (ce !== null) {
395 result = ce.data('cell');
395 result = ce.data('cell');
396 }
396 }
397 return result;
397 return result;
398 };
398 };
399
399
400 /**
400 /**
401 * Get the cell below a given cell.
401 * Get the cell below a given cell.
402 *
402 *
403 * @method get_next_cell
403 * @method get_next_cell
404 * @param {Cell} cell The provided cell
404 * @param {Cell} cell The provided cell
405 * @return {Cell} The next cell
405 * @return {Cell} The next cell
406 */
406 */
407 Notebook.prototype.get_next_cell = function (cell) {
407 Notebook.prototype.get_next_cell = function (cell) {
408 var result = null;
408 var result = null;
409 var index = this.find_cell_index(cell);
409 var index = this.find_cell_index(cell);
410 if (this.is_valid_cell_index(index+1)) {
410 if (this.is_valid_cell_index(index+1)) {
411 result = this.get_cell(index+1);
411 result = this.get_cell(index+1);
412 }
412 }
413 return result;
413 return result;
414 };
414 };
415
415
416 /**
416 /**
417 * Get the cell above a given cell.
417 * Get the cell above a given cell.
418 *
418 *
419 * @method get_prev_cell
419 * @method get_prev_cell
420 * @param {Cell} cell The provided cell
420 * @param {Cell} cell The provided cell
421 * @return {Cell} The previous cell
421 * @return {Cell} The previous cell
422 */
422 */
423 Notebook.prototype.get_prev_cell = function (cell) {
423 Notebook.prototype.get_prev_cell = function (cell) {
424 // TODO: off-by-one
424 // TODO: off-by-one
425 // nb.get_prev_cell(nb.get_cell(1)) is null
425 // nb.get_prev_cell(nb.get_cell(1)) is null
426 var result = null;
426 var result = null;
427 var index = this.find_cell_index(cell);
427 var index = this.find_cell_index(cell);
428 if (index !== null && index > 1) {
428 if (index !== null && index > 1) {
429 result = this.get_cell(index-1);
429 result = this.get_cell(index-1);
430 }
430 }
431 return result;
431 return result;
432 };
432 };
433
433
434 /**
434 /**
435 * Get the numeric index of a given cell.
435 * Get the numeric index of a given cell.
436 *
436 *
437 * @method find_cell_index
437 * @method find_cell_index
438 * @param {Cell} cell The provided cell
438 * @param {Cell} cell The provided cell
439 * @return {Number} The cell's numeric index
439 * @return {Number} The cell's numeric index
440 */
440 */
441 Notebook.prototype.find_cell_index = function (cell) {
441 Notebook.prototype.find_cell_index = function (cell) {
442 var result = null;
442 var result = null;
443 this.get_cell_elements().filter(function (index) {
443 this.get_cell_elements().filter(function (index) {
444 if ($(this).data("cell") === cell) {
444 if ($(this).data("cell") === cell) {
445 result = index;
445 result = index;
446 }
446 }
447 });
447 });
448 return result;
448 return result;
449 };
449 };
450
450
451 /**
451 /**
452 * Get a given index , or the selected index if none is provided.
452 * Get a given index , or the selected index if none is provided.
453 *
453 *
454 * @method index_or_selected
454 * @method index_or_selected
455 * @param {Number} index A cell's index
455 * @param {Number} index A cell's index
456 * @return {Number} The given index, or selected index if none is provided.
456 * @return {Number} The given index, or selected index if none is provided.
457 */
457 */
458 Notebook.prototype.index_or_selected = function (index) {
458 Notebook.prototype.index_or_selected = function (index) {
459 var i;
459 var i;
460 if (index === undefined || index === null) {
460 if (index === undefined || index === null) {
461 i = this.get_selected_index();
461 i = this.get_selected_index();
462 if (i === null) {
462 if (i === null) {
463 i = 0;
463 i = 0;
464 }
464 }
465 } else {
465 } else {
466 i = index;
466 i = index;
467 }
467 }
468 return i;
468 return i;
469 };
469 };
470
470
471 /**
471 /**
472 * Get the currently selected cell.
472 * Get the currently selected cell.
473 * @method get_selected_cell
473 * @method get_selected_cell
474 * @return {Cell} The selected cell
474 * @return {Cell} The selected cell
475 */
475 */
476 Notebook.prototype.get_selected_cell = function () {
476 Notebook.prototype.get_selected_cell = function () {
477 var index = this.get_selected_index();
477 var index = this.get_selected_index();
478 return this.get_cell(index);
478 return this.get_cell(index);
479 };
479 };
480
480
481 /**
481 /**
482 * Check whether a cell index is valid.
482 * Check whether a cell index is valid.
483 *
483 *
484 * @method is_valid_cell_index
484 * @method is_valid_cell_index
485 * @param {Number} index A cell index
485 * @param {Number} index A cell index
486 * @return True if the index is valid, false otherwise
486 * @return True if the index is valid, false otherwise
487 */
487 */
488 Notebook.prototype.is_valid_cell_index = function (index) {
488 Notebook.prototype.is_valid_cell_index = function (index) {
489 if (index !== null && index >= 0 && index < this.ncells()) {
489 if (index !== null && index >= 0 && index < this.ncells()) {
490 return true;
490 return true;
491 } else {
491 } else {
492 return false;
492 return false;
493 }
493 }
494 };
494 };
495
495
496 /**
496 /**
497 * Get the index of the currently selected cell.
497 * Get the index of the currently selected cell.
498
498
499 * @method get_selected_index
499 * @method get_selected_index
500 * @return {Number} The selected cell's numeric index
500 * @return {Number} The selected cell's numeric index
501 */
501 */
502 Notebook.prototype.get_selected_index = function () {
502 Notebook.prototype.get_selected_index = function () {
503 var result = null;
503 var result = null;
504 this.get_cell_elements().filter(function (index) {
504 this.get_cell_elements().filter(function (index) {
505 if ($(this).data("cell").selected === true) {
505 if ($(this).data("cell").selected === true) {
506 result = index;
506 result = index;
507 }
507 }
508 });
508 });
509 return result;
509 return result;
510 };
510 };
511
511
512
512
513 // Cell selection.
513 // Cell selection.
514
514
515 /**
515 /**
516 * Programmatically select a cell.
516 * Programmatically select a cell.
517 *
517 *
518 * @method select
518 * @method select
519 * @param {Number} index A cell's index
519 * @param {Number} index A cell's index
520 * @return {Notebook} This notebook
520 * @return {Notebook} This notebook
521 */
521 */
522 Notebook.prototype.select = function (index) {
522 Notebook.prototype.select = function (index) {
523 if (this.is_valid_cell_index(index)) {
523 if (this.is_valid_cell_index(index)) {
524 var sindex = this.get_selected_index();
524 var sindex = this.get_selected_index();
525 if (sindex !== null && index !== sindex) {
525 if (sindex !== null && index !== sindex) {
526 // If we are about to select a different cell, make sure we are
526 // If we are about to select a different cell, make sure we are
527 // first in command mode.
527 // first in command mode.
528 if (this.mode !== 'command') {
528 if (this.mode !== 'command') {
529 this.command_mode();
529 this.command_mode();
530 }
530 }
531 this.get_cell(sindex).unselect();
531 this.get_cell(sindex).unselect();
532 }
532 }
533 var cell = this.get_cell(index);
533 var cell = this.get_cell(index);
534 cell.select();
534 cell.select();
535 if (cell.cell_type === 'heading') {
535 if (cell.cell_type === 'heading') {
536 this.events.trigger('selected_cell_type_changed.Notebook',
536 this.events.trigger('selected_cell_type_changed.Notebook',
537 {'cell_type':cell.cell_type,level:cell.level}
537 {'cell_type':cell.cell_type,level:cell.level}
538 );
538 );
539 } else {
539 } else {
540 this.events.trigger('selected_cell_type_changed.Notebook',
540 this.events.trigger('selected_cell_type_changed.Notebook',
541 {'cell_type':cell.cell_type}
541 {'cell_type':cell.cell_type}
542 );
542 );
543 }
543 }
544 }
544 }
545 return this;
545 return this;
546 };
546 };
547
547
548 /**
548 /**
549 * Programmatically select the next cell.
549 * Programmatically select the next cell.
550 *
550 *
551 * @method select_next
551 * @method select_next
552 * @return {Notebook} This notebook
552 * @return {Notebook} This notebook
553 */
553 */
554 Notebook.prototype.select_next = function () {
554 Notebook.prototype.select_next = function () {
555 var index = this.get_selected_index();
555 var index = this.get_selected_index();
556 this.select(index+1);
556 this.select(index+1);
557 return this;
557 return this;
558 };
558 };
559
559
560 /**
560 /**
561 * Programmatically select the previous cell.
561 * Programmatically select the previous cell.
562 *
562 *
563 * @method select_prev
563 * @method select_prev
564 * @return {Notebook} This notebook
564 * @return {Notebook} This notebook
565 */
565 */
566 Notebook.prototype.select_prev = function () {
566 Notebook.prototype.select_prev = function () {
567 var index = this.get_selected_index();
567 var index = this.get_selected_index();
568 this.select(index-1);
568 this.select(index-1);
569 return this;
569 return this;
570 };
570 };
571
571
572
572
573 // Edit/Command mode
573 // Edit/Command mode
574
574
575 /**
575 /**
576 * Gets the index of the cell that is in edit mode.
576 * Gets the index of the cell that is in edit mode.
577 *
577 *
578 * @method get_edit_index
578 * @method get_edit_index
579 *
579 *
580 * @return index {int}
580 * @return index {int}
581 **/
581 **/
582 Notebook.prototype.get_edit_index = function () {
582 Notebook.prototype.get_edit_index = function () {
583 var result = null;
583 var result = null;
584 this.get_cell_elements().filter(function (index) {
584 this.get_cell_elements().filter(function (index) {
585 if ($(this).data("cell").mode === 'edit') {
585 if ($(this).data("cell").mode === 'edit') {
586 result = index;
586 result = index;
587 }
587 }
588 });
588 });
589 return result;
589 return result;
590 };
590 };
591
591
592 /**
592 /**
593 * Handle when a a cell blurs and the notebook should enter command mode.
593 * Handle when a a cell blurs and the notebook should enter command mode.
594 *
594 *
595 * @method handle_command_mode
595 * @method handle_command_mode
596 * @param [cell] {Cell} Cell to enter command mode on.
596 * @param [cell] {Cell} Cell to enter command mode on.
597 **/
597 **/
598 Notebook.prototype.handle_command_mode = function (cell) {
598 Notebook.prototype.handle_command_mode = function (cell) {
599 if (this.mode !== 'command') {
599 if (this.mode !== 'command') {
600 cell.command_mode();
600 cell.command_mode();
601 this.mode = 'command';
601 this.mode = 'command';
602 this.events.trigger('command_mode.Notebook');
602 this.events.trigger('command_mode.Notebook');
603 this.keyboard_manager.command_mode();
603 this.keyboard_manager.command_mode();
604 }
604 }
605 };
605 };
606
606
607 /**
607 /**
608 * Make the notebook enter command mode.
608 * Make the notebook enter command mode.
609 *
609 *
610 * @method command_mode
610 * @method command_mode
611 **/
611 **/
612 Notebook.prototype.command_mode = function () {
612 Notebook.prototype.command_mode = function () {
613 var cell = this.get_cell(this.get_edit_index());
613 var cell = this.get_cell(this.get_edit_index());
614 if (cell && this.mode !== 'command') {
614 if (cell && this.mode !== 'command') {
615 // We don't call cell.command_mode, but rather call cell.focus_cell()
615 // We don't call cell.command_mode, but rather call cell.focus_cell()
616 // which will blur and CM editor and trigger the call to
616 // which will blur and CM editor and trigger the call to
617 // handle_command_mode.
617 // handle_command_mode.
618 cell.focus_cell();
618 cell.focus_cell();
619 }
619 }
620 };
620 };
621
621
622 /**
622 /**
623 * Handle when a cell fires it's edit_mode event.
623 * Handle when a cell fires it's edit_mode event.
624 *
624 *
625 * @method handle_edit_mode
625 * @method handle_edit_mode
626 * @param [cell] {Cell} Cell to enter edit mode on.
626 * @param [cell] {Cell} Cell to enter edit mode on.
627 **/
627 **/
628 Notebook.prototype.handle_edit_mode = function (cell) {
628 Notebook.prototype.handle_edit_mode = function (cell) {
629 if (cell && this.mode !== 'edit') {
629 if (cell && this.mode !== 'edit') {
630 cell.edit_mode();
630 cell.edit_mode();
631 this.mode = 'edit';
631 this.mode = 'edit';
632 this.events.trigger('edit_mode.Notebook');
632 this.events.trigger('edit_mode.Notebook');
633 this.keyboard_manager.edit_mode();
633 this.keyboard_manager.edit_mode();
634 }
634 }
635 };
635 };
636
636
637 /**
637 /**
638 * Make a cell enter edit mode.
638 * Make a cell enter edit mode.
639 *
639 *
640 * @method edit_mode
640 * @method edit_mode
641 **/
641 **/
642 Notebook.prototype.edit_mode = function () {
642 Notebook.prototype.edit_mode = function () {
643 var cell = this.get_selected_cell();
643 var cell = this.get_selected_cell();
644 if (cell && this.mode !== 'edit') {
644 if (cell && this.mode !== 'edit') {
645 cell.unrender();
645 cell.unrender();
646 cell.focus_editor();
646 cell.focus_editor();
647 }
647 }
648 };
648 };
649
649
650 /**
650 /**
651 * Focus the currently selected cell.
651 * Focus the currently selected cell.
652 *
652 *
653 * @method focus_cell
653 * @method focus_cell
654 **/
654 **/
655 Notebook.prototype.focus_cell = function () {
655 Notebook.prototype.focus_cell = function () {
656 var cell = this.get_selected_cell();
656 var cell = this.get_selected_cell();
657 if (cell === null) {return;} // No cell is selected
657 if (cell === null) {return;} // No cell is selected
658 cell.focus_cell();
658 cell.focus_cell();
659 };
659 };
660
660
661 // Cell movement
661 // Cell movement
662
662
663 /**
663 /**
664 * Move given (or selected) cell up and select it.
664 * Move given (or selected) cell up and select it.
665 *
665 *
666 * @method move_cell_up
666 * @method move_cell_up
667 * @param [index] {integer} cell index
667 * @param [index] {integer} cell index
668 * @return {Notebook} This notebook
668 * @return {Notebook} This notebook
669 **/
669 **/
670 Notebook.prototype.move_cell_up = function (index) {
670 Notebook.prototype.move_cell_up = function (index) {
671 var i = this.index_or_selected(index);
671 var i = this.index_or_selected(index);
672 if (this.is_valid_cell_index(i) && i > 0) {
672 if (this.is_valid_cell_index(i) && i > 0) {
673 var pivot = this.get_cell_element(i-1);
673 var pivot = this.get_cell_element(i-1);
674 var tomove = this.get_cell_element(i);
674 var tomove = this.get_cell_element(i);
675 if (pivot !== null && tomove !== null) {
675 if (pivot !== null && tomove !== null) {
676 tomove.detach();
676 tomove.detach();
677 pivot.before(tomove);
677 pivot.before(tomove);
678 this.select(i-1);
678 this.select(i-1);
679 var cell = this.get_selected_cell();
679 var cell = this.get_selected_cell();
680 cell.focus_cell();
680 cell.focus_cell();
681 }
681 }
682 this.set_dirty(true);
682 this.set_dirty(true);
683 }
683 }
684 return this;
684 return this;
685 };
685 };
686
686
687
687
688 /**
688 /**
689 * Move given (or selected) cell down and select it
689 * Move given (or selected) cell down and select it
690 *
690 *
691 * @method move_cell_down
691 * @method move_cell_down
692 * @param [index] {integer} cell index
692 * @param [index] {integer} cell index
693 * @return {Notebook} This notebook
693 * @return {Notebook} This notebook
694 **/
694 **/
695 Notebook.prototype.move_cell_down = function (index) {
695 Notebook.prototype.move_cell_down = function (index) {
696 var i = this.index_or_selected(index);
696 var i = this.index_or_selected(index);
697 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
697 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
698 var pivot = this.get_cell_element(i+1);
698 var pivot = this.get_cell_element(i+1);
699 var tomove = this.get_cell_element(i);
699 var tomove = this.get_cell_element(i);
700 if (pivot !== null && tomove !== null) {
700 if (pivot !== null && tomove !== null) {
701 tomove.detach();
701 tomove.detach();
702 pivot.after(tomove);
702 pivot.after(tomove);
703 this.select(i+1);
703 this.select(i+1);
704 var cell = this.get_selected_cell();
704 var cell = this.get_selected_cell();
705 cell.focus_cell();
705 cell.focus_cell();
706 }
706 }
707 }
707 }
708 this.set_dirty();
708 this.set_dirty();
709 return this;
709 return this;
710 };
710 };
711
711
712
712
713 // Insertion, deletion.
713 // Insertion, deletion.
714
714
715 /**
715 /**
716 * Delete a cell from the notebook.
716 * Delete a cell from the notebook.
717 *
717 *
718 * @method delete_cell
718 * @method delete_cell
719 * @param [index] A cell's numeric index
719 * @param [index] A cell's numeric index
720 * @return {Notebook} This notebook
720 * @return {Notebook} This notebook
721 */
721 */
722 Notebook.prototype.delete_cell = function (index) {
722 Notebook.prototype.delete_cell = function (index) {
723 var i = this.index_or_selected(index);
723 var i = this.index_or_selected(index);
724 var cell = this.get_selected_cell();
724 var cell = this.get_selected_cell();
725 this.undelete_backup = cell.toJSON();
725 this.undelete_backup = cell.toJSON();
726 $('#undelete_cell').removeClass('disabled');
726 $('#undelete_cell').removeClass('disabled');
727 if (this.is_valid_cell_index(i)) {
727 if (this.is_valid_cell_index(i)) {
728 var old_ncells = this.ncells();
728 var old_ncells = this.ncells();
729 var ce = this.get_cell_element(i);
729 var ce = this.get_cell_element(i);
730 ce.remove();
730 ce.remove();
731 if (i === 0) {
731 if (i === 0) {
732 // Always make sure we have at least one cell.
732 // Always make sure we have at least one cell.
733 if (old_ncells === 1) {
733 if (old_ncells === 1) {
734 this.insert_cell_below('code');
734 this.insert_cell_below('code');
735 }
735 }
736 this.select(0);
736 this.select(0);
737 this.undelete_index = 0;
737 this.undelete_index = 0;
738 this.undelete_below = false;
738 this.undelete_below = false;
739 } else if (i === old_ncells-1 && i !== 0) {
739 } else if (i === old_ncells-1 && i !== 0) {
740 this.select(i-1);
740 this.select(i-1);
741 this.undelete_index = i - 1;
741 this.undelete_index = i - 1;
742 this.undelete_below = true;
742 this.undelete_below = true;
743 } else {
743 } else {
744 this.select(i);
744 this.select(i);
745 this.undelete_index = i;
745 this.undelete_index = i;
746 this.undelete_below = false;
746 this.undelete_below = false;
747 }
747 }
748 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
748 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
749 this.set_dirty(true);
749 this.set_dirty(true);
750 }
750 }
751 return this;
751 return this;
752 };
752 };
753
753
754 /**
754 /**
755 * Restore the most recently deleted cell.
755 * Restore the most recently deleted cell.
756 *
756 *
757 * @method undelete
757 * @method undelete
758 */
758 */
759 Notebook.prototype.undelete_cell = function() {
759 Notebook.prototype.undelete_cell = function() {
760 if (this.undelete_backup !== null && this.undelete_index !== null) {
760 if (this.undelete_backup !== null && this.undelete_index !== null) {
761 var current_index = this.get_selected_index();
761 var current_index = this.get_selected_index();
762 if (this.undelete_index < current_index) {
762 if (this.undelete_index < current_index) {
763 current_index = current_index + 1;
763 current_index = current_index + 1;
764 }
764 }
765 if (this.undelete_index >= this.ncells()) {
765 if (this.undelete_index >= this.ncells()) {
766 this.select(this.ncells() - 1);
766 this.select(this.ncells() - 1);
767 }
767 }
768 else {
768 else {
769 this.select(this.undelete_index);
769 this.select(this.undelete_index);
770 }
770 }
771 var cell_data = this.undelete_backup;
771 var cell_data = this.undelete_backup;
772 var new_cell = null;
772 var new_cell = null;
773 if (this.undelete_below) {
773 if (this.undelete_below) {
774 new_cell = this.insert_cell_below(cell_data.cell_type);
774 new_cell = this.insert_cell_below(cell_data.cell_type);
775 } else {
775 } else {
776 new_cell = this.insert_cell_above(cell_data.cell_type);
776 new_cell = this.insert_cell_above(cell_data.cell_type);
777 }
777 }
778 new_cell.fromJSON(cell_data);
778 new_cell.fromJSON(cell_data);
779 if (this.undelete_below) {
779 if (this.undelete_below) {
780 this.select(current_index+1);
780 this.select(current_index+1);
781 } else {
781 } else {
782 this.select(current_index);
782 this.select(current_index);
783 }
783 }
784 this.undelete_backup = null;
784 this.undelete_backup = null;
785 this.undelete_index = null;
785 this.undelete_index = null;
786 }
786 }
787 $('#undelete_cell').addClass('disabled');
787 $('#undelete_cell').addClass('disabled');
788 };
788 };
789
789
790 /**
790 /**
791 * Insert a cell so that after insertion the cell is at given index.
791 * Insert a cell so that after insertion the cell is at given index.
792 *
792 *
793 * If cell type is not provided, it will default to the type of the
793 * If cell type is not provided, it will default to the type of the
794 * currently active cell.
794 * currently active cell.
795 *
795 *
796 * Similar to insert_above, but index parameter is mandatory
796 * Similar to insert_above, but index parameter is mandatory
797 *
797 *
798 * Index will be brought back into the accessible range [0,n]
798 * Index will be brought back into the accessible range [0,n]
799 *
799 *
800 * @method insert_cell_at_index
800 * @method insert_cell_at_index
801 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
801 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
802 * @param [index] {int} a valid index where to insert cell
802 * @param [index] {int} a valid index where to insert cell
803 *
803 *
804 * @return cell {cell|null} created cell or null
804 * @return cell {cell|null} created cell or null
805 **/
805 **/
806 Notebook.prototype.insert_cell_at_index = function(type, index){
806 Notebook.prototype.insert_cell_at_index = function(type, index){
807
807
808 var ncells = this.ncells();
808 var ncells = this.ncells();
809 index = Math.min(index,ncells);
809 index = Math.min(index,ncells);
810 index = Math.max(index,0);
810 index = Math.max(index,0);
811 var cell = null;
811 var cell = null;
812 type = type || this.get_selected_cell().cell_type;
812 type = type || this.get_selected_cell().cell_type;
813
813
814 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
814 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
815 if (type === 'code') {
815 if (type === 'code') {
816 cell = new CodeCell(this.kernel);
816 cell = new CodeCell(this.kernel);
817 cell.set_input_prompt();
817 cell.set_input_prompt();
818 } else if (type === 'markdown') {
818 } else if (type === 'markdown') {
819 cell = new Cells.MarkdownCell();
819 cell = new Cells.MarkdownCell();
820 } else if (type === 'raw') {
820 } else if (type === 'raw') {
821 cell = new Cells.RawCell();
821 cell = new Cells.RawCell();
822 } else if (type === 'heading') {
822 } else if (type === 'heading') {
823 cell = new Cells.HeadingCell();
823 cell = new Cells.HeadingCell();
824 }
824 }
825
825
826 if(this._insert_element_at_index(cell.element,index)) {
826 if(this._insert_element_at_index(cell.element,index)) {
827 cell.render();
827 cell.render();
828 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
828 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
829 cell.refresh();
829 cell.refresh();
830 // We used to select the cell after we refresh it, but there
830 // We used to select the cell after we refresh it, but there
831 // are now cases were this method is called where select is
831 // are now cases were this method is called where select is
832 // not appropriate. The selection logic should be handled by the
832 // not appropriate. The selection logic should be handled by the
833 // caller of the the top level insert_cell methods.
833 // caller of the the top level insert_cell methods.
834 this.set_dirty(true);
834 this.set_dirty(true);
835 }
835 }
836 }
836 }
837 return cell;
837 return cell;
838
838
839 };
839 };
840
840
841 /**
841 /**
842 * Insert an element at given cell index.
842 * Insert an element at given cell index.
843 *
843 *
844 * @method _insert_element_at_index
844 * @method _insert_element_at_index
845 * @param element {dom element} a cell element
845 * @param element {dom element} a cell element
846 * @param [index] {int} a valid index where to inser cell
846 * @param [index] {int} a valid index where to inser cell
847 * @private
847 * @private
848 *
848 *
849 * return true if everything whent fine.
849 * return true if everything whent fine.
850 **/
850 **/
851 Notebook.prototype._insert_element_at_index = function(element, index){
851 Notebook.prototype._insert_element_at_index = function(element, index){
852 if (element === undefined){
852 if (element === undefined){
853 return false;
853 return false;
854 }
854 }
855
855
856 var ncells = this.ncells();
856 var ncells = this.ncells();
857
857
858 if (ncells === 0) {
858 if (ncells === 0) {
859 // special case append if empty
859 // special case append if empty
860 this.element.find('div.end_space').before(element);
860 this.element.find('div.end_space').before(element);
861 } else if ( ncells === index ) {
861 } else if ( ncells === index ) {
862 // special case append it the end, but not empty
862 // special case append it the end, but not empty
863 this.get_cell_element(index-1).after(element);
863 this.get_cell_element(index-1).after(element);
864 } else if (this.is_valid_cell_index(index)) {
864 } else if (this.is_valid_cell_index(index)) {
865 // otherwise always somewhere to append to
865 // otherwise always somewhere to append to
866 this.get_cell_element(index).before(element);
866 this.get_cell_element(index).before(element);
867 } else {
867 } else {
868 return false;
868 return false;
869 }
869 }
870
870
871 if (this.undelete_index !== null && index <= this.undelete_index) {
871 if (this.undelete_index !== null && index <= this.undelete_index) {
872 this.undelete_index = this.undelete_index + 1;
872 this.undelete_index = this.undelete_index + 1;
873 this.set_dirty(true);
873 this.set_dirty(true);
874 }
874 }
875 return true;
875 return true;
876 };
876 };
877
877
878 /**
878 /**
879 * Insert a cell of given type above given index, or at top
879 * Insert a cell of given type above given index, or at top
880 * of notebook if index smaller than 0.
880 * of notebook if index smaller than 0.
881 *
881 *
882 * default index value is the one of currently selected cell
882 * default index value is the one of currently selected cell
883 *
883 *
884 * @method insert_cell_above
884 * @method insert_cell_above
885 * @param [type] {string} cell type
885 * @param [type] {string} cell type
886 * @param [index] {integer}
886 * @param [index] {integer}
887 *
887 *
888 * @return handle to created cell or null
888 * @return handle to created cell or null
889 **/
889 **/
890 Notebook.prototype.insert_cell_above = function (type, index) {
890 Notebook.prototype.insert_cell_above = function (type, index) {
891 index = this.index_or_selected(index);
891 index = this.index_or_selected(index);
892 return this.insert_cell_at_index(type, index);
892 return this.insert_cell_at_index(type, index);
893 };
893 };
894
894
895 /**
895 /**
896 * Insert a cell of given type below given index, or at bottom
896 * Insert a cell of given type below given index, or at bottom
897 * of notebook if index greater than number of cells
897 * of notebook if index greater than number of cells
898 *
898 *
899 * default index value is the one of currently selected cell
899 * default index value is the one of currently selected cell
900 *
900 *
901 * @method insert_cell_below
901 * @method insert_cell_below
902 * @param [type] {string} cell type
902 * @param [type] {string} cell type
903 * @param [index] {integer}
903 * @param [index] {integer}
904 *
904 *
905 * @return handle to created cell or null
905 * @return handle to created cell or null
906 *
906 *
907 **/
907 **/
908 Notebook.prototype.insert_cell_below = function (type, index) {
908 Notebook.prototype.insert_cell_below = function (type, index) {
909 index = this.index_or_selected(index);
909 index = this.index_or_selected(index);
910 return this.insert_cell_at_index(type, index+1);
910 return this.insert_cell_at_index(type, index+1);
911 };
911 };
912
912
913
913
914 /**
914 /**
915 * Insert cell at end of notebook
915 * Insert cell at end of notebook
916 *
916 *
917 * @method insert_cell_at_bottom
917 * @method insert_cell_at_bottom
918 * @param {String} type cell type
918 * @param {String} type cell type
919 *
919 *
920 * @return the added cell; or null
920 * @return the added cell; or null
921 **/
921 **/
922 Notebook.prototype.insert_cell_at_bottom = function (type){
922 Notebook.prototype.insert_cell_at_bottom = function (type){
923 var len = this.ncells();
923 var len = this.ncells();
924 return this.insert_cell_below(type,len-1);
924 return this.insert_cell_below(type,len-1);
925 };
925 };
926
926
927 /**
927 /**
928 * Turn a cell into a code cell.
928 * Turn a cell into a code cell.
929 *
929 *
930 * @method to_code
930 * @method to_code
931 * @param {Number} [index] A cell's index
931 * @param {Number} [index] A cell's index
932 */
932 */
933 Notebook.prototype.to_code = function (index) {
933 Notebook.prototype.to_code = function (index) {
934 var i = this.index_or_selected(index);
934 var i = this.index_or_selected(index);
935 if (this.is_valid_cell_index(i)) {
935 if (this.is_valid_cell_index(i)) {
936 var source_element = this.get_cell_element(i);
936 var source_element = this.get_cell_element(i);
937 var source_cell = source_element.data("cell");
937 var source_cell = source_element.data("cell");
938 if (!(source_cell instanceof CodeCell)) {
938 if (!(source_cell instanceof CodeCell)) {
939 var target_cell = this.insert_cell_below('code',i);
939 var target_cell = this.insert_cell_below('code',i);
940 var text = source_cell.get_text();
940 var text = source_cell.get_text();
941 if (text === source_cell.placeholder) {
941 if (text === source_cell.placeholder) {
942 text = '';
942 text = '';
943 }
943 }
944 target_cell.set_text(text);
944 target_cell.set_text(text);
945 // make this value the starting point, so that we can only undo
945 // make this value the starting point, so that we can only undo
946 // to this state, instead of a blank cell
946 // to this state, instead of a blank cell
947 target_cell.code_mirror.clearHistory();
947 target_cell.code_mirror.clearHistory();
948 source_element.remove();
948 source_element.remove();
949 this.select(i);
949 this.select(i);
950 var cursor = source_cell.code_mirror.getCursor();
950 var cursor = source_cell.code_mirror.getCursor();
951 target_cell.code_mirror.setCursor(cursor);
951 target_cell.code_mirror.setCursor(cursor);
952 this.set_dirty(true);
952 this.set_dirty(true);
953 }
953 }
954 }
954 }
955 };
955 };
956
956
957 /**
957 /**
958 * Turn a cell into a Markdown cell.
958 * Turn a cell into a Markdown cell.
959 *
959 *
960 * @method to_markdown
960 * @method to_markdown
961 * @param {Number} [index] A cell's index
961 * @param {Number} [index] A cell's index
962 */
962 */
963 Notebook.prototype.to_markdown = function (index) {
963 Notebook.prototype.to_markdown = function (index) {
964 var i = this.index_or_selected(index);
964 var i = this.index_or_selected(index);
965 if (this.is_valid_cell_index(i)) {
965 if (this.is_valid_cell_index(i)) {
966 var source_element = this.get_cell_element(i);
966 var source_element = this.get_cell_element(i);
967 var source_cell = source_element.data("cell");
967 var source_cell = source_element.data("cell");
968 if (!(source_cell instanceof Cells.MarkdownCell)) {
968 if (!(source_cell instanceof Cells.MarkdownCell)) {
969 var target_cell = this.insert_cell_below('markdown',i);
969 var target_cell = this.insert_cell_below('markdown',i);
970 var text = source_cell.get_text();
970 var text = source_cell.get_text();
971 if (text === source_cell.placeholder) {
971 if (text === source_cell.placeholder) {
972 text = '';
972 text = '';
973 }
973 }
974 // We must show the editor before setting its contents
974 // We must show the editor before setting its contents
975 target_cell.unrender();
975 target_cell.unrender();
976 target_cell.set_text(text);
976 target_cell.set_text(text);
977 // make this value the starting point, so that we can only undo
977 // make this value the starting point, so that we can only undo
978 // to this state, instead of a blank cell
978 // to this state, instead of a blank cell
979 target_cell.code_mirror.clearHistory();
979 target_cell.code_mirror.clearHistory();
980 source_element.remove();
980 source_element.remove();
981 this.select(i);
981 this.select(i);
982 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
982 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
983 target_cell.render();
983 target_cell.render();
984 }
984 }
985 var cursor = source_cell.code_mirror.getCursor();
985 var cursor = source_cell.code_mirror.getCursor();
986 target_cell.code_mirror.setCursor(cursor);
986 target_cell.code_mirror.setCursor(cursor);
987 this.set_dirty(true);
987 this.set_dirty(true);
988 }
988 }
989 }
989 }
990 };
990 };
991
991
992 /**
992 /**
993 * Turn a cell into a raw text cell.
993 * Turn a cell into a raw text cell.
994 *
994 *
995 * @method to_raw
995 * @method to_raw
996 * @param {Number} [index] A cell's index
996 * @param {Number} [index] A cell's index
997 */
997 */
998 Notebook.prototype.to_raw = function (index) {
998 Notebook.prototype.to_raw = function (index) {
999 var i = this.index_or_selected(index);
999 var i = this.index_or_selected(index);
1000 if (this.is_valid_cell_index(i)) {
1000 if (this.is_valid_cell_index(i)) {
1001 var source_element = this.get_cell_element(i);
1001 var source_element = this.get_cell_element(i);
1002 var source_cell = source_element.data("cell");
1002 var source_cell = source_element.data("cell");
1003 var target_cell = null;
1003 var target_cell = null;
1004 if (!(source_cell instanceof Cells.RawCell)) {
1004 if (!(source_cell instanceof Cells.RawCell)) {
1005 target_cell = this.insert_cell_below('raw',i);
1005 target_cell = this.insert_cell_below('raw',i);
1006 var text = source_cell.get_text();
1006 var text = source_cell.get_text();
1007 if (text === source_cell.placeholder) {
1007 if (text === source_cell.placeholder) {
1008 text = '';
1008 text = '';
1009 }
1009 }
1010 // We must show the editor before setting its contents
1010 // We must show the editor before setting its contents
1011 target_cell.unrender();
1011 target_cell.unrender();
1012 target_cell.set_text(text);
1012 target_cell.set_text(text);
1013 // make this value the starting point, so that we can only undo
1013 // make this value the starting point, so that we can only undo
1014 // to this state, instead of a blank cell
1014 // to this state, instead of a blank cell
1015 target_cell.code_mirror.clearHistory();
1015 target_cell.code_mirror.clearHistory();
1016 source_element.remove();
1016 source_element.remove();
1017 this.select(i);
1017 this.select(i);
1018 var cursor = source_cell.code_mirror.getCursor();
1018 var cursor = source_cell.code_mirror.getCursor();
1019 target_cell.code_mirror.setCursor(cursor);
1019 target_cell.code_mirror.setCursor(cursor);
1020 this.set_dirty(true);
1020 this.set_dirty(true);
1021 }
1021 }
1022 }
1022 }
1023 };
1023 };
1024
1024
1025 /**
1025 /**
1026 * Turn a cell into a heading cell.
1026 * Turn a cell into a heading cell.
1027 *
1027 *
1028 * @method to_heading
1028 * @method to_heading
1029 * @param {Number} [index] A cell's index
1029 * @param {Number} [index] A cell's index
1030 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1030 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1031 */
1031 */
1032 Notebook.prototype.to_heading = function (index, level) {
1032 Notebook.prototype.to_heading = function (index, level) {
1033 level = level || 1;
1033 level = level || 1;
1034 var i = this.index_or_selected(index);
1034 var i = this.index_or_selected(index);
1035 if (this.is_valid_cell_index(i)) {
1035 if (this.is_valid_cell_index(i)) {
1036 var source_element = this.get_cell_element(i);
1036 var source_element = this.get_cell_element(i);
1037 var source_cell = source_element.data("cell");
1037 var source_cell = source_element.data("cell");
1038 var target_cell = null;
1038 var target_cell = null;
1039 if (source_cell instanceof Cells.HeadingCell) {
1039 if (source_cell instanceof Cells.HeadingCell) {
1040 source_cell.set_level(level);
1040 source_cell.set_level(level);
1041 } else {
1041 } else {
1042 target_cell = this.insert_cell_below('heading',i);
1042 target_cell = this.insert_cell_below('heading',i);
1043 var text = source_cell.get_text();
1043 var text = source_cell.get_text();
1044 if (text === source_cell.placeholder) {
1044 if (text === source_cell.placeholder) {
1045 text = '';
1045 text = '';
1046 }
1046 }
1047 // We must show the editor before setting its contents
1047 // We must show the editor before setting its contents
1048 target_cell.set_level(level);
1048 target_cell.set_level(level);
1049 target_cell.unrender();
1049 target_cell.unrender();
1050 target_cell.set_text(text);
1050 target_cell.set_text(text);
1051 // make this value the starting point, so that we can only undo
1051 // make this value the starting point, so that we can only undo
1052 // to this state, instead of a blank cell
1052 // to this state, instead of a blank cell
1053 target_cell.code_mirror.clearHistory();
1053 target_cell.code_mirror.clearHistory();
1054 source_element.remove();
1054 source_element.remove();
1055 this.select(i);
1055 this.select(i);
1056 var cursor = source_cell.code_mirror.getCursor();
1056 var cursor = source_cell.code_mirror.getCursor();
1057 target_cell.code_mirror.setCursor(cursor);
1057 target_cell.code_mirror.setCursor(cursor);
1058 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
1058 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
1059 target_cell.render();
1059 target_cell.render();
1060 }
1060 }
1061 }
1061 }
1062 this.set_dirty(true);
1062 this.set_dirty(true);
1063 this.events.trigger('selected_cell_type_changed.Notebook',
1063 this.events.trigger('selected_cell_type_changed.Notebook',
1064 {'cell_type':'heading',level:level}
1064 {'cell_type':'heading',level:level}
1065 );
1065 );
1066 }
1066 }
1067 };
1067 };
1068
1068
1069
1069
1070 // Cut/Copy/Paste
1070 // Cut/Copy/Paste
1071
1071
1072 /**
1072 /**
1073 * Enable UI elements for pasting cells.
1073 * Enable UI elements for pasting cells.
1074 *
1074 *
1075 * @method enable_paste
1075 * @method enable_paste
1076 */
1076 */
1077 Notebook.prototype.enable_paste = function () {
1077 Notebook.prototype.enable_paste = function () {
1078 var that = this;
1078 var that = this;
1079 if (!this.paste_enabled) {
1079 if (!this.paste_enabled) {
1080 $('#paste_cell_replace').removeClass('disabled')
1080 $('#paste_cell_replace').removeClass('disabled')
1081 .on('click', function () {that.paste_cell_replace();});
1081 .on('click', function () {that.paste_cell_replace();});
1082 $('#paste_cell_above').removeClass('disabled')
1082 $('#paste_cell_above').removeClass('disabled')
1083 .on('click', function () {that.paste_cell_above();});
1083 .on('click', function () {that.paste_cell_above();});
1084 $('#paste_cell_below').removeClass('disabled')
1084 $('#paste_cell_below').removeClass('disabled')
1085 .on('click', function () {that.paste_cell_below();});
1085 .on('click', function () {that.paste_cell_below();});
1086 this.paste_enabled = true;
1086 this.paste_enabled = true;
1087 }
1087 }
1088 };
1088 };
1089
1089
1090 /**
1090 /**
1091 * Disable UI elements for pasting cells.
1091 * Disable UI elements for pasting cells.
1092 *
1092 *
1093 * @method disable_paste
1093 * @method disable_paste
1094 */
1094 */
1095 Notebook.prototype.disable_paste = function () {
1095 Notebook.prototype.disable_paste = function () {
1096 if (this.paste_enabled) {
1096 if (this.paste_enabled) {
1097 $('#paste_cell_replace').addClass('disabled').off('click');
1097 $('#paste_cell_replace').addClass('disabled').off('click');
1098 $('#paste_cell_above').addClass('disabled').off('click');
1098 $('#paste_cell_above').addClass('disabled').off('click');
1099 $('#paste_cell_below').addClass('disabled').off('click');
1099 $('#paste_cell_below').addClass('disabled').off('click');
1100 this.paste_enabled = false;
1100 this.paste_enabled = false;
1101 }
1101 }
1102 };
1102 };
1103
1103
1104 /**
1104 /**
1105 * Cut a cell.
1105 * Cut a cell.
1106 *
1106 *
1107 * @method cut_cell
1107 * @method cut_cell
1108 */
1108 */
1109 Notebook.prototype.cut_cell = function () {
1109 Notebook.prototype.cut_cell = function () {
1110 this.copy_cell();
1110 this.copy_cell();
1111 this.delete_cell();
1111 this.delete_cell();
1112 };
1112 };
1113
1113
1114 /**
1114 /**
1115 * Copy a cell.
1115 * Copy a cell.
1116 *
1116 *
1117 * @method copy_cell
1117 * @method copy_cell
1118 */
1118 */
1119 Notebook.prototype.copy_cell = function () {
1119 Notebook.prototype.copy_cell = function () {
1120 var cell = this.get_selected_cell();
1120 var cell = this.get_selected_cell();
1121 this.clipboard = cell.toJSON();
1121 this.clipboard = cell.toJSON();
1122 this.enable_paste();
1122 this.enable_paste();
1123 };
1123 };
1124
1124
1125 /**
1125 /**
1126 * Replace the selected cell with a cell in the clipboard.
1126 * Replace the selected cell with a cell in the clipboard.
1127 *
1127 *
1128 * @method paste_cell_replace
1128 * @method paste_cell_replace
1129 */
1129 */
1130 Notebook.prototype.paste_cell_replace = function () {
1130 Notebook.prototype.paste_cell_replace = function () {
1131 if (this.clipboard !== null && this.paste_enabled) {
1131 if (this.clipboard !== null && this.paste_enabled) {
1132 var cell_data = this.clipboard;
1132 var cell_data = this.clipboard;
1133 var new_cell = this.insert_cell_above(cell_data.cell_type);
1133 var new_cell = this.insert_cell_above(cell_data.cell_type);
1134 new_cell.fromJSON(cell_data);
1134 new_cell.fromJSON(cell_data);
1135 var old_cell = this.get_next_cell(new_cell);
1135 var old_cell = this.get_next_cell(new_cell);
1136 this.delete_cell(this.find_cell_index(old_cell));
1136 this.delete_cell(this.find_cell_index(old_cell));
1137 this.select(this.find_cell_index(new_cell));
1137 this.select(this.find_cell_index(new_cell));
1138 }
1138 }
1139 };
1139 };
1140
1140
1141 /**
1141 /**
1142 * Paste a cell from the clipboard above the selected cell.
1142 * Paste a cell from the clipboard above the selected cell.
1143 *
1143 *
1144 * @method paste_cell_above
1144 * @method paste_cell_above
1145 */
1145 */
1146 Notebook.prototype.paste_cell_above = function () {
1146 Notebook.prototype.paste_cell_above = function () {
1147 if (this.clipboard !== null && this.paste_enabled) {
1147 if (this.clipboard !== null && this.paste_enabled) {
1148 var cell_data = this.clipboard;
1148 var cell_data = this.clipboard;
1149 var new_cell = this.insert_cell_above(cell_data.cell_type);
1149 var new_cell = this.insert_cell_above(cell_data.cell_type);
1150 new_cell.fromJSON(cell_data);
1150 new_cell.fromJSON(cell_data);
1151 new_cell.focus_cell();
1151 new_cell.focus_cell();
1152 }
1152 }
1153 };
1153 };
1154
1154
1155 /**
1155 /**
1156 * Paste a cell from the clipboard below the selected cell.
1156 * Paste a cell from the clipboard below the selected cell.
1157 *
1157 *
1158 * @method paste_cell_below
1158 * @method paste_cell_below
1159 */
1159 */
1160 Notebook.prototype.paste_cell_below = function () {
1160 Notebook.prototype.paste_cell_below = function () {
1161 if (this.clipboard !== null && this.paste_enabled) {
1161 if (this.clipboard !== null && this.paste_enabled) {
1162 var cell_data = this.clipboard;
1162 var cell_data = this.clipboard;
1163 var new_cell = this.insert_cell_below(cell_data.cell_type);
1163 var new_cell = this.insert_cell_below(cell_data.cell_type);
1164 new_cell.fromJSON(cell_data);
1164 new_cell.fromJSON(cell_data);
1165 new_cell.focus_cell();
1165 new_cell.focus_cell();
1166 }
1166 }
1167 };
1167 };
1168
1168
1169 // Split/merge
1169 // Split/merge
1170
1170
1171 /**
1171 /**
1172 * Split the selected cell into two, at the cursor.
1172 * Split the selected cell into two, at the cursor.
1173 *
1173 *
1174 * @method split_cell
1174 * @method split_cell
1175 */
1175 */
1176 Notebook.prototype.split_cell = function () {
1176 Notebook.prototype.split_cell = function () {
1177 var mdc = Cells.MarkdownCell;
1177 var mdc = Cells.MarkdownCell;
1178 var rc = Cells.RawCell;
1178 var rc = Cells.RawCell;
1179 var cell = this.get_selected_cell();
1179 var cell = this.get_selected_cell();
1180 if (cell.is_splittable()) {
1180 if (cell.is_splittable()) {
1181 var texta = cell.get_pre_cursor();
1181 var texta = cell.get_pre_cursor();
1182 var textb = cell.get_post_cursor();
1182 var textb = cell.get_post_cursor();
1183 if (cell instanceof CodeCell) {
1183 if (cell instanceof CodeCell) {
1184 // In this case the operations keep the notebook in its existing mode
1184 // In this case the operations keep the notebook in its existing mode
1185 // so we don't need to do any post-op mode changes.
1185 // so we don't need to do any post-op mode changes.
1186 cell.set_text(textb);
1186 cell.set_text(textb);
1187 var new_cell = this.insert_cell_above('code');
1187 var new_cell = this.insert_cell_above('code');
1188 new_cell.set_text(texta);
1188 new_cell.set_text(texta);
1189 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1189 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1190 // We know cell is !rendered so we can use set_text.
1190 // We know cell is !rendered so we can use set_text.
1191 cell.set_text(textb);
1191 cell.set_text(textb);
1192 var new_cell = this.insert_cell_above(cell.cell_type);
1192 var new_cell = this.insert_cell_above(cell.cell_type);
1193 // Unrender the new cell so we can call set_text.
1193 // Unrender the new cell so we can call set_text.
1194 new_cell.unrender();
1194 new_cell.unrender();
1195 new_cell.set_text(texta);
1195 new_cell.set_text(texta);
1196 }
1196 }
1197 }
1197 }
1198 };
1198 };
1199
1199
1200 /**
1200 /**
1201 * Combine the selected cell into the cell above it.
1201 * Combine the selected cell into the cell above it.
1202 *
1202 *
1203 * @method merge_cell_above
1203 * @method merge_cell_above
1204 */
1204 */
1205 Notebook.prototype.merge_cell_above = function () {
1205 Notebook.prototype.merge_cell_above = function () {
1206 var mdc = Cells.MarkdownCell;
1206 var mdc = Cells.MarkdownCell;
1207 var rc = Cells.RawCell;
1207 var rc = Cells.RawCell;
1208 var index = this.get_selected_index();
1208 var index = this.get_selected_index();
1209 var cell = this.get_cell(index);
1209 var cell = this.get_cell(index);
1210 var render = cell.rendered;
1210 var render = cell.rendered;
1211 if (!cell.is_mergeable()) {
1211 if (!cell.is_mergeable()) {
1212 return;
1212 return;
1213 }
1213 }
1214 if (index > 0) {
1214 if (index > 0) {
1215 var upper_cell = this.get_cell(index-1);
1215 var upper_cell = this.get_cell(index-1);
1216 if (!upper_cell.is_mergeable()) {
1216 if (!upper_cell.is_mergeable()) {
1217 return;
1217 return;
1218 }
1218 }
1219 var upper_text = upper_cell.get_text();
1219 var upper_text = upper_cell.get_text();
1220 var text = cell.get_text();
1220 var text = cell.get_text();
1221 if (cell instanceof CodeCell) {
1221 if (cell instanceof CodeCell) {
1222 cell.set_text(upper_text+'\n'+text);
1222 cell.set_text(upper_text+'\n'+text);
1223 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1223 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1224 cell.unrender(); // Must unrender before we set_text.
1224 cell.unrender(); // Must unrender before we set_text.
1225 cell.set_text(upper_text+'\n\n'+text);
1225 cell.set_text(upper_text+'\n\n'+text);
1226 if (render) {
1226 if (render) {
1227 // The rendered state of the final cell should match
1227 // The rendered state of the final cell should match
1228 // that of the original selected cell;
1228 // that of the original selected cell;
1229 cell.render();
1229 cell.render();
1230 }
1230 }
1231 }
1231 }
1232 this.delete_cell(index-1);
1232 this.delete_cell(index-1);
1233 this.select(this.find_cell_index(cell));
1233 this.select(this.find_cell_index(cell));
1234 }
1234 }
1235 };
1235 };
1236
1236
1237 /**
1237 /**
1238 * Combine the selected cell into the cell below it.
1238 * Combine the selected cell into the cell below it.
1239 *
1239 *
1240 * @method merge_cell_below
1240 * @method merge_cell_below
1241 */
1241 */
1242 Notebook.prototype.merge_cell_below = function () {
1242 Notebook.prototype.merge_cell_below = function () {
1243 var mdc = Cells.MarkdownCell;
1243 var mdc = Cells.MarkdownCell;
1244 var rc = Cells.RawCell;
1244 var rc = Cells.RawCell;
1245 var index = this.get_selected_index();
1245 var index = this.get_selected_index();
1246 var cell = this.get_cell(index);
1246 var cell = this.get_cell(index);
1247 var render = cell.rendered;
1247 var render = cell.rendered;
1248 if (!cell.is_mergeable()) {
1248 if (!cell.is_mergeable()) {
1249 return;
1249 return;
1250 }
1250 }
1251 if (index < this.ncells()-1) {
1251 if (index < this.ncells()-1) {
1252 var lower_cell = this.get_cell(index+1);
1252 var lower_cell = this.get_cell(index+1);
1253 if (!lower_cell.is_mergeable()) {
1253 if (!lower_cell.is_mergeable()) {
1254 return;
1254 return;
1255 }
1255 }
1256 var lower_text = lower_cell.get_text();
1256 var lower_text = lower_cell.get_text();
1257 var text = cell.get_text();
1257 var text = cell.get_text();
1258 if (cell instanceof CodeCell) {
1258 if (cell instanceof CodeCell) {
1259 cell.set_text(text+'\n'+lower_text);
1259 cell.set_text(text+'\n'+lower_text);
1260 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1260 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1261 cell.unrender(); // Must unrender before we set_text.
1261 cell.unrender(); // Must unrender before we set_text.
1262 cell.set_text(text+'\n\n'+lower_text);
1262 cell.set_text(text+'\n\n'+lower_text);
1263 if (render) {
1263 if (render) {
1264 // The rendered state of the final cell should match
1264 // The rendered state of the final cell should match
1265 // that of the original selected cell;
1265 // that of the original selected cell;
1266 cell.render();
1266 cell.render();
1267 }
1267 }
1268 }
1268 }
1269 this.delete_cell(index+1);
1269 this.delete_cell(index+1);
1270 this.select(this.find_cell_index(cell));
1270 this.select(this.find_cell_index(cell));
1271 }
1271 }
1272 };
1272 };
1273
1273
1274
1274
1275 // Cell collapsing and output clearing
1275 // Cell collapsing and output clearing
1276
1276
1277 /**
1277 /**
1278 * Hide a cell's output.
1278 * Hide a cell's output.
1279 *
1279 *
1280 * @method collapse_output
1280 * @method collapse_output
1281 * @param {Number} index A cell's numeric index
1281 * @param {Number} index A cell's numeric index
1282 */
1282 */
1283 Notebook.prototype.collapse_output = function (index) {
1283 Notebook.prototype.collapse_output = function (index) {
1284 var i = this.index_or_selected(index);
1284 var i = this.index_or_selected(index);
1285 var cell = this.get_cell(i);
1285 var cell = this.get_cell(i);
1286 if (cell !== null && (cell instanceof CodeCell)) {
1286 if (cell !== null && (cell instanceof CodeCell)) {
1287 cell.collapse_output();
1287 cell.collapse_output();
1288 this.set_dirty(true);
1288 this.set_dirty(true);
1289 }
1289 }
1290 };
1290 };
1291
1291
1292 /**
1292 /**
1293 * Hide each code cell's output area.
1293 * Hide each code cell's output area.
1294 *
1294 *
1295 * @method collapse_all_output
1295 * @method collapse_all_output
1296 */
1296 */
1297 Notebook.prototype.collapse_all_output = function () {
1297 Notebook.prototype.collapse_all_output = function () {
1298 $.map(this.get_cells(), function (cell, i) {
1298 $.map(this.get_cells(), function (cell, i) {
1299 if (cell instanceof CodeCell) {
1299 if (cell instanceof CodeCell) {
1300 cell.collapse_output();
1300 cell.collapse_output();
1301 }
1301 }
1302 });
1302 });
1303 // this should not be set if the `collapse` key is removed from nbformat
1303 // this should not be set if the `collapse` key is removed from nbformat
1304 this.set_dirty(true);
1304 this.set_dirty(true);
1305 };
1305 };
1306
1306
1307 /**
1307 /**
1308 * Show a cell's output.
1308 * Show a cell's output.
1309 *
1309 *
1310 * @method expand_output
1310 * @method expand_output
1311 * @param {Number} index A cell's numeric index
1311 * @param {Number} index A cell's numeric index
1312 */
1312 */
1313 Notebook.prototype.expand_output = function (index) {
1313 Notebook.prototype.expand_output = function (index) {
1314 var i = this.index_or_selected(index);
1314 var i = this.index_or_selected(index);
1315 var cell = this.get_cell(i);
1315 var cell = this.get_cell(i);
1316 if (cell !== null && (cell instanceof CodeCell)) {
1316 if (cell !== null && (cell instanceof CodeCell)) {
1317 cell.expand_output();
1317 cell.expand_output();
1318 this.set_dirty(true);
1318 this.set_dirty(true);
1319 }
1319 }
1320 };
1320 };
1321
1321
1322 /**
1322 /**
1323 * Expand each code cell's output area, and remove scrollbars.
1323 * Expand each code cell's output area, and remove scrollbars.
1324 *
1324 *
1325 * @method expand_all_output
1325 * @method expand_all_output
1326 */
1326 */
1327 Notebook.prototype.expand_all_output = function () {
1327 Notebook.prototype.expand_all_output = function () {
1328 $.map(this.get_cells(), function (cell, i) {
1328 $.map(this.get_cells(), function (cell, i) {
1329 if (cell instanceof CodeCell) {
1329 if (cell instanceof CodeCell) {
1330 cell.expand_output();
1330 cell.expand_output();
1331 }
1331 }
1332 });
1332 });
1333 // this should not be set if the `collapse` key is removed from nbformat
1333 // this should not be set if the `collapse` key is removed from nbformat
1334 this.set_dirty(true);
1334 this.set_dirty(true);
1335 };
1335 };
1336
1336
1337 /**
1337 /**
1338 * Clear the selected CodeCell's output area.
1338 * Clear the selected CodeCell's output area.
1339 *
1339 *
1340 * @method clear_output
1340 * @method clear_output
1341 * @param {Number} index A cell's numeric index
1341 * @param {Number} index A cell's numeric index
1342 */
1342 */
1343 Notebook.prototype.clear_output = function (index) {
1343 Notebook.prototype.clear_output = function (index) {
1344 var i = this.index_or_selected(index);
1344 var i = this.index_or_selected(index);
1345 var cell = this.get_cell(i);
1345 var cell = this.get_cell(i);
1346 if (cell !== null && (cell instanceof CodeCell)) {
1346 if (cell !== null && (cell instanceof CodeCell)) {
1347 cell.clear_output();
1347 cell.clear_output();
1348 this.set_dirty(true);
1348 this.set_dirty(true);
1349 }
1349 }
1350 };
1350 };
1351
1351
1352 /**
1352 /**
1353 * Clear each code cell's output area.
1353 * Clear each code cell's output area.
1354 *
1354 *
1355 * @method clear_all_output
1355 * @method clear_all_output
1356 */
1356 */
1357 Notebook.prototype.clear_all_output = function () {
1357 Notebook.prototype.clear_all_output = function () {
1358 $.map(this.get_cells(), function (cell, i) {
1358 $.map(this.get_cells(), function (cell, i) {
1359 if (cell instanceof CodeCell) {
1359 if (cell instanceof CodeCell) {
1360 cell.clear_output();
1360 cell.clear_output();
1361 }
1361 }
1362 });
1362 });
1363 this.set_dirty(true);
1363 this.set_dirty(true);
1364 };
1364 };
1365
1365
1366 /**
1366 /**
1367 * Scroll the selected CodeCell's output area.
1367 * Scroll the selected CodeCell's output area.
1368 *
1368 *
1369 * @method scroll_output
1369 * @method scroll_output
1370 * @param {Number} index A cell's numeric index
1370 * @param {Number} index A cell's numeric index
1371 */
1371 */
1372 Notebook.prototype.scroll_output = function (index) {
1372 Notebook.prototype.scroll_output = function (index) {
1373 var i = this.index_or_selected(index);
1373 var i = this.index_or_selected(index);
1374 var cell = this.get_cell(i);
1374 var cell = this.get_cell(i);
1375 if (cell !== null && (cell instanceof CodeCell)) {
1375 if (cell !== null && (cell instanceof CodeCell)) {
1376 cell.scroll_output();
1376 cell.scroll_output();
1377 this.set_dirty(true);
1377 this.set_dirty(true);
1378 }
1378 }
1379 };
1379 };
1380
1380
1381 /**
1381 /**
1382 * Expand each code cell's output area, and add a scrollbar for long output.
1382 * Expand each code cell's output area, and add a scrollbar for long output.
1383 *
1383 *
1384 * @method scroll_all_output
1384 * @method scroll_all_output
1385 */
1385 */
1386 Notebook.prototype.scroll_all_output = function () {
1386 Notebook.prototype.scroll_all_output = function () {
1387 $.map(this.get_cells(), function (cell, i) {
1387 $.map(this.get_cells(), function (cell, i) {
1388 if (cell instanceof CodeCell) {
1388 if (cell instanceof CodeCell) {
1389 cell.scroll_output();
1389 cell.scroll_output();
1390 }
1390 }
1391 });
1391 });
1392 // this should not be set if the `collapse` key is removed from nbformat
1392 // this should not be set if the `collapse` key is removed from nbformat
1393 this.set_dirty(true);
1393 this.set_dirty(true);
1394 };
1394 };
1395
1395
1396 /** Toggle whether a cell's output is collapsed or expanded.
1396 /** Toggle whether a cell's output is collapsed or expanded.
1397 *
1397 *
1398 * @method toggle_output
1398 * @method toggle_output
1399 * @param {Number} index A cell's numeric index
1399 * @param {Number} index A cell's numeric index
1400 */
1400 */
1401 Notebook.prototype.toggle_output = function (index) {
1401 Notebook.prototype.toggle_output = function (index) {
1402 var i = this.index_or_selected(index);
1402 var i = this.index_or_selected(index);
1403 var cell = this.get_cell(i);
1403 var cell = this.get_cell(i);
1404 if (cell !== null && (cell instanceof CodeCell)) {
1404 if (cell !== null && (cell instanceof CodeCell)) {
1405 cell.toggle_output();
1405 cell.toggle_output();
1406 this.set_dirty(true);
1406 this.set_dirty(true);
1407 }
1407 }
1408 };
1408 };
1409
1409
1410 /**
1410 /**
1411 * Hide/show the output of all cells.
1411 * Hide/show the output of all cells.
1412 *
1412 *
1413 * @method toggle_all_output
1413 * @method toggle_all_output
1414 */
1414 */
1415 Notebook.prototype.toggle_all_output = function () {
1415 Notebook.prototype.toggle_all_output = function () {
1416 $.map(this.get_cells(), function (cell, i) {
1416 $.map(this.get_cells(), function (cell, i) {
1417 if (cell instanceof CodeCell) {
1417 if (cell instanceof CodeCell) {
1418 cell.toggle_output();
1418 cell.toggle_output();
1419 }
1419 }
1420 });
1420 });
1421 // this should not be set if the `collapse` key is removed from nbformat
1421 // this should not be set if the `collapse` key is removed from nbformat
1422 this.set_dirty(true);
1422 this.set_dirty(true);
1423 };
1423 };
1424
1424
1425 /**
1425 /**
1426 * Toggle a scrollbar for long cell outputs.
1426 * Toggle a scrollbar for long cell outputs.
1427 *
1427 *
1428 * @method toggle_output_scroll
1428 * @method toggle_output_scroll
1429 * @param {Number} index A cell's numeric index
1429 * @param {Number} index A cell's numeric index
1430 */
1430 */
1431 Notebook.prototype.toggle_output_scroll = function (index) {
1431 Notebook.prototype.toggle_output_scroll = function (index) {
1432 var i = this.index_or_selected(index);
1432 var i = this.index_or_selected(index);
1433 var cell = this.get_cell(i);
1433 var cell = this.get_cell(i);
1434 if (cell !== null && (cell instanceof CodeCell)) {
1434 if (cell !== null && (cell instanceof CodeCell)) {
1435 cell.toggle_output_scroll();
1435 cell.toggle_output_scroll();
1436 this.set_dirty(true);
1436 this.set_dirty(true);
1437 }
1437 }
1438 };
1438 };
1439
1439
1440 /**
1440 /**
1441 * Toggle the scrolling of long output on all cells.
1441 * Toggle the scrolling of long output on all cells.
1442 *
1442 *
1443 * @method toggle_all_output_scrolling
1443 * @method toggle_all_output_scrolling
1444 */
1444 */
1445 Notebook.prototype.toggle_all_output_scroll = function () {
1445 Notebook.prototype.toggle_all_output_scroll = function () {
1446 $.map(this.get_cells(), function (cell, i) {
1446 $.map(this.get_cells(), function (cell, i) {
1447 if (cell instanceof CodeCell) {
1447 if (cell instanceof CodeCell) {
1448 cell.toggle_output_scroll();
1448 cell.toggle_output_scroll();
1449 }
1449 }
1450 });
1450 });
1451 // this should not be set if the `collapse` key is removed from nbformat
1451 // this should not be set if the `collapse` key is removed from nbformat
1452 this.set_dirty(true);
1452 this.set_dirty(true);
1453 };
1453 };
1454
1454
1455 // Other cell functions: line numbers, ...
1455 // Other cell functions: line numbers, ...
1456
1456
1457 /**
1457 /**
1458 * Toggle line numbers in the selected cell's input area.
1458 * Toggle line numbers in the selected cell's input area.
1459 *
1459 *
1460 * @method cell_toggle_line_numbers
1460 * @method cell_toggle_line_numbers
1461 */
1461 */
1462 Notebook.prototype.cell_toggle_line_numbers = function() {
1462 Notebook.prototype.cell_toggle_line_numbers = function() {
1463 this.get_selected_cell().toggle_line_numbers();
1463 this.get_selected_cell().toggle_line_numbers();
1464 };
1464 };
1465
1465
1466 // Session related things
1466 // Session related things
1467
1467
1468 /**
1468 /**
1469 * Start a new session and set it on each code cell.
1469 * Start a new session and set it on each code cell.
1470 *
1470 *
1471 * @method start_session
1471 * @method start_session
1472 */
1472 */
1473 Notebook.prototype.start_session = function () {
1473 Notebook.prototype.start_session = function () {
1474 this.session = new Session(this, this.options);
1474 this.session = new Session(this, this.options);
1475 this.session.start($.proxy(this._session_started, this));
1475 this.session.start($.proxy(this._session_started, this));
1476 };
1476 };
1477
1477
1478
1478
1479 /**
1479 /**
1480 * Once a session is started, link the code cells to the kernel and pass the
1480 * Once a session is started, link the code cells to the kernel and pass the
1481 * comm manager to the widget manager
1481 * comm manager to the widget manager
1482 *
1482 *
1483 */
1483 */
1484 Notebook.prototype._session_started = function(){
1484 Notebook.prototype._session_started = function(){
1485 this.kernel = this.session.kernel;
1485 this.kernel = this.session.kernel;
1486 var ncells = this.ncells();
1486 var ncells = this.ncells();
1487 for (var i=0; i<ncells; i++) {
1487 for (var i=0; i<ncells; i++) {
1488 var cell = this.get_cell(i);
1488 var cell = this.get_cell(i);
1489 if (cell instanceof CodeCell) {
1489 if (cell instanceof CodeCell) {
1490 cell.set_kernel(this.session.kernel);
1490 cell.set_kernel(this.session.kernel);
1491 }
1491 }
1492 }
1492 }
1493 };
1493 };
1494
1494
1495 /**
1495 /**
1496 * Prompt the user to restart the IPython kernel.
1496 * Prompt the user to restart the IPython kernel.
1497 *
1497 *
1498 * @method restart_kernel
1498 * @method restart_kernel
1499 */
1499 */
1500 Notebook.prototype.restart_kernel = function () {
1500 Notebook.prototype.restart_kernel = function () {
1501 var that = this;
1501 var that = this;
1502 Dialog.modal({
1502 Dialog.modal({
1503 title : "Restart kernel or continue running?",
1503 title : "Restart kernel or continue running?",
1504 body : $("<p/>").text(
1504 body : $("<p/>").text(
1505 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1505 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1506 ),
1506 ),
1507 buttons : {
1507 buttons : {
1508 "Continue running" : {},
1508 "Continue running" : {},
1509 "Restart" : {
1509 "Restart" : {
1510 "class" : "btn-danger",
1510 "class" : "btn-danger",
1511 "click" : function() {
1511 "click" : function() {
1512 that.session.restart_kernel();
1512 that.session.restart_kernel();
1513 }
1513 }
1514 }
1514 }
1515 }
1515 }
1516 });
1516 });
1517 };
1517 };
1518
1518
1519 /**
1519 /**
1520 * Execute or render cell outputs and go into command mode.
1520 * Execute or render cell outputs and go into command mode.
1521 *
1521 *
1522 * @method execute_cell
1522 * @method execute_cell
1523 */
1523 */
1524 Notebook.prototype.execute_cell = function () {
1524 Notebook.prototype.execute_cell = function () {
1525 // mode = shift, ctrl, alt
1525 // mode = shift, ctrl, alt
1526 var cell = this.get_selected_cell();
1526 var cell = this.get_selected_cell();
1527 var cell_index = this.find_cell_index(cell);
1527 var cell_index = this.find_cell_index(cell);
1528
1528
1529 cell.execute();
1529 cell.execute();
1530 this.command_mode();
1530 this.command_mode();
1531 this.set_dirty(true);
1531 this.set_dirty(true);
1532 };
1532 };
1533
1533
1534 /**
1534 /**
1535 * Execute or render cell outputs and insert a new cell below.
1535 * Execute or render cell outputs and insert a new cell below.
1536 *
1536 *
1537 * @method execute_cell_and_insert_below
1537 * @method execute_cell_and_insert_below
1538 */
1538 */
1539 Notebook.prototype.execute_cell_and_insert_below = function () {
1539 Notebook.prototype.execute_cell_and_insert_below = function () {
1540 var cell = this.get_selected_cell();
1540 var cell = this.get_selected_cell();
1541 var cell_index = this.find_cell_index(cell);
1541 var cell_index = this.find_cell_index(cell);
1542
1542
1543 cell.execute();
1543 cell.execute();
1544
1544
1545 // If we are at the end always insert a new cell and return
1545 // If we are at the end always insert a new cell and return
1546 if (cell_index === (this.ncells()-1)) {
1546 if (cell_index === (this.ncells()-1)) {
1547 this.command_mode();
1547 this.command_mode();
1548 this.insert_cell_below();
1548 this.insert_cell_below();
1549 this.select(cell_index+1);
1549 this.select(cell_index+1);
1550 this.edit_mode();
1550 this.edit_mode();
1551 this.scroll_to_bottom();
1551 this.scroll_to_bottom();
1552 this.set_dirty(true);
1552 this.set_dirty(true);
1553 return;
1553 return;
1554 }
1554 }
1555
1555
1556 this.command_mode();
1556 this.command_mode();
1557 this.insert_cell_below();
1557 this.insert_cell_below();
1558 this.select(cell_index+1);
1558 this.select(cell_index+1);
1559 this.edit_mode();
1559 this.edit_mode();
1560 this.set_dirty(true);
1560 this.set_dirty(true);
1561 };
1561 };
1562
1562
1563 /**
1563 /**
1564 * Execute or render cell outputs and select the next cell.
1564 * Execute or render cell outputs and select the next cell.
1565 *
1565 *
1566 * @method execute_cell_and_select_below
1566 * @method execute_cell_and_select_below
1567 */
1567 */
1568 Notebook.prototype.execute_cell_and_select_below = function () {
1568 Notebook.prototype.execute_cell_and_select_below = function () {
1569
1569
1570 var cell = this.get_selected_cell();
1570 var cell = this.get_selected_cell();
1571 var cell_index = this.find_cell_index(cell);
1571 var cell_index = this.find_cell_index(cell);
1572
1572
1573 cell.execute();
1573 cell.execute();
1574
1574
1575 // If we are at the end always insert a new cell and return
1575 // If we are at the end always insert a new cell and return
1576 if (cell_index === (this.ncells()-1)) {
1576 if (cell_index === (this.ncells()-1)) {
1577 this.command_mode();
1577 this.command_mode();
1578 this.insert_cell_below();
1578 this.insert_cell_below();
1579 this.select(cell_index+1);
1579 this.select(cell_index+1);
1580 this.edit_mode();
1580 this.edit_mode();
1581 this.scroll_to_bottom();
1581 this.scroll_to_bottom();
1582 this.set_dirty(true);
1582 this.set_dirty(true);
1583 return;
1583 return;
1584 }
1584 }
1585
1585
1586 this.command_mode();
1586 this.command_mode();
1587 this.select(cell_index+1);
1587 this.select(cell_index+1);
1588 this.focus_cell();
1588 this.focus_cell();
1589 this.set_dirty(true);
1589 this.set_dirty(true);
1590 };
1590 };
1591
1591
1592 /**
1592 /**
1593 * Execute all cells below the selected cell.
1593 * Execute all cells below the selected cell.
1594 *
1594 *
1595 * @method execute_cells_below
1595 * @method execute_cells_below
1596 */
1596 */
1597 Notebook.prototype.execute_cells_below = function () {
1597 Notebook.prototype.execute_cells_below = function () {
1598 this.execute_cell_range(this.get_selected_index(), this.ncells());
1598 this.execute_cell_range(this.get_selected_index(), this.ncells());
1599 this.scroll_to_bottom();
1599 this.scroll_to_bottom();
1600 };
1600 };
1601
1601
1602 /**
1602 /**
1603 * Execute all cells above the selected cell.
1603 * Execute all cells above the selected cell.
1604 *
1604 *
1605 * @method execute_cells_above
1605 * @method execute_cells_above
1606 */
1606 */
1607 Notebook.prototype.execute_cells_above = function () {
1607 Notebook.prototype.execute_cells_above = function () {
1608 this.execute_cell_range(0, this.get_selected_index());
1608 this.execute_cell_range(0, this.get_selected_index());
1609 };
1609 };
1610
1610
1611 /**
1611 /**
1612 * Execute all cells.
1612 * Execute all cells.
1613 *
1613 *
1614 * @method execute_all_cells
1614 * @method execute_all_cells
1615 */
1615 */
1616 Notebook.prototype.execute_all_cells = function () {
1616 Notebook.prototype.execute_all_cells = function () {
1617 this.execute_cell_range(0, this.ncells());
1617 this.execute_cell_range(0, this.ncells());
1618 this.scroll_to_bottom();
1618 this.scroll_to_bottom();
1619 };
1619 };
1620
1620
1621 /**
1621 /**
1622 * Execute a contiguous range of cells.
1622 * Execute a contiguous range of cells.
1623 *
1623 *
1624 * @method execute_cell_range
1624 * @method execute_cell_range
1625 * @param {Number} start Index of the first cell to execute (inclusive)
1625 * @param {Number} start Index of the first cell to execute (inclusive)
1626 * @param {Number} end Index of the last cell to execute (exclusive)
1626 * @param {Number} end Index of the last cell to execute (exclusive)
1627 */
1627 */
1628 Notebook.prototype.execute_cell_range = function (start, end) {
1628 Notebook.prototype.execute_cell_range = function (start, end) {
1629 this.command_mode();
1629 this.command_mode();
1630 for (var i=start; i<end; i++) {
1630 for (var i=start; i<end; i++) {
1631 this.select(i);
1631 this.select(i);
1632 this.execute_cell();
1632 this.execute_cell();
1633 }
1633 }
1634 };
1634 };
1635
1635
1636 // Persistance and loading
1636 // Persistance and loading
1637
1637
1638 /**
1638 /**
1639 * Getter method for this notebook's name.
1639 * Getter method for this notebook's name.
1640 *
1640 *
1641 * @method get_notebook_name
1641 * @method get_notebook_name
1642 * @return {String} This notebook's name (excluding file extension)
1642 * @return {String} This notebook's name (excluding file extension)
1643 */
1643 */
1644 Notebook.prototype.get_notebook_name = function () {
1644 Notebook.prototype.get_notebook_name = function () {
1645 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1645 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1646 return nbname;
1646 return nbname;
1647 };
1647 };
1648
1648
1649 /**
1649 /**
1650 * Setter method for this notebook's name.
1650 * Setter method for this notebook's name.
1651 *
1651 *
1652 * @method set_notebook_name
1652 * @method set_notebook_name
1653 * @param {String} name A new name for this notebook
1653 * @param {String} name A new name for this notebook
1654 */
1654 */
1655 Notebook.prototype.set_notebook_name = function (name) {
1655 Notebook.prototype.set_notebook_name = function (name) {
1656 this.notebook_name = name;
1656 this.notebook_name = name;
1657 };
1657 };
1658
1658
1659 /**
1659 /**
1660 * Check that a notebook's name is valid.
1660 * Check that a notebook's name is valid.
1661 *
1661 *
1662 * @method test_notebook_name
1662 * @method test_notebook_name
1663 * @param {String} nbname A name for this notebook
1663 * @param {String} nbname A name for this notebook
1664 * @return {Boolean} True if the name is valid, false if invalid
1664 * @return {Boolean} True if the name is valid, false if invalid
1665 */
1665 */
1666 Notebook.prototype.test_notebook_name = function (nbname) {
1666 Notebook.prototype.test_notebook_name = function (nbname) {
1667 nbname = nbname || '';
1667 nbname = nbname || '';
1668 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1668 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1669 return true;
1669 return true;
1670 } else {
1670 } else {
1671 return false;
1671 return false;
1672 }
1672 }
1673 };
1673 };
1674
1674
1675 /**
1675 /**
1676 * Load a notebook from JSON (.ipynb).
1676 * Load a notebook from JSON (.ipynb).
1677 *
1677 *
1678 * This currently handles one worksheet: others are deleted.
1678 * This currently handles one worksheet: others are deleted.
1679 *
1679 *
1680 * @method fromJSON
1680 * @method fromJSON
1681 * @param {Object} data JSON representation of a notebook
1681 * @param {Object} data JSON representation of a notebook
1682 */
1682 */
1683 Notebook.prototype.fromJSON = function (data) {
1683 Notebook.prototype.fromJSON = function (data) {
1684 var content = data.content;
1684 var content = data.content;
1685 var ncells = this.ncells();
1685 var ncells = this.ncells();
1686 var i;
1686 var i;
1687 for (i=0; i<ncells; i++) {
1687 for (i=0; i<ncells; i++) {
1688 // Always delete cell 0 as they get renumbered as they are deleted.
1688 // Always delete cell 0 as they get renumbered as they are deleted.
1689 this.delete_cell(0);
1689 this.delete_cell(0);
1690 }
1690 }
1691 // Save the metadata and name.
1691 // Save the metadata and name.
1692 this.metadata = content.metadata;
1692 this.metadata = content.metadata;
1693 this.notebook_name = data.name;
1693 this.notebook_name = data.name;
1694 var trusted = true;
1694 var trusted = true;
1695 // Only handle 1 worksheet for now.
1695 // Only handle 1 worksheet for now.
1696 var worksheet = content.worksheets[0];
1696 var worksheet = content.worksheets[0];
1697 if (worksheet !== undefined) {
1697 if (worksheet !== undefined) {
1698 if (worksheet.metadata) {
1698 if (worksheet.metadata) {
1699 this.worksheet_metadata = worksheet.metadata;
1699 this.worksheet_metadata = worksheet.metadata;
1700 }
1700 }
1701 var new_cells = worksheet.cells;
1701 var new_cells = worksheet.cells;
1702 ncells = new_cells.length;
1702 ncells = new_cells.length;
1703 var cell_data = null;
1703 var cell_data = null;
1704 var new_cell = null;
1704 var new_cell = null;
1705 for (i=0; i<ncells; i++) {
1705 for (i=0; i<ncells; i++) {
1706 cell_data = new_cells[i];
1706 cell_data = new_cells[i];
1707 // VERSIONHACK: plaintext -> raw
1707 // VERSIONHACK: plaintext -> raw
1708 // handle never-released plaintext name for raw cells
1708 // handle never-released plaintext name for raw cells
1709 if (cell_data.cell_type === 'plaintext'){
1709 if (cell_data.cell_type === 'plaintext'){
1710 cell_data.cell_type = 'raw';
1710 cell_data.cell_type = 'raw';
1711 }
1711 }
1712
1712
1713 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1713 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1714 new_cell.fromJSON(cell_data);
1714 new_cell.fromJSON(cell_data);
1715 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1715 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1716 trusted = false;
1716 trusted = false;
1717 }
1717 }
1718 }
1718 }
1719 }
1719 }
1720 if (trusted != this.trusted) {
1720 if (trusted != this.trusted) {
1721 this.trusted = trusted;
1721 this.trusted = trusted;
1722 this.events.trigger("trust_changed.Notebook", trusted);
1722 this.events.trigger("trust_changed.Notebook", trusted);
1723 }
1723 }
1724 if (content.worksheets.length > 1) {
1724 if (content.worksheets.length > 1) {
1725 Dialog.modal({
1725 Dialog.modal({
1726 title : "Multiple worksheets",
1726 title : "Multiple worksheets",
1727 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1727 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1728 "but this version of IPython can only handle the first. " +
1728 "but this version of IPython can only handle the first. " +
1729 "If you save this notebook, worksheets after the first will be lost.",
1729 "If you save this notebook, worksheets after the first will be lost.",
1730 buttons : {
1730 buttons : {
1731 OK : {
1731 OK : {
1732 class : "btn-danger"
1732 class : "btn-danger"
1733 }
1733 }
1734 }
1734 }
1735 });
1735 });
1736 }
1736 }
1737 };
1737 };
1738
1738
1739 /**
1739 /**
1740 * Dump this notebook into a JSON-friendly object.
1740 * Dump this notebook into a JSON-friendly object.
1741 *
1741 *
1742 * @method toJSON
1742 * @method toJSON
1743 * @return {Object} A JSON-friendly representation of this notebook.
1743 * @return {Object} A JSON-friendly representation of this notebook.
1744 */
1744 */
1745 Notebook.prototype.toJSON = function () {
1745 Notebook.prototype.toJSON = function () {
1746 var cells = this.get_cells();
1746 var cells = this.get_cells();
1747 var ncells = cells.length;
1747 var ncells = cells.length;
1748 var cell_array = new Array(ncells);
1748 var cell_array = new Array(ncells);
1749 var trusted = true;
1749 var trusted = true;
1750 for (var i=0; i<ncells; i++) {
1750 for (var i=0; i<ncells; i++) {
1751 var cell = cells[i];
1751 var cell = cells[i];
1752 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1752 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1753 trusted = false;
1753 trusted = false;
1754 }
1754 }
1755 cell_array[i] = cell.toJSON();
1755 cell_array[i] = cell.toJSON();
1756 }
1756 }
1757 var data = {
1757 var data = {
1758 // Only handle 1 worksheet for now.
1758 // Only handle 1 worksheet for now.
1759 worksheets : [{
1759 worksheets : [{
1760 cells: cell_array,
1760 cells: cell_array,
1761 metadata: this.worksheet_metadata
1761 metadata: this.worksheet_metadata
1762 }],
1762 }],
1763 metadata : this.metadata
1763 metadata : this.metadata
1764 };
1764 };
1765 if (trusted != this.trusted) {
1765 if (trusted != this.trusted) {
1766 this.trusted = trusted;
1766 this.trusted = trusted;
1767 this.events.trigger("trust_changed.Notebook", trusted);
1767 this.events.trigger("trust_changed.Notebook", trusted);
1768 }
1768 }
1769 return data;
1769 return data;
1770 };
1770 };
1771
1771
1772 /**
1772 /**
1773 * Start an autosave timer, for periodically saving the notebook.
1773 * Start an autosave timer, for periodically saving the notebook.
1774 *
1774 *
1775 * @method set_autosave_interval
1775 * @method set_autosave_interval
1776 * @param {Integer} interval the autosave interval in milliseconds
1776 * @param {Integer} interval the autosave interval in milliseconds
1777 */
1777 */
1778 Notebook.prototype.set_autosave_interval = function (interval) {
1778 Notebook.prototype.set_autosave_interval = function (interval) {
1779 var that = this;
1779 var that = this;
1780 // clear previous interval, so we don't get simultaneous timers
1780 // clear previous interval, so we don't get simultaneous timers
1781 if (this.autosave_timer) {
1781 if (this.autosave_timer) {
1782 clearInterval(this.autosave_timer);
1782 clearInterval(this.autosave_timer);
1783 }
1783 }
1784
1784
1785 this.autosave_interval = this.minimum_autosave_interval = interval;
1785 this.autosave_interval = this.minimum_autosave_interval = interval;
1786 if (interval) {
1786 if (interval) {
1787 this.autosave_timer = setInterval(function() {
1787 this.autosave_timer = setInterval(function() {
1788 if (that.dirty) {
1788 if (that.dirty) {
1789 that.save_notebook();
1789 that.save_notebook();
1790 }
1790 }
1791 }, interval);
1791 }, interval);
1792 this.events.trigger("autosave_enabled.Notebook", interval);
1792 this.events.trigger("autosave_enabled.Notebook", interval);
1793 } else {
1793 } else {
1794 this.autosave_timer = null;
1794 this.autosave_timer = null;
1795 this.events.trigger("autosave_disabled.Notebook");
1795 this.events.trigger("autosave_disabled.Notebook");
1796 }
1796 }
1797 };
1797 };
1798
1798
1799 /**
1799 /**
1800 * Save this notebook on the server. This becomes a notebook instance's
1800 * Save this notebook on the server. This becomes a notebook instance's
1801 * .save_notebook method *after* the entire notebook has been loaded.
1801 * .save_notebook method *after* the entire notebook has been loaded.
1802 *
1802 *
1803 * @method save_notebook
1803 * @method save_notebook
1804 */
1804 */
1805 Notebook.prototype.save_notebook = function (extra_settings) {
1805 Notebook.prototype.save_notebook = function (extra_settings) {
1806 // Create a JSON model to be sent to the server.
1806 // Create a JSON model to be sent to the server.
1807 var model = {};
1807 var model = {};
1808 model.name = this.notebook_name;
1808 model.name = this.notebook_name;
1809 model.path = this.notebook_path;
1809 model.path = this.notebook_path;
1810 model.content = this.toJSON();
1810 model.content = this.toJSON();
1811 model.content.nbformat = this.nbformat;
1811 model.content.nbformat = this.nbformat;
1812 model.content.nbformat_minor = this.nbformat_minor;
1812 model.content.nbformat_minor = this.nbformat_minor;
1813 // time the ajax call for autosave tuning purposes.
1813 // time the ajax call for autosave tuning purposes.
1814 var start = new Date().getTime();
1814 var start = new Date().getTime();
1815 // We do the call with settings so we can set cache to false.
1815 // We do the call with settings so we can set cache to false.
1816 var settings = {
1816 var settings = {
1817 processData : false,
1817 processData : false,
1818 cache : false,
1818 cache : false,
1819 type : "PUT",
1819 type : "PUT",
1820 data : JSON.stringify(model),
1820 data : JSON.stringify(model),
1821 headers : {'Content-Type': 'application/json'},
1821 headers : {'Content-Type': 'application/json'},
1822 success : $.proxy(this.save_notebook_success, this, start),
1822 success : $.proxy(this.save_notebook_success, this, start),
1823 error : $.proxy(this.save_notebook_error, this)
1823 error : $.proxy(this.save_notebook_error, this)
1824 };
1824 };
1825 if (extra_settings) {
1825 if (extra_settings) {
1826 for (var key in extra_settings) {
1826 for (var key in extra_settings) {
1827 settings[key] = extra_settings[key];
1827 settings[key] = extra_settings[key];
1828 }
1828 }
1829 }
1829 }
1830 this.events.trigger('notebook_saving.Notebook');
1830 this.events.trigger('notebook_saving.Notebook');
1831 var url = utils.url_join_encode(
1831 var url = utils.url_join_encode(
1832 this.base_url,
1832 this.base_url,
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 saving a notebook.
1841 * Success callback for saving a notebook.
1842 *
1842 *
1843 * @method save_notebook_success
1843 * @method save_notebook_success
1844 * @param {Integer} start the time when the save request started
1844 * @param {Integer} start the time when the save request started
1845 * @param {Object} data JSON representation of a notebook
1845 * @param {Object} data JSON representation of a notebook
1846 * @param {String} status Description of response status
1846 * @param {String} status Description of response status
1847 * @param {jqXHR} xhr jQuery Ajax object
1847 * @param {jqXHR} xhr jQuery Ajax object
1848 */
1848 */
1849 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1849 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1850 this.set_dirty(false);
1850 this.set_dirty(false);
1851 this.events.trigger('notebook_saved.Notebook');
1851 this.events.trigger('notebook_saved.Notebook');
1852 this._update_autosave_interval(start);
1852 this._update_autosave_interval(start);
1853 if (this._checkpoint_after_save) {
1853 if (this._checkpoint_after_save) {
1854 this.create_checkpoint();
1854 this.create_checkpoint();
1855 this._checkpoint_after_save = false;
1855 this._checkpoint_after_save = false;
1856 }
1856 }
1857 };
1857 };
1858
1858
1859 /**
1859 /**
1860 * update the autosave interval based on how long the last save took
1860 * update the autosave interval based on how long the last save took
1861 *
1861 *
1862 * @method _update_autosave_interval
1862 * @method _update_autosave_interval
1863 * @param {Integer} timestamp when the save request started
1863 * @param {Integer} timestamp when the save request started
1864 */
1864 */
1865 Notebook.prototype._update_autosave_interval = function (start) {
1865 Notebook.prototype._update_autosave_interval = function (start) {
1866 var duration = (new Date().getTime() - start);
1866 var duration = (new Date().getTime() - start);
1867 if (this.autosave_interval) {
1867 if (this.autosave_interval) {
1868 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1868 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1869 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1869 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1870 // round to 10 seconds, otherwise we will be setting a new interval too often
1870 // round to 10 seconds, otherwise we will be setting a new interval too often
1871 interval = 10000 * Math.round(interval / 10000);
1871 interval = 10000 * Math.round(interval / 10000);
1872 // set new interval, if it's changed
1872 // set new interval, if it's changed
1873 if (interval != this.autosave_interval) {
1873 if (interval != this.autosave_interval) {
1874 this.set_autosave_interval(interval);
1874 this.set_autosave_interval(interval);
1875 }
1875 }
1876 }
1876 }
1877 };
1877 };
1878
1878
1879 /**
1879 /**
1880 * Failure callback for saving a notebook.
1880 * Failure callback for saving a notebook.
1881 *
1881 *
1882 * @method save_notebook_error
1882 * @method save_notebook_error
1883 * @param {jqXHR} xhr jQuery Ajax object
1883 * @param {jqXHR} xhr jQuery Ajax object
1884 * @param {String} status Description of response status
1884 * @param {String} status Description of response status
1885 * @param {String} error HTTP error message
1885 * @param {String} error HTTP error message
1886 */
1886 */
1887 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1887 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1888 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1888 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1889 };
1889 };
1890
1890
1891 /**
1891 /**
1892 * Explicitly trust the output of this notebook.
1892 * Explicitly trust the output of this notebook.
1893 *
1893 *
1894 * @method trust_notebook
1894 * @method trust_notebook
1895 */
1895 */
1896 Notebook.prototype.trust_notebook = function (extra_settings) {
1896 Notebook.prototype.trust_notebook = function (extra_settings) {
1897 var body = $("<div>").append($("<p>")
1897 var body = $("<div>").append($("<p>")
1898 .text("A trusted IPython notebook may execute hidden malicious code ")
1898 .text("A trusted IPython notebook may execute hidden malicious code ")
1899 .append($("<strong>")
1899 .append($("<strong>")
1900 .append(
1900 .append(
1901 $("<em>").text("when you open it")
1901 $("<em>").text("when you open it")
1902 )
1902 )
1903 ).append(".").append(
1903 ).append(".").append(
1904 " Selecting trust will immediately reload this notebook in a trusted state."
1904 " Selecting trust will immediately reload this notebook in a trusted state."
1905 ).append(
1905 ).append(
1906 " For more information, see the "
1906 " For more information, see the "
1907 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1907 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1908 .text("IPython security documentation")
1908 .text("IPython security documentation")
1909 ).append(".")
1909 ).append(".")
1910 );
1910 );
1911
1911
1912 var nb = this;
1912 var nb = this;
1913 Dialog.modal({
1913 Dialog.modal({
1914 title: "Trust this notebook?",
1914 title: "Trust this notebook?",
1915 body: body,
1915 body: body,
1916
1916
1917 buttons: {
1917 buttons: {
1918 Cancel : {},
1918 Cancel : {},
1919 Trust : {
1919 Trust : {
1920 class : "btn-danger",
1920 class : "btn-danger",
1921 click : function () {
1921 click : function () {
1922 var cells = nb.get_cells();
1922 var cells = nb.get_cells();
1923 for (var i = 0; i < cells.length; i++) {
1923 for (var i = 0; i < cells.length; i++) {
1924 var cell = cells[i];
1924 var cell = cells[i];
1925 if (cell.cell_type == 'code') {
1925 if (cell.cell_type == 'code') {
1926 cell.output_area.trusted = true;
1926 cell.output_area.trusted = true;
1927 }
1927 }
1928 }
1928 }
1929 this.events.on('notebook_saved.Notebook', function () {
1929 this.events.on('notebook_saved.Notebook', function () {
1930 window.location.reload();
1930 window.location.reload();
1931 });
1931 });
1932 nb.save_notebook();
1932 nb.save_notebook();
1933 }
1933 }
1934 }
1934 }
1935 }
1935 }
1936 });
1936 });
1937 };
1937 };
1938
1938
1939 Notebook.prototype.new_notebook = function(){
1939 Notebook.prototype.new_notebook = function(){
1940 var path = this.notebook_path;
1940 var path = this.notebook_path;
1941 var base_url = this.base_url;
1941 var base_url = this.base_url;
1942 var settings = {
1942 var settings = {
1943 processData : false,
1943 processData : false,
1944 cache : false,
1944 cache : false,
1945 type : "POST",
1945 type : "POST",
1946 dataType : "json",
1946 dataType : "json",
1947 async : false,
1947 async : false,
1948 success : function (data, status, xhr){
1948 success : function (data, status, xhr){
1949 var notebook_name = data.name;
1949 var notebook_name = data.name;
1950 window.open(
1950 window.open(
1951 utils.url_join_encode(
1951 utils.url_join_encode(
1952 base_url,
1952 base_url,
1953 'notebooks',
1953 'notebooks',
1954 path,
1954 path,
1955 notebook_name
1955 notebook_name
1956 ),
1956 ),
1957 '_blank'
1957 '_blank'
1958 );
1958 );
1959 },
1959 },
1960 error : utils.log_ajax_error,
1960 error : utils.log_ajax_error,
1961 };
1961 };
1962 var url = utils.url_join_encode(
1962 var url = utils.url_join_encode(
1963 base_url,
1963 base_url,
1964 'api/notebooks',
1964 'api/notebooks',
1965 path
1965 path
1966 );
1966 );
1967 $.ajax(url,settings);
1967 $.ajax(url,settings);
1968 };
1968 };
1969
1969
1970
1970
1971 Notebook.prototype.copy_notebook = function(){
1971 Notebook.prototype.copy_notebook = function(){
1972 var path = this.notebook_path;
1972 var path = this.notebook_path;
1973 var base_url = this.base_url;
1973 var base_url = this.base_url;
1974 var settings = {
1974 var settings = {
1975 processData : false,
1975 processData : false,
1976 cache : false,
1976 cache : false,
1977 type : "POST",
1977 type : "POST",
1978 dataType : "json",
1978 dataType : "json",
1979 data : JSON.stringify({copy_from : this.notebook_name}),
1979 data : JSON.stringify({copy_from : this.notebook_name}),
1980 async : false,
1980 async : false,
1981 success : function (data, status, xhr) {
1981 success : function (data, status, xhr) {
1982 window.open(utils.url_join_encode(
1982 window.open(utils.url_join_encode(
1983 base_url,
1983 base_url,
1984 'notebooks',
1984 'notebooks',
1985 data.path,
1985 data.path,
1986 data.name
1986 data.name
1987 ), '_blank');
1987 ), '_blank');
1988 },
1988 },
1989 error : utils.log_ajax_error,
1989 error : utils.log_ajax_error,
1990 };
1990 };
1991 var url = utils.url_join_encode(
1991 var url = utils.url_join_encode(
1992 base_url,
1992 base_url,
1993 'api/notebooks',
1993 'api/notebooks',
1994 path
1994 path
1995 );
1995 );
1996 $.ajax(url,settings);
1996 $.ajax(url,settings);
1997 };
1997 };
1998
1998
1999 Notebook.prototype.rename = function (nbname) {
1999 Notebook.prototype.rename = function (nbname) {
2000 var that = this;
2000 var that = this;
2001 if (!nbname.match(/\.ipynb$/)) {
2001 if (!nbname.match(/\.ipynb$/)) {
2002 nbname = nbname + ".ipynb";
2002 nbname = nbname + ".ipynb";
2003 }
2003 }
2004 var data = {name: nbname};
2004 var data = {name: nbname};
2005 var settings = {
2005 var settings = {
2006 processData : false,
2006 processData : false,
2007 cache : false,
2007 cache : false,
2008 type : "PATCH",
2008 type : "PATCH",
2009 data : JSON.stringify(data),
2009 data : JSON.stringify(data),
2010 dataType: "json",
2010 dataType: "json",
2011 headers : {'Content-Type': 'application/json'},
2011 headers : {'Content-Type': 'application/json'},
2012 success : $.proxy(that.rename_success, this),
2012 success : $.proxy(that.rename_success, this),
2013 error : $.proxy(that.rename_error, this)
2013 error : $.proxy(that.rename_error, this)
2014 };
2014 };
2015 this.events.trigger('rename_notebook.Notebook', data);
2015 this.events.trigger('rename_notebook.Notebook', data);
2016 var url = utils.url_join_encode(
2016 var url = utils.url_join_encode(
2017 this.base_url,
2017 this.base_url,
2018 'api/notebooks',
2018 'api/notebooks',
2019 this.notebook_path,
2019 this.notebook_path,
2020 this.notebook_name
2020 this.notebook_name
2021 );
2021 );
2022 $.ajax(url, settings);
2022 $.ajax(url, settings);
2023 };
2023 };
2024
2024
2025 Notebook.prototype.delete = function () {
2025 Notebook.prototype.delete = function () {
2026 var that = this;
2026 var that = this;
2027 var settings = {
2027 var settings = {
2028 processData : false,
2028 processData : false,
2029 cache : false,
2029 cache : false,
2030 type : "DELETE",
2030 type : "DELETE",
2031 dataType: "json",
2031 dataType: "json",
2032 error : utils.log_ajax_error,
2032 error : utils.log_ajax_error,
2033 };
2033 };
2034 var url = utils.url_join_encode(
2034 var url = utils.url_join_encode(
2035 this.base_url,
2035 this.base_url,
2036 'api/notebooks',
2036 'api/notebooks',
2037 this.notebook_path,
2037 this.notebook_path,
2038 this.notebook_name
2038 this.notebook_name
2039 );
2039 );
2040 $.ajax(url, settings);
2040 $.ajax(url, settings);
2041 };
2041 };
2042
2042
2043
2043
2044 Notebook.prototype.rename_success = function (json, status, xhr) {
2044 Notebook.prototype.rename_success = function (json, status, xhr) {
2045 var name = this.notebook_name = json.name;
2045 var name = this.notebook_name = json.name;
2046 var path = json.path;
2046 var path = json.path;
2047 this.session.rename_notebook(name, path);
2047 this.session.rename_notebook(name, path);
2048 this.events.trigger('notebook_renamed.Notebook', json);
2048 this.events.trigger('notebook_renamed.Notebook', json);
2049 };
2049 };
2050
2050
2051 Notebook.prototype.rename_error = function (xhr, status, error) {
2051 Notebook.prototype.rename_error = function (xhr, status, error) {
2052 var that = this;
2052 var that = this;
2053 var dialog = $('<div/>').append(
2053 var dialog = $('<div/>').append(
2054 $("<p/>").addClass("rename-message")
2054 $("<p/>").addClass("rename-message")
2055 .text('This notebook name already exists.')
2055 .text('This notebook name already exists.')
2056 );
2056 );
2057 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2057 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2058 Dialog.modal({
2058 Dialog.modal({
2059 title: "Notebook Rename Error!",
2059 title: "Notebook Rename Error!",
2060 body: dialog,
2060 body: dialog,
2061 buttons : {
2061 buttons : {
2062 "Cancel": {},
2062 "Cancel": {},
2063 "OK": {
2063 "OK": {
2064 class: "btn-primary",
2064 class: "btn-primary",
2065 click: function () {
2065 click: function () {
2066 this.save_widget.rename_notebook();
2066 this.save_widget.rename_notebook();
2067 }}
2067 }}
2068 },
2068 },
2069 open : function (event, ui) {
2069 open : function (event, ui) {
2070 var that = $(this);
2070 var that = $(this);
2071 // Upon ENTER, click the OK button.
2071 // Upon ENTER, click the OK button.
2072 that.find('input[type="text"]').keydown(function (event, ui) {
2072 that.find('input[type="text"]').keydown(function (event, ui) {
2073 if (event.which === this.keyboard.keycodes.enter) {
2073 if (event.which === this.keyboard.keycodes.enter) {
2074 that.find('.btn-primary').first().click();
2074 that.find('.btn-primary').first().click();
2075 }
2075 }
2076 });
2076 });
2077 that.find('input[type="text"]').focus();
2077 that.find('input[type="text"]').focus();
2078 }
2078 }
2079 });
2079 });
2080 };
2080 };
2081
2081
2082 /**
2082 /**
2083 * Request a notebook's data from the server.
2083 * Request a notebook's data from the server.
2084 *
2084 *
2085 * @method load_notebook
2085 * @method load_notebook
2086 * @param {String} notebook_name and path A notebook to load
2086 * @param {String} notebook_name and path A notebook to load
2087 */
2087 */
2088 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2088 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2089 var that = this;
2089 var that = this;
2090 this.notebook_name = notebook_name;
2090 this.notebook_name = notebook_name;
2091 this.notebook_path = notebook_path;
2091 this.notebook_path = notebook_path;
2092 // We do the call with settings so we can set cache to false.
2092 // We do the call with settings so we can set cache to false.
2093 var settings = {
2093 var settings = {
2094 processData : false,
2094 processData : false,
2095 cache : false,
2095 cache : false,
2096 type : "GET",
2096 type : "GET",
2097 dataType : "json",
2097 dataType : "json",
2098 success : $.proxy(this.load_notebook_success,this),
2098 success : $.proxy(this.load_notebook_success,this),
2099 error : $.proxy(this.load_notebook_error,this),
2099 error : $.proxy(this.load_notebook_error,this),
2100 };
2100 };
2101 this.events.trigger('notebook_loading.Notebook');
2101 this.events.trigger('notebook_loading.Notebook');
2102 var url = utils.url_join_encode(
2102 var url = utils.url_join_encode(
2103 this.base_url,
2103 this.base_url,
2104 'api/notebooks',
2104 'api/notebooks',
2105 this.notebook_path,
2105 this.notebook_path,
2106 this.notebook_name
2106 this.notebook_name
2107 );
2107 );
2108 $.ajax(url, settings);
2108 $.ajax(url, settings);
2109 };
2109 };
2110
2110
2111 /**
2111 /**
2112 * Success callback for loading a notebook from the server.
2112 * Success callback for loading a notebook from the server.
2113 *
2113 *
2114 * Load notebook data from the JSON response.
2114 * Load notebook data from the JSON response.
2115 *
2115 *
2116 * @method load_notebook_success
2116 * @method load_notebook_success
2117 * @param {Object} data JSON representation of a notebook
2117 * @param {Object} data JSON representation of a notebook
2118 * @param {String} status Description of response status
2118 * @param {String} status Description of response status
2119 * @param {jqXHR} xhr jQuery Ajax object
2119 * @param {jqXHR} xhr jQuery Ajax object
2120 */
2120 */
2121 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2121 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2122 this.fromJSON(data);
2122 this.fromJSON(data);
2123 if (this.ncells() === 0) {
2123 if (this.ncells() === 0) {
2124 this.insert_cell_below('code');
2124 this.insert_cell_below('code');
2125 this.edit_mode(0);
2125 this.edit_mode(0);
2126 } else {
2126 } else {
2127 this.select(0);
2127 this.select(0);
2128 this.handle_command_mode(this.get_cell(0));
2128 this.handle_command_mode(this.get_cell(0));
2129 }
2129 }
2130 this.set_dirty(false);
2130 this.set_dirty(false);
2131 this.scroll_to_top();
2131 this.scroll_to_top();
2132 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2132 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2133 var msg = "This notebook has been converted from an older " +
2133 var msg = "This notebook has been converted from an older " +
2134 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2134 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2135 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2135 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2136 "newer notebook format will be used and older versions of IPython " +
2136 "newer notebook format will be used and older versions of IPython " +
2137 "may not be able to read it. To keep the older version, close the " +
2137 "may not be able to read it. To keep the older version, close the " +
2138 "notebook without saving it.";
2138 "notebook without saving it.";
2139 Dialog.modal({
2139 Dialog.modal({
2140 title : "Notebook converted",
2140 title : "Notebook converted",
2141 body : msg,
2141 body : msg,
2142 buttons : {
2142 buttons : {
2143 OK : {
2143 OK : {
2144 class : "btn-primary"
2144 class : "btn-primary"
2145 }
2145 }
2146 }
2146 }
2147 });
2147 });
2148 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2148 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2149 var that = this;
2149 var that = this;
2150 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2150 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2151 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2151 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2152 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2152 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2153 this_vs + ". You can still work with this notebook, but some features " +
2153 this_vs + ". You can still work with this notebook, but some features " +
2154 "introduced in later notebook versions may not be available.";
2154 "introduced in later notebook versions may not be available.";
2155
2155
2156 Dialog.modal({
2156 Dialog.modal({
2157 title : "Newer Notebook",
2157 title : "Newer Notebook",
2158 body : msg,
2158 body : msg,
2159 buttons : {
2159 buttons : {
2160 OK : {
2160 OK : {
2161 class : "btn-danger"
2161 class : "btn-danger"
2162 }
2162 }
2163 }
2163 }
2164 });
2164 });
2165
2165
2166 }
2166 }
2167
2167
2168 // Create the session after the notebook is completely loaded to prevent
2168 // Create the session after the notebook is completely loaded to prevent
2169 // code execution upon loading, which is a security risk.
2169 // code execution upon loading, which is a security risk.
2170 if (this.session === null) {
2170 if (this.session === null) {
2171 this.start_session();
2171 this.start_session();
2172 }
2172 }
2173 // load our checkpoint list
2173 // load our checkpoint list
2174 this.list_checkpoints();
2174 this.list_checkpoints();
2175
2175
2176 // load toolbar state
2176 // load toolbar state
2177 if (this.metadata.celltoolbar) {
2177 if (this.metadata.celltoolbar) {
2178 CellToolbar.global_show();
2178 CellToolbar.global_show();
2179 CellToolbar.activate_preset(this.metadata.celltoolbar);
2179 CellToolbar.activate_preset(this.metadata.celltoolbar);
2180 } else {
2180 } else {
2181 CellToolbar.global_hide();
2181 CellToolbar.global_hide();
2182 }
2182 }
2183
2183
2184 // now that we're fully loaded, it is safe to restore save functionality
2184 // now that we're fully loaded, it is safe to restore save functionality
2185 delete(this.save_notebook);
2185 delete(this.save_notebook);
2186 this.events.trigger('notebook_loaded.Notebook');
2186 this.events.trigger('notebook_loaded.Notebook');
2187 };
2187 };
2188
2188
2189 /**
2189 /**
2190 * Failure callback for loading a notebook from the server.
2190 * Failure callback for loading a notebook from the server.
2191 *
2191 *
2192 * @method load_notebook_error
2192 * @method load_notebook_error
2193 * @param {jqXHR} xhr jQuery Ajax object
2193 * @param {jqXHR} xhr jQuery Ajax object
2194 * @param {String} status Description of response status
2194 * @param {String} status Description of response status
2195 * @param {String} error HTTP error message
2195 * @param {String} error HTTP error message
2196 */
2196 */
2197 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2197 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2198 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2198 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2199 var msg;
2199 var msg;
2200 if (xhr.status === 400) {
2200 if (xhr.status === 400) {
2201 msg = error;
2201 msg = error;
2202 } else if (xhr.status === 500) {
2202 } else if (xhr.status === 500) {
2203 msg = "An unknown error occurred while loading this notebook. " +
2203 msg = "An unknown error occurred while loading this notebook. " +
2204 "This version can load notebook formats " +
2204 "This version can load notebook formats " +
2205 "v" + this.nbformat + " or earlier.";
2205 "v" + this.nbformat + " or earlier.";
2206 }
2206 }
2207 Dialog.modal({
2207 Dialog.modal({
2208 title: "Error loading notebook",
2208 title: "Error loading notebook",
2209 body : msg,
2209 body : msg,
2210 buttons : {
2210 buttons : {
2211 "OK": {}
2211 "OK": {}
2212 }
2212 }
2213 });
2213 });
2214 };
2214 };
2215
2215
2216 /********************* checkpoint-related *********************/
2216 /********************* checkpoint-related *********************/
2217
2217
2218 /**
2218 /**
2219 * Save the notebook then immediately create a checkpoint.
2219 * Save the notebook then immediately create a checkpoint.
2220 *
2220 *
2221 * @method save_checkpoint
2221 * @method save_checkpoint
2222 */
2222 */
2223 Notebook.prototype.save_checkpoint = function () {
2223 Notebook.prototype.save_checkpoint = function () {
2224 this._checkpoint_after_save = true;
2224 this._checkpoint_after_save = true;
2225 this.save_notebook();
2225 this.save_notebook();
2226 };
2226 };
2227
2227
2228 /**
2228 /**
2229 * Add a checkpoint for this notebook.
2229 * Add a checkpoint for this notebook.
2230 * for use as a callback from checkpoint creation.
2230 * for use as a callback from checkpoint creation.
2231 *
2231 *
2232 * @method add_checkpoint
2232 * @method add_checkpoint
2233 */
2233 */
2234 Notebook.prototype.add_checkpoint = function (checkpoint) {
2234 Notebook.prototype.add_checkpoint = function (checkpoint) {
2235 var found = false;
2235 var found = false;
2236 for (var i = 0; i < this.checkpoints.length; i++) {
2236 for (var i = 0; i < this.checkpoints.length; i++) {
2237 var existing = this.checkpoints[i];
2237 var existing = this.checkpoints[i];
2238 if (existing.id == checkpoint.id) {
2238 if (existing.id == checkpoint.id) {
2239 found = true;
2239 found = true;
2240 this.checkpoints[i] = checkpoint;
2240 this.checkpoints[i] = checkpoint;
2241 break;
2241 break;
2242 }
2242 }
2243 }
2243 }
2244 if (!found) {
2244 if (!found) {
2245 this.checkpoints.push(checkpoint);
2245 this.checkpoints.push(checkpoint);
2246 }
2246 }
2247 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2247 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2248 };
2248 };
2249
2249
2250 /**
2250 /**
2251 * List checkpoints for this notebook.
2251 * List checkpoints for this notebook.
2252 *
2252 *
2253 * @method list_checkpoints
2253 * @method list_checkpoints
2254 */
2254 */
2255 Notebook.prototype.list_checkpoints = function () {
2255 Notebook.prototype.list_checkpoints = function () {
2256 var url = utils.url_join_encode(
2256 var url = utils.url_join_encode(
2257 this.base_url,
2257 this.base_url,
2258 'api/notebooks',
2258 'api/notebooks',
2259 this.notebook_path,
2259 this.notebook_path,
2260 this.notebook_name,
2260 this.notebook_name,
2261 'checkpoints'
2261 'checkpoints'
2262 );
2262 );
2263 $.get(url).done(
2263 $.get(url).done(
2264 $.proxy(this.list_checkpoints_success, this)
2264 $.proxy(this.list_checkpoints_success, this)
2265 ).fail(
2265 ).fail(
2266 $.proxy(this.list_checkpoints_error, this)
2266 $.proxy(this.list_checkpoints_error, this)
2267 );
2267 );
2268 };
2268 };
2269
2269
2270 /**
2270 /**
2271 * Success callback for listing checkpoints.
2271 * Success callback for listing checkpoints.
2272 *
2272 *
2273 * @method list_checkpoint_success
2273 * @method list_checkpoint_success
2274 * @param {Object} data JSON representation of a checkpoint
2274 * @param {Object} data JSON representation of a checkpoint
2275 * @param {String} status Description of response status
2275 * @param {String} status Description of response status
2276 * @param {jqXHR} xhr jQuery Ajax object
2276 * @param {jqXHR} xhr jQuery Ajax object
2277 */
2277 */
2278 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2278 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2279 data = $.parseJSON(data);
2279 data = $.parseJSON(data);
2280 this.checkpoints = data;
2280 this.checkpoints = data;
2281 if (data.length) {
2281 if (data.length) {
2282 this.last_checkpoint = data[data.length - 1];
2282 this.last_checkpoint = data[data.length - 1];
2283 } else {
2283 } else {
2284 this.last_checkpoint = null;
2284 this.last_checkpoint = null;
2285 }
2285 }
2286 this.events.trigger('checkpoints_listed.Notebook', [data]);
2286 this.events.trigger('checkpoints_listed.Notebook', [data]);
2287 };
2287 };
2288
2288
2289 /**
2289 /**
2290 * Failure callback for listing a checkpoint.
2290 * Failure callback for listing a checkpoint.
2291 *
2291 *
2292 * @method list_checkpoint_error
2292 * @method list_checkpoint_error
2293 * @param {jqXHR} xhr jQuery Ajax object
2293 * @param {jqXHR} xhr jQuery Ajax object
2294 * @param {String} status Description of response status
2294 * @param {String} status Description of response status
2295 * @param {String} error_msg HTTP error message
2295 * @param {String} error_msg HTTP error message
2296 */
2296 */
2297 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2297 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2298 this.events.trigger('list_checkpoints_failed.Notebook');
2298 this.events.trigger('list_checkpoints_failed.Notebook');
2299 };
2299 };
2300
2300
2301 /**
2301 /**
2302 * Create a checkpoint of this notebook on the server from the most recent save.
2302 * Create a checkpoint of this notebook on the server from the most recent save.
2303 *
2303 *
2304 * @method create_checkpoint
2304 * @method create_checkpoint
2305 */
2305 */
2306 Notebook.prototype.create_checkpoint = function () {
2306 Notebook.prototype.create_checkpoint = function () {
2307 var url = utils.url_join_encode(
2307 var url = utils.url_join_encode(
2308 this.base_url,
2308 this.base_url,
2309 'api/notebooks',
2309 'api/notebooks',
2310 this.notebook_path,
2310 this.notebook_path,
2311 this.notebook_name,
2311 this.notebook_name,
2312 'checkpoints'
2312 'checkpoints'
2313 );
2313 );
2314 $.post(url).done(
2314 $.post(url).done(
2315 $.proxy(this.create_checkpoint_success, this)
2315 $.proxy(this.create_checkpoint_success, this)
2316 ).fail(
2316 ).fail(
2317 $.proxy(this.create_checkpoint_error, this)
2317 $.proxy(this.create_checkpoint_error, this)
2318 );
2318 );
2319 };
2319 };
2320
2320
2321 /**
2321 /**
2322 * Success callback for creating a checkpoint.
2322 * Success callback for creating a checkpoint.
2323 *
2323 *
2324 * @method create_checkpoint_success
2324 * @method create_checkpoint_success
2325 * @param {Object} data JSON representation of a checkpoint
2325 * @param {Object} data JSON representation of a checkpoint
2326 * @param {String} status Description of response status
2326 * @param {String} status Description of response status
2327 * @param {jqXHR} xhr jQuery Ajax object
2327 * @param {jqXHR} xhr jQuery Ajax object
2328 */
2328 */
2329 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2329 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2330 data = $.parseJSON(data);
2330 data = $.parseJSON(data);
2331 this.add_checkpoint(data);
2331 this.add_checkpoint(data);
2332 this.events.trigger('checkpoint_created.Notebook', data);
2332 this.events.trigger('checkpoint_created.Notebook', data);
2333 };
2333 };
2334
2334
2335 /**
2335 /**
2336 * Failure callback for creating a checkpoint.
2336 * Failure callback for creating a checkpoint.
2337 *
2337 *
2338 * @method create_checkpoint_error
2338 * @method create_checkpoint_error
2339 * @param {jqXHR} xhr jQuery Ajax object
2339 * @param {jqXHR} xhr jQuery Ajax object
2340 * @param {String} status Description of response status
2340 * @param {String} status Description of response status
2341 * @param {String} error_msg HTTP error message
2341 * @param {String} error_msg HTTP error message
2342 */
2342 */
2343 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2343 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2344 this.events.trigger('checkpoint_failed.Notebook');
2344 this.events.trigger('checkpoint_failed.Notebook');
2345 };
2345 };
2346
2346
2347 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2347 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2348 var that = this;
2348 var that = this;
2349 checkpoint = checkpoint || this.last_checkpoint;
2349 checkpoint = checkpoint || this.last_checkpoint;
2350 if ( ! checkpoint ) {
2350 if ( ! checkpoint ) {
2351 console.log("restore dialog, but no checkpoint to restore to!");
2351 console.log("restore dialog, but no checkpoint to restore to!");
2352 return;
2352 return;
2353 }
2353 }
2354 var body = $('<div/>').append(
2354 var body = $('<div/>').append(
2355 $('<p/>').addClass("p-space").text(
2355 $('<p/>').addClass("p-space").text(
2356 "Are you sure you want to revert the notebook to " +
2356 "Are you sure you want to revert the notebook to " +
2357 "the latest checkpoint?"
2357 "the latest checkpoint?"
2358 ).append(
2358 ).append(
2359 $("<strong/>").text(
2359 $("<strong/>").text(
2360 " This cannot be undone."
2360 " This cannot be undone."
2361 )
2361 )
2362 )
2362 )
2363 ).append(
2363 ).append(
2364 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2364 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2365 ).append(
2365 ).append(
2366 $('<p/>').addClass("p-space").text(
2366 $('<p/>').addClass("p-space").text(
2367 Date(checkpoint.last_modified)
2367 Date(checkpoint.last_modified)
2368 ).css("text-align", "center")
2368 ).css("text-align", "center")
2369 );
2369 );
2370
2370
2371 Dialog.modal({
2371 Dialog.modal({
2372 title : "Revert notebook to checkpoint",
2372 title : "Revert notebook to checkpoint",
2373 body : body,
2373 body : body,
2374 buttons : {
2374 buttons : {
2375 Revert : {
2375 Revert : {
2376 class : "btn-danger",
2376 class : "btn-danger",
2377 click : function () {
2377 click : function () {
2378 that.restore_checkpoint(checkpoint.id);
2378 that.restore_checkpoint(checkpoint.id);
2379 }
2379 }
2380 },
2380 },
2381 Cancel : {}
2381 Cancel : {}
2382 }
2382 }
2383 });
2383 });
2384 };
2384 };
2385
2385
2386 /**
2386 /**
2387 * Restore the notebook to a checkpoint state.
2387 * Restore the notebook to a checkpoint state.
2388 *
2388 *
2389 * @method restore_checkpoint
2389 * @method restore_checkpoint
2390 * @param {String} checkpoint ID
2390 * @param {String} checkpoint ID
2391 */
2391 */
2392 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2392 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2393 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2393 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2394 var url = utils.url_join_encode(
2394 var url = utils.url_join_encode(
2395 this.base_url,
2395 this.base_url,
2396 'api/notebooks',
2396 'api/notebooks',
2397 this.notebook_path,
2397 this.notebook_path,
2398 this.notebook_name,
2398 this.notebook_name,
2399 'checkpoints',
2399 'checkpoints',
2400 checkpoint
2400 checkpoint
2401 );
2401 );
2402 $.post(url).done(
2402 $.post(url).done(
2403 $.proxy(this.restore_checkpoint_success, this)
2403 $.proxy(this.restore_checkpoint_success, this)
2404 ).fail(
2404 ).fail(
2405 $.proxy(this.restore_checkpoint_error, this)
2405 $.proxy(this.restore_checkpoint_error, this)
2406 );
2406 );
2407 };
2407 };
2408
2408
2409 /**
2409 /**
2410 * Success callback for restoring a notebook to a checkpoint.
2410 * Success callback for restoring a notebook to a checkpoint.
2411 *
2411 *
2412 * @method restore_checkpoint_success
2412 * @method restore_checkpoint_success
2413 * @param {Object} data (ignored, should be empty)
2413 * @param {Object} data (ignored, should be empty)
2414 * @param {String} status Description of response status
2414 * @param {String} status Description of response status
2415 * @param {jqXHR} xhr jQuery Ajax object
2415 * @param {jqXHR} xhr jQuery Ajax object
2416 */
2416 */
2417 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2417 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2418 this.events.trigger('checkpoint_restored.Notebook');
2418 this.events.trigger('checkpoint_restored.Notebook');
2419 this.load_notebook(this.notebook_name, this.notebook_path);
2419 this.load_notebook(this.notebook_name, this.notebook_path);
2420 };
2420 };
2421
2421
2422 /**
2422 /**
2423 * Failure callback for restoring a notebook to a checkpoint.
2423 * Failure callback for restoring a notebook to a checkpoint.
2424 *
2424 *
2425 * @method restore_checkpoint_error
2425 * @method restore_checkpoint_error
2426 * @param {jqXHR} xhr jQuery Ajax object
2426 * @param {jqXHR} xhr jQuery Ajax object
2427 * @param {String} status Description of response status
2427 * @param {String} status Description of response status
2428 * @param {String} error_msg HTTP error message
2428 * @param {String} error_msg HTTP error message
2429 */
2429 */
2430 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2430 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2431 this.events.trigger('checkpoint_restore_failed.Notebook');
2431 this.events.trigger('checkpoint_restore_failed.Notebook');
2432 };
2432 };
2433
2433
2434 /**
2434 /**
2435 * Delete a notebook checkpoint.
2435 * Delete a notebook checkpoint.
2436 *
2436 *
2437 * @method delete_checkpoint
2437 * @method delete_checkpoint
2438 * @param {String} checkpoint ID
2438 * @param {String} checkpoint ID
2439 */
2439 */
2440 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2440 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2441 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2441 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2442 var url = utils.url_join_encode(
2442 var url = utils.url_join_encode(
2443 this.base_url,
2443 this.base_url,
2444 'api/notebooks',
2444 'api/notebooks',
2445 this.notebook_path,
2445 this.notebook_path,
2446 this.notebook_name,
2446 this.notebook_name,
2447 'checkpoints',
2447 'checkpoints',
2448 checkpoint
2448 checkpoint
2449 );
2449 );
2450 $.ajax(url, {
2450 $.ajax(url, {
2451 type: 'DELETE',
2451 type: 'DELETE',
2452 success: $.proxy(this.delete_checkpoint_success, this),
2452 success: $.proxy(this.delete_checkpoint_success, this),
2453 error: $.proxy(this.delete_checkpoint_error, this)
2453 error: $.proxy(this.delete_checkpoint_error, this)
2454 });
2454 });
2455 };
2455 };
2456
2456
2457 /**
2457 /**
2458 * Success callback for deleting a notebook checkpoint
2458 * Success callback for deleting a notebook checkpoint
2459 *
2459 *
2460 * @method delete_checkpoint_success
2460 * @method delete_checkpoint_success
2461 * @param {Object} data (ignored, should be empty)
2461 * @param {Object} data (ignored, should be empty)
2462 * @param {String} status Description of response status
2462 * @param {String} status Description of response status
2463 * @param {jqXHR} xhr jQuery Ajax object
2463 * @param {jqXHR} xhr jQuery Ajax object
2464 */
2464 */
2465 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2465 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2466 this.events.trigger('checkpoint_deleted.Notebook', data);
2466 this.events.trigger('checkpoint_deleted.Notebook', data);
2467 this.load_notebook(this.notebook_name, this.notebook_path);
2467 this.load_notebook(this.notebook_name, this.notebook_path);
2468 };
2468 };
2469
2469
2470 /**
2470 /**
2471 * Failure callback for deleting a notebook checkpoint.
2471 * Failure callback for deleting a notebook checkpoint.
2472 *
2472 *
2473 * @method delete_checkpoint_error
2473 * @method delete_checkpoint_error
2474 * @param {jqXHR} xhr jQuery Ajax object
2474 * @param {jqXHR} xhr jQuery Ajax object
2475 * @param {String} status Description of response status
2475 * @param {String} status Description of response status
2476 * @param {String} error_msg HTTP error message
2476 * @param {String} error_msg HTTP error message
2477 */
2477 */
2478 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2478 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2479 this.events.trigger('checkpoint_delete_failed.Notebook');
2479 this.events.trigger('checkpoint_delete_failed.Notebook');
2480 };
2480 };
2481
2481
2482
2482
2483 // For backwards compatability.
2483 // For backwards compatability.
2484 IPython.Notebook = Notebook;
2484 IPython.Notebook = Notebook;
2485
2485
2486 return Notebook;
2486 return Notebook;
2487 });
2487 });
@@ -1,180 +1,180 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 define([
5 // Pager
5 'base/js/namespace',
6 //============================================================================
6 'components/jquery/jquery.min',
7
7 'notebook/js/toolbar',
8 var IPython = (function (IPython) {
8 'notebook/js/celltoolbar',
9 ], function(IPython, $, Toolbar, CellToolbar) {
9 "use strict";
10 "use strict";
10
11
11 var utils = IPython.utils;
12 var utils = IPython.utils;
12
13
13 var Pager = function (pager_selector, pager_splitter_selector) {
14 var Pager = function (pager_selector, pager_splitter_selector, layout_manager, events) {
15 this.events = events;
14 this.pager_element = $(pager_selector);
16 this.pager_element = $(pager_selector);
15 this.pager_button_area = $('#pager_button_area');
17 this.pager_button_area = $('#pager_button_area');
16 var that = this;
18 var that = this;
17 this.percentage_height = 0.40;
19 this.percentage_height = 0.40;
18 this.pager_splitter_element = $(pager_splitter_selector)
20 this.pager_splitter_element = $(pager_splitter_selector)
19 .draggable({
21 .draggable({
20 containment: 'window',
22 containment: 'window',
21 axis:'y',
23 axis:'y',
22 helper: null ,
24 helper: null ,
23 drag: function(event, ui) {
25 drag: function(event, ui) {
24 // recalculate the amount of space the pager should take
26 // recalculate the amount of space the pager should take
25 var pheight = ($(document.body).height()-event.clientY-4);
27 var pheight = ($(document.body).height()-event.clientY-4);
26 var downprct = pheight/IPython.layout_manager.app_height();
28 var downprct = pheight/layout_manager.app_height();
27 downprct = Math.min(0.9, downprct);
29 downprct = Math.min(0.9, downprct);
28 if (downprct < 0.1) {
30 if (downprct < 0.1) {
29 that.percentage_height = 0.1;
31 that.percentage_height = 0.1;
30 that.collapse({'duration':0});
32 that.collapse({'duration':0});
31 } else if (downprct > 0.2) {
33 } else if (downprct > 0.2) {
32 that.percentage_height = downprct;
34 that.percentage_height = downprct;
33 that.expand({'duration':0});
35 that.expand({'duration':0});
34 }
36 }
35 IPython.layout_manager.do_resize();
37 layout_manager.do_resize();
36 }
38 }
37 });
39 });
38 this.expanded = false;
40 this.expanded = false;
39 this.style();
41 this.style();
40 this.create_button_area();
42 this.create_button_area();
41 this.bind_events();
43 this.bind_events();
42 };
44 };
43
45
44 Pager.prototype.create_button_area = function(){
46 Pager.prototype.create_button_area = function(){
45 var that = this;
47 var that = this;
46 this.pager_button_area.append(
48 this.pager_button_area.append(
47 $('<a>').attr('role', "button")
49 $('<a>').attr('role', "button")
48 .attr('title',"Open the pager in an external window")
50 .attr('title',"Open the pager in an external window")
49 .addClass('ui-button')
51 .addClass('ui-button')
50 .click(function(){that.detach()})
52 .click(function(){that.detach();})
51 .attr('style','position: absolute; right: 20px;')
53 .attr('style','position: absolute; right: 20px;')
52 .append(
54 .append(
53 $('<span>').addClass("ui-icon ui-icon-extlink")
55 $('<span>').addClass("ui-icon ui-icon-extlink")
54 )
56 )
55 )
57 );
56 this.pager_button_area.append(
58 this.pager_button_area.append(
57 $('<a>').attr('role', "button")
59 $('<a>').attr('role', "button")
58 .attr('title',"Close the pager")
60 .attr('title',"Close the pager")
59 .addClass('ui-button')
61 .addClass('ui-button')
60 .click(function(){that.collapse()})
62 .click(function(){that.collapse();})
61 .attr('style','position: absolute; right: 5px;')
63 .attr('style','position: absolute; right: 5px;')
62 .append(
64 .append(
63 $('<span>').addClass("ui-icon ui-icon-close")
65 $('<span>').addClass("ui-icon ui-icon-close")
64 )
66 )
65 )
67 );
66 };
68 };
67
69
68 Pager.prototype.style = function () {
70 Pager.prototype.style = function () {
69 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
71 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
70 this.pager_element.addClass('border-box-sizing');
72 this.pager_element.addClass('border-box-sizing');
71 this.pager_element.find(".container").addClass('border-box-sizing');
73 this.pager_element.find(".container").addClass('border-box-sizing');
72 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
74 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
73 };
75 };
74
76
75
77
76 Pager.prototype.bind_events = function () {
78 Pager.prototype.bind_events = function () {
77 var that = this;
79 var that = this;
78
80
79 this.pager_element.bind('collapse_pager', function (event, extrap) {
81 this.pager_element.bind('collapse_pager', function (event, extrap) {
80 var time = 'fast';
82 var time = 'fast';
81 if (extrap && extrap.duration) {
83 if (extrap && extrap.duration) {
82 time = extrap.duration;
84 time = extrap.duration;
83 }
85 }
84 that.pager_element.hide(time);
86 that.pager_element.hide(time);
85 });
87 });
86
88
87 this.pager_element.bind('expand_pager', function (event, extrap) {
89 this.pager_element.bind('expand_pager', function (event, extrap) {
88 var time = 'fast';
90 var time = 'fast';
89 if (extrap && extrap.duration) {
91 if (extrap && extrap.duration) {
90 time = extrap.duration;
92 time = extrap.duration;
91 }
93 }
92 that.pager_element.show(time);
94 that.pager_element.show(time);
93 });
95 });
94
96
95 this.pager_splitter_element.hover(
97 this.pager_splitter_element.hover(
96 function () {
98 function () {
97 that.pager_splitter_element.addClass('ui-state-hover');
99 that.pager_splitter_element.addClass('ui-state-hover');
98 },
100 },
99 function () {
101 function () {
100 that.pager_splitter_element.removeClass('ui-state-hover');
102 that.pager_splitter_element.removeClass('ui-state-hover');
101 }
103 }
102 );
104 );
103
105
104 this.pager_splitter_element.click(function () {
106 this.pager_splitter_element.click(function () {
105 that.toggle();
107 that.toggle();
106 });
108 });
107
109
108 $([IPython.events]).on('open_with_text.Pager', function (event, payload) {
110 this.events.on('open_with_text.Pager', function (event, payload) {
109 // FIXME: support other mime types
111 // FIXME: support other mime types
110 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
112 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
111 that.clear();
113 that.clear();
112 that.expand();
114 that.expand();
113 that.append_text(payload.data['text/plain']);
115 that.append_text(payload.data['text/plain']);
114 }
116 }
115 });
117 });
116 };
118 };
117
119
118
120
119 Pager.prototype.collapse = function (extrap) {
121 Pager.prototype.collapse = function (extrap) {
120 if (this.expanded === true) {
122 if (this.expanded === true) {
121 this.expanded = false;
123 this.expanded = false;
122 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
124 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
123 }
125 }
124 };
126 };
125
127
126
128
127 Pager.prototype.expand = function (extrap) {
129 Pager.prototype.expand = function (extrap) {
128 if (this.expanded !== true) {
130 if (this.expanded !== true) {
129 this.expanded = true;
131 this.expanded = true;
130 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
132 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
131 }
133 }
132 };
134 };
133
135
134
136
135 Pager.prototype.toggle = function () {
137 Pager.prototype.toggle = function () {
136 if (this.expanded === true) {
138 if (this.expanded === true) {
137 this.collapse();
139 this.collapse();
138 } else {
140 } else {
139 this.expand();
141 this.expand();
140 }
142 }
141 };
143 };
142
144
143
145
144 Pager.prototype.clear = function (text) {
146 Pager.prototype.clear = function (text) {
145 this.pager_element.find(".container").empty();
147 this.pager_element.find(".container").empty();
146 };
148 };
147
149
148 Pager.prototype.detach = function(){
150 Pager.prototype.detach = function(){
149 var w = window.open("","_blank");
151 var w = window.open("","_blank");
150 $(w.document.head)
152 $(w.document.head)
151 .append(
153 .append(
152 $('<link>')
154 $('<link>')
153 .attr('rel',"stylesheet")
155 .attr('rel',"stylesheet")
154 .attr('href',"/static/css/notebook.css")
156 .attr('href',"/static/css/notebook.css")
155 .attr('type',"text/css")
157 .attr('type',"text/css")
156 )
158 )
157 .append(
159 .append(
158 $('<title>').text("IPython Pager")
160 $('<title>').text("IPython Pager")
159 );
161 );
160 var pager_body = $(w.document.body);
162 var pager_body = $(w.document.body);
161 pager_body.css('overflow','scroll');
163 pager_body.css('overflow','scroll');
162
164
163 pager_body.append(this.pager_element.clone().children());
165 pager_body.append(this.pager_element.clone().children());
164 w.document.close();
166 w.document.close();
165 this.collapse();
167 this.collapse();
166 };
168 };
167
169
168 Pager.prototype.append_text = function (text) {
170 Pager.prototype.append_text = function (text) {
169 // The only user content injected with this HTML call is escaped by
171 // The only user content injected with this HTML call is escaped by
170 // the fixConsole() method.
172 // the fixConsole() method.
171 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
173 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
172 };
174 };
173
175
174
176 // Backwards compatability.
175 IPython.Pager = Pager;
177 IPython.Pager = Pager;
176
178
177 return IPython;
179 return Pager;
178
180 });
179 }(IPython));
180
@@ -1,459 +1,449 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'components/jquery/jquery.min',
7
7 'notebook/js/cell',
8 //============================================================================
8 'base/js/security',
9 // TextCell
9 ], function(IPython, $, Cell, Security) {
10 //============================================================================
11
12
13
14 /**
15 A module that allow to create different type of Text Cell
16 @module IPython
17 @namespace IPython
18 */
19 var IPython = (function (IPython) {
20 "use strict";
10 "use strict";
21
11
22 // TextCell base class
23 var keycodes = IPython.keyboard.keycodes;
24 var security = IPython.security;
25
26 /**
12 /**
27 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
13 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
28 * cell start as not redered.
14 * cell start as not redered.
29 *
15 *
30 * @class TextCell
16 * @class TextCell
31 * @constructor TextCell
17 * @constructor TextCell
32 * @extend IPython.Cell
18 * @extend Cell
33 * @param {object|undefined} [options]
19 * @param {object|undefined} [options]
34 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
20 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
35 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
21 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
36 */
22 */
37 var TextCell = function (options) {
23 var TextCell = function (options, events, config, mathjaxutils) {
24 // TODO: Config is IPython.config
38 // in all TextCell/Cell subclasses
25 // in all TextCell/Cell subclasses
39 // do not assign most of members here, just pass it down
26 // do not assign most of members here, just pass it down
40 // in the options dict potentially overwriting what you wish.
27 // in the options dict potentially overwriting what you wish.
41 // they will be assigned in the base class.
28 // they will be assigned in the base class.
42
29
43 // we cannot put this as a class key as it has handle to "this".
30 // we cannot put this as a class key as it has handle to "this".
44 var cm_overwrite_options = {
31 var cm_overwrite_options = {
45 onKeyEvent: $.proxy(this.handle_keyevent,this)
32 onKeyEvent: $.proxy(this.handle_keyevent,this)
46 };
33 };
47
34
48 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
35 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
49
36
50 this.cell_type = this.cell_type || 'text';
37 this.cell_type = this.cell_type || 'text';
38 this.mathjaxutils = mathjaxutils;
51
39
52 IPython.Cell.apply(this, [options]);
40 Cell.apply(this, [options], events);
53
41
54 this.rendered = false;
42 this.rendered = false;
55 };
43 };
56
44
57 TextCell.prototype = new IPython.Cell();
45 TextCell.prototype = new Cell();
58
46
59 TextCell.options_default = {
47 TextCell.options_default = {
60 cm_config : {
48 cm_config : {
61 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
49 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
62 mode: 'htmlmixed',
50 mode: 'htmlmixed',
63 lineWrapping : true,
51 lineWrapping : true,
64 }
52 }
65 };
53 };
66
54
67
55
68 /**
56 /**
69 * Create the DOM element of the TextCell
57 * Create the DOM element of the TextCell
70 * @method create_element
58 * @method create_element
71 * @private
59 * @private
72 */
60 */
73 TextCell.prototype.create_element = function () {
61 TextCell.prototype.create_element = function () {
74 IPython.Cell.prototype.create_element.apply(this, arguments);
62 Cell.prototype.create_element.apply(this, arguments);
75
63
76 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
64 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
77 cell.attr('tabindex','2');
65 cell.attr('tabindex','2');
78
66
79 var prompt = $('<div/>').addClass('prompt input_prompt');
67 var prompt = $('<div/>').addClass('prompt input_prompt');
80 cell.append(prompt);
68 cell.append(prompt);
81 var inner_cell = $('<div/>').addClass('inner_cell');
69 var inner_cell = $('<div/>').addClass('inner_cell');
82 this.celltoolbar = new IPython.CellToolbar(this);
70 this.celltoolbar = new CellToolbar(this);
83 inner_cell.append(this.celltoolbar.element);
71 inner_cell.append(this.celltoolbar.element);
84 var input_area = $('<div/>').addClass('input_area');
72 var input_area = $('<div/>').addClass('input_area');
85 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
73 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
86 // The tabindex=-1 makes this div focusable.
74 // The tabindex=-1 makes this div focusable.
87 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
75 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
88 addClass('rendered_html').attr('tabindex','-1');
76 addClass('rendered_html').attr('tabindex','-1');
89 inner_cell.append(input_area).append(render_area);
77 inner_cell.append(input_area).append(render_area);
90 cell.append(inner_cell);
78 cell.append(inner_cell);
91 this.element = cell;
79 this.element = cell;
92 };
80 };
93
81
94
82
95 /**
83 /**
96 * Bind the DOM evet to cell actions
84 * Bind the DOM evet to cell actions
97 * Need to be called after TextCell.create_element
85 * Need to be called after TextCell.create_element
98 * @private
86 * @private
99 * @method bind_event
87 * @method bind_event
100 */
88 */
101 TextCell.prototype.bind_events = function () {
89 TextCell.prototype.bind_events = function () {
102 IPython.Cell.prototype.bind_events.apply(this);
90 Cell.prototype.bind_events.apply(this);
103 var that = this;
91 var that = this;
104
92
105 this.element.dblclick(function () {
93 this.element.dblclick(function () {
106 if (that.selected === false) {
94 if (that.selected === false) {
107 $([IPython.events]).trigger('select.Cell', {'cell':that});
95 this.events.trigger('select.Cell', {'cell':that});
108 }
96 }
109 var cont = that.unrender();
97 var cont = that.unrender();
110 if (cont) {
98 if (cont) {
111 that.focus_editor();
99 that.focus_editor();
112 }
100 }
113 });
101 });
114 };
102 };
115
103
116 // Cell level actions
104 // Cell level actions
117
105
118 TextCell.prototype.select = function () {
106 TextCell.prototype.select = function () {
119 var cont = IPython.Cell.prototype.select.apply(this);
107 var cont = Cell.prototype.select.apply(this);
120 if (cont) {
108 if (cont) {
121 if (this.mode === 'edit') {
109 if (this.mode === 'edit') {
122 this.code_mirror.refresh();
110 this.code_mirror.refresh();
123 }
111 }
124 }
112 }
125 return cont;
113 return cont;
126 };
114 };
127
115
128 TextCell.prototype.unrender = function () {
116 TextCell.prototype.unrender = function () {
129 if (this.read_only) return;
117 if (this.read_only) return;
130 var cont = IPython.Cell.prototype.unrender.apply(this);
118 var cont = Cell.prototype.unrender.apply(this);
131 if (cont) {
119 if (cont) {
132 var text_cell = this.element;
120 var text_cell = this.element;
133 var output = text_cell.find("div.text_cell_render");
121 var output = text_cell.find("div.text_cell_render");
134 output.hide();
122 output.hide();
135 text_cell.find('div.input_area').show();
123 text_cell.find('div.input_area').show();
136 if (this.get_text() === this.placeholder) {
124 if (this.get_text() === this.placeholder) {
137 this.set_text('');
125 this.set_text('');
138 }
126 }
139 this.refresh();
127 this.refresh();
140 }
128 }
141 if (this.celltoolbar.ui_controls_list.length) {
129 if (this.celltoolbar.ui_controls_list.length) {
142 this.celltoolbar.show();
130 this.celltoolbar.show();
143 }
131 }
144 return cont;
132 return cont;
145 };
133 };
146
134
147 TextCell.prototype.execute = function () {
135 TextCell.prototype.execute = function () {
148 this.render();
136 this.render();
149 };
137 };
150
138
151 /**
139 /**
152 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
140 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
153 * @method get_text
141 * @method get_text
154 * @retrun {string} CodeMirror current text value
142 * @retrun {string} CodeMirror current text value
155 */
143 */
156 TextCell.prototype.get_text = function() {
144 TextCell.prototype.get_text = function() {
157 return this.code_mirror.getValue();
145 return this.code_mirror.getValue();
158 };
146 };
159
147
160 /**
148 /**
161 * @param {string} text - Codemiror text value
149 * @param {string} text - Codemiror text value
162 * @see TextCell#get_text
150 * @see TextCell#get_text
163 * @method set_text
151 * @method set_text
164 * */
152 * */
165 TextCell.prototype.set_text = function(text) {
153 TextCell.prototype.set_text = function(text) {
166 this.code_mirror.setValue(text);
154 this.code_mirror.setValue(text);
167 this.code_mirror.refresh();
155 this.code_mirror.refresh();
168 };
156 };
169
157
170 /**
158 /**
171 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
159 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
172 * @method get_rendered
160 * @method get_rendered
173 * @return {html} html of rendered element
161 * @return {html} html of rendered element
174 * */
162 * */
175 TextCell.prototype.get_rendered = function() {
163 TextCell.prototype.get_rendered = function() {
176 return this.element.find('div.text_cell_render').html();
164 return this.element.find('div.text_cell_render').html();
177 };
165 };
178
166
179 /**
167 /**
180 * @method set_rendered
168 * @method set_rendered
181 */
169 */
182 TextCell.prototype.set_rendered = function(text) {
170 TextCell.prototype.set_rendered = function(text) {
183 this.element.find('div.text_cell_render').html(text);
171 this.element.find('div.text_cell_render').html(text);
184 this.celltoolbar.hide();
172 this.celltoolbar.hide();
185 };
173 };
186
174
187
175
188 /**
176 /**
189 * Create Text cell from JSON
177 * Create Text cell from JSON
190 * @param {json} data - JSON serialized text-cell
178 * @param {json} data - JSON serialized text-cell
191 * @method fromJSON
179 * @method fromJSON
192 */
180 */
193 TextCell.prototype.fromJSON = function (data) {
181 TextCell.prototype.fromJSON = function (data) {
194 IPython.Cell.prototype.fromJSON.apply(this, arguments);
182 Cell.prototype.fromJSON.apply(this, arguments);
195 if (data.cell_type === this.cell_type) {
183 if (data.cell_type === this.cell_type) {
196 if (data.source !== undefined) {
184 if (data.source !== undefined) {
197 this.set_text(data.source);
185 this.set_text(data.source);
198 // make this value the starting point, so that we can only undo
186 // make this value the starting point, so that we can only undo
199 // to this state, instead of a blank cell
187 // to this state, instead of a blank cell
200 this.code_mirror.clearHistory();
188 this.code_mirror.clearHistory();
201 // TODO: This HTML needs to be treated as potentially dangerous
189 // TODO: This HTML needs to be treated as potentially dangerous
202 // user input and should be handled before set_rendered.
190 // user input and should be handled before set_rendered.
203 this.set_rendered(data.rendered || '');
191 this.set_rendered(data.rendered || '');
204 this.rendered = false;
192 this.rendered = false;
205 this.render();
193 this.render();
206 }
194 }
207 }
195 }
208 };
196 };
209
197
210 /** Generate JSON from cell
198 /** Generate JSON from cell
211 * @return {object} cell data serialised to json
199 * @return {object} cell data serialised to json
212 */
200 */
213 TextCell.prototype.toJSON = function () {
201 TextCell.prototype.toJSON = function () {
214 var data = IPython.Cell.prototype.toJSON.apply(this);
202 var data = Cell.prototype.toJSON.apply(this);
215 data.source = this.get_text();
203 data.source = this.get_text();
216 if (data.source == this.placeholder) {
204 if (data.source == this.placeholder) {
217 data.source = "";
205 data.source = "";
218 }
206 }
219 return data;
207 return data;
220 };
208 };
221
209
222
210
223 /**
211 /**
224 * @class MarkdownCell
212 * @class MarkdownCell
225 * @constructor MarkdownCell
213 * @constructor MarkdownCell
226 * @extends IPython.HTMLCell
214 * @extends IPython.HTMLCell
227 */
215 */
228 var MarkdownCell = function (options) {
216 var MarkdownCell = function (options) {
229 options = this.mergeopt(MarkdownCell, options);
217 options = this.mergeopt(MarkdownCell, options);
230
218
231 this.cell_type = 'markdown';
219 this.cell_type = 'markdown';
232 TextCell.apply(this, [options]);
220 TextCell.apply(this, [options]);
233 };
221 };
234
222
235 MarkdownCell.options_default = {
223 MarkdownCell.options_default = {
236 cm_config: {
224 cm_config: {
237 mode: 'ipythongfm'
225 mode: 'ipythongfm'
238 },
226 },
239 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
227 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
240 };
228 };
241
229
242 MarkdownCell.prototype = new TextCell();
230 MarkdownCell.prototype = new TextCell();
243
231
244 /**
232 /**
245 * @method render
233 * @method render
246 */
234 */
247 MarkdownCell.prototype.render = function () {
235 MarkdownCell.prototype.render = function () {
248 var cont = IPython.TextCell.prototype.render.apply(this);
236 var cont = TextCell.prototype.render.apply(this);
249 if (cont) {
237 if (cont) {
250 var text = this.get_text();
238 var text = this.get_text();
251 var math = null;
239 var math = null;
252 if (text === "") { text = this.placeholder; }
240 if (text === "") { text = this.placeholder; }
253 var text_and_math = IPython.mathjaxutils.remove_math(text);
241 var text_and_math = this.mathjaxutils.remove_math(text);
254 text = text_and_math[0];
242 text = text_and_math[0];
255 math = text_and_math[1];
243 math = text_and_math[1];
256 var html = marked.parser(marked.lexer(text));
244 var html = marked.parser(marked.lexer(text));
257 html = IPython.mathjaxutils.replace_math(html, math);
245 html = this.mathjaxutils.replace_math(html, math);
258 html = security.sanitize_html(html);
246 html = Security.sanitize_html(html);
259 html = $($.parseHTML(html));
247 html = $($.parseHTML(html));
260 // links in markdown cells should open in new tabs
248 // links in markdown cells should open in new tabs
261 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
249 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
262 this.set_rendered(html);
250 this.set_rendered(html);
263 this.element.find('div.input_area').hide();
251 this.element.find('div.input_area').hide();
264 this.element.find("div.text_cell_render").show();
252 this.element.find("div.text_cell_render").show();
265 this.typeset();
253 this.typeset();
266 }
254 }
267 return cont;
255 return cont;
268 };
256 };
269
257
270
258
271 // RawCell
259 // RawCell
272
260
273 /**
261 /**
274 * @class RawCell
262 * @class RawCell
275 * @constructor RawCell
263 * @constructor RawCell
276 * @extends IPython.TextCell
264 * @extends TextCell
277 */
265 */
278 var RawCell = function (options) {
266 var RawCell = function (options) {
279
267
280 options = this.mergeopt(RawCell,options);
268 options = this.mergeopt(RawCell,options);
281 TextCell.apply(this, [options]);
269 TextCell.apply(this, [options]);
282 this.cell_type = 'raw';
270 this.cell_type = 'raw';
283 // RawCell should always hide its rendered div
271 // RawCell should always hide its rendered div
284 this.element.find('div.text_cell_render').hide();
272 this.element.find('div.text_cell_render').hide();
285 };
273 };
286
274
287 RawCell.options_default = {
275 RawCell.options_default = {
288 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
276 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
289 "It will not be rendered in the notebook. " +
277 "It will not be rendered in the notebook. " +
290 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
278 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
291 };
279 };
292
280
293 RawCell.prototype = new TextCell();
281 RawCell.prototype = new TextCell();
294
282
295 /** @method bind_events **/
283 /** @method bind_events **/
296 RawCell.prototype.bind_events = function () {
284 RawCell.prototype.bind_events = function () {
297 TextCell.prototype.bind_events.apply(this);
285 TextCell.prototype.bind_events.apply(this);
298 var that = this;
286 var that = this;
299 this.element.focusout(function() {
287 this.element.focusout(function() {
300 that.auto_highlight();
288 that.auto_highlight();
301 that.render();
289 that.render();
302 });
290 });
303
291
304 this.code_mirror.on('focus', function() { that.unrender(); });
292 this.code_mirror.on('focus', function() { that.unrender(); });
305 };
293 };
306
294
307 /**
295 /**
308 * Trigger autodetection of highlight scheme for current cell
296 * Trigger autodetection of highlight scheme for current cell
309 * @method auto_highlight
297 * @method auto_highlight
310 */
298 */
311 RawCell.prototype.auto_highlight = function () {
299 RawCell.prototype.auto_highlight = function () {
312 this._auto_highlight(IPython.config.raw_cell_highlight);
300 this._auto_highlight(config.raw_cell_highlight);
313 };
301 };
314
302
315 /** @method render **/
303 /** @method render **/
316 RawCell.prototype.render = function () {
304 RawCell.prototype.render = function () {
317 var cont = IPython.TextCell.prototype.render.apply(this);
305 var cont = TextCell.prototype.render.apply(this);
318 if (cont){
306 if (cont){
319 var text = this.get_text();
307 var text = this.get_text();
320 if (text === "") { text = this.placeholder; }
308 if (text === "") { text = this.placeholder; }
321 this.set_text(text);
309 this.set_text(text);
322 this.element.removeClass('rendered');
310 this.element.removeClass('rendered');
323 }
311 }
324 return cont;
312 return cont;
325 };
313 };
326
314
327
315
328 /**
316 /**
329 * @class HeadingCell
317 * @class HeadingCell
330 * @extends IPython.TextCell
318 * @extends TextCell
331 */
319 */
332
320
333 /**
321 /**
334 * @constructor HeadingCell
322 * @constructor HeadingCell
335 * @extends IPython.TextCell
323 * @extends TextCell
336 */
324 */
337 var HeadingCell = function (options) {
325 var HeadingCell = function (options) {
338 options = this.mergeopt(HeadingCell, options);
326 options = this.mergeopt(HeadingCell, options);
339
327
340 this.level = 1;
328 this.level = 1;
341 this.cell_type = 'heading';
329 this.cell_type = 'heading';
342 TextCell.apply(this, [options]);
330 TextCell.apply(this, [options]);
343
331
344 /**
332 /**
345 * heading level of the cell, use getter and setter to access
333 * heading level of the cell, use getter and setter to access
346 * @property level
334 * @property level
347 */
335 */
348 };
336 };
349
337
350 HeadingCell.options_default = {
338 HeadingCell.options_default = {
351 placeholder: "Type Heading Here"
339 placeholder: "Type Heading Here"
352 };
340 };
353
341
354 HeadingCell.prototype = new TextCell();
342 HeadingCell.prototype = new TextCell();
355
343
356 /** @method fromJSON */
344 /** @method fromJSON */
357 HeadingCell.prototype.fromJSON = function (data) {
345 HeadingCell.prototype.fromJSON = function (data) {
358 if (data.level !== undefined){
346 if (data.level !== undefined){
359 this.level = data.level;
347 this.level = data.level;
360 }
348 }
361 TextCell.prototype.fromJSON.apply(this, arguments);
349 TextCell.prototype.fromJSON.apply(this, arguments);
362 };
350 };
363
351
364
352
365 /** @method toJSON */
353 /** @method toJSON */
366 HeadingCell.prototype.toJSON = function () {
354 HeadingCell.prototype.toJSON = function () {
367 var data = TextCell.prototype.toJSON.apply(this);
355 var data = TextCell.prototype.toJSON.apply(this);
368 data.level = this.get_level();
356 data.level = this.get_level();
369 return data;
357 return data;
370 };
358 };
371
359
372 /**
360 /**
373 * can the cell be split into two cells
361 * can the cell be split into two cells
374 * @method is_splittable
362 * @method is_splittable
375 **/
363 **/
376 HeadingCell.prototype.is_splittable = function () {
364 HeadingCell.prototype.is_splittable = function () {
377 return false;
365 return false;
378 };
366 };
379
367
380
368
381 /**
369 /**
382 * can the cell be merged with other cells
370 * can the cell be merged with other cells
383 * @method is_mergeable
371 * @method is_mergeable
384 **/
372 **/
385 HeadingCell.prototype.is_mergeable = function () {
373 HeadingCell.prototype.is_mergeable = function () {
386 return false;
374 return false;
387 };
375 };
388
376
389 /**
377 /**
390 * Change heading level of cell, and re-render
378 * Change heading level of cell, and re-render
391 * @method set_level
379 * @method set_level
392 */
380 */
393 HeadingCell.prototype.set_level = function (level) {
381 HeadingCell.prototype.set_level = function (level) {
394 this.level = level;
382 this.level = level;
395 if (this.rendered) {
383 if (this.rendered) {
396 this.rendered = false;
384 this.rendered = false;
397 this.render();
385 this.render();
398 }
386 }
399 };
387 };
400
388
401 /** The depth of header cell, based on html (h1 to h6)
389 /** The depth of header cell, based on html (h1 to h6)
402 * @method get_level
390 * @method get_level
403 * @return {integer} level - for 1 to 6
391 * @return {integer} level - for 1 to 6
404 */
392 */
405 HeadingCell.prototype.get_level = function () {
393 HeadingCell.prototype.get_level = function () {
406 return this.level;
394 return this.level;
407 };
395 };
408
396
409
397
410 HeadingCell.prototype.get_rendered = function () {
398 HeadingCell.prototype.get_rendered = function () {
411 var r = this.element.find("div.text_cell_render");
399 var r = this.element.find("div.text_cell_render");
412 return r.children().first().html();
400 return r.children().first().html();
413 };
401 };
414
402
415
416 HeadingCell.prototype.render = function () {
403 HeadingCell.prototype.render = function () {
417 var cont = IPython.TextCell.prototype.render.apply(this);
404 var cont = TextCell.prototype.render.apply(this);
418 if (cont) {
405 if (cont) {
419 var text = this.get_text();
406 var text = this.get_text();
420 var math = null;
407 var math = null;
421 // Markdown headings must be a single line
408 // Markdown headings must be a single line
422 text = text.replace(/\n/g, ' ');
409 text = text.replace(/\n/g, ' ');
423 if (text === "") { text = this.placeholder; }
410 if (text === "") { text = this.placeholder; }
424 text = Array(this.level + 1).join("#") + " " + text;
411 text = new Array(this.level + 1).join("#") + " " + text;
425 var text_and_math = IPython.mathjaxutils.remove_math(text);
412 var text_and_math = this.mathjaxutils.remove_math(text);
426 text = text_and_math[0];
413 text = text_and_math[0];
427 math = text_and_math[1];
414 math = text_and_math[1];
428 var html = marked.parser(marked.lexer(text));
415 var html = marked.parser(marked.lexer(text));
429 html = IPython.mathjaxutils.replace_math(html, math);
416 html = this.mathjaxutils.replace_math(html, math);
430 html = security.sanitize_html(html);
417 html = Security.sanitize_html(html);
431 var h = $($.parseHTML(html));
418 var h = $($.parseHTML(html));
432 // add id and linkback anchor
419 // add id and linkback anchor
433 var hash = h.text().replace(/ /g, '-');
420 var hash = h.text().replace(/ /g, '-');
434 h.attr('id', hash);
421 h.attr('id', hash);
435 h.append(
422 h.append(
436 $('<a/>')
423 $('<a/>')
437 .addClass('anchor-link')
424 .addClass('anchor-link')
438 .attr('href', '#' + hash)
425 .attr('href', '#' + hash)
439 .text('ΒΆ')
426 .text('ΒΆ')
440 );
427 );
441 this.set_rendered(h);
428 this.set_rendered(h);
442 this.element.find('div.input_area').hide();
429 this.element.find('div.input_area').hide();
443 this.element.find("div.text_cell_render").show();
430 this.element.find("div.text_cell_render").show();
444 this.typeset();
431 this.typeset();
445 }
432 }
446 return cont;
433 return cont;
447 };
434 };
448
435
449 // TODO: RETURN IN THIS NAMESPACE!
436 // Backwards compatability.
450 IPython.TextCell = TextCell;
437 IPython.TextCell = TextCell;
451 IPython.MarkdownCell = MarkdownCell;
438 IPython.MarkdownCell = MarkdownCell;
452 IPython.RawCell = RawCell;
439 IPython.RawCell = RawCell;
453 IPython.HeadingCell = HeadingCell;
440 IPython.HeadingCell = HeadingCell;
454
441
455
442 Cells = {
456 return IPython;
443 'TextCell': TextCell,
457
444 'MarkdownCell': MarkdownCell,
458 }(IPython));
445 'RawCell': RawCell,
459
446 'HeadingCell': HeadingCell,
447 };
448 return Cells;
449 });
@@ -1,111 +1,101 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // ToolBar
5 'base/js/namespace',
10 //============================================================================
6 'components/jquery/jquery.min',
11 /**
7 ], function(IPython, $) {
12 * @module IPython
13 * @namespace IPython
14 * @submodule ToolBar
15 */
16
17 var IPython = (function (IPython) {
18 "use strict";
8 "use strict";
19
9
20 /**
10 /**
21 * A generic toolbar on which one can add button
11 * A generic toolbar on which one can add button
22 * @class ToolBar
12 * @class ToolBar
23 * @constructor
13 * @constructor
24 * @param {Dom object} selector
14 * @param {Dom object} selector
25 */
15 */
26 var ToolBar = function (selector) {
16 var ToolBar = function (selector, layout_manager) {
27 this.selector = selector;
17 this.selector = selector;
18 this.layout_manager = layout_manager;
28 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
29 this.element = $(selector);
20 this.element = $(selector);
30 this.style();
21 this.style();
31 }
22 }
32 };
23 };
33
24
34 /**
25 /**
35 * add a group of button into the current toolbar.
26 * add a group of button into the current toolbar.
36 *
27 *
37 *
28 *
38 * @example
29 * @example
39 *
30 *
40 * IPython.toolbar.add_buttons_group([
31 * IPython.toolbar.add_buttons_group([
41 * {
32 * {
42 * label:'my button',
33 * label:'my button',
43 * icon:'icon-hdd',
34 * icon:'icon-hdd',
44 * callback:function(){alert('hoho')},
35 * callback:function(){alert('hoho')},
45 * id : 'my_button_id', // this is optional
36 * id : 'my_button_id', // this is optional
46 * },
37 * },
47 * {
38 * {
48 * label:'my second button',
39 * label:'my second button',
49 * icon:'icon-play',
40 * icon:'icon-play',
50 * callback:function(){alert('be carefull I cut')}
41 * callback:function(){alert('be carefull I cut')}
51 * }
42 * }
52 * ],
43 * ],
53 * "my_button_group_id"
44 * "my_button_group_id"
54 * )
45 * )
55 *
46 *
56 * @method add_buttons_group
47 * @method add_buttons_group
57 * @param list {List}
48 * @param list {List}
58 * List of button of the group, with the following paramter for each :
49 * List of button of the group, with the following paramter for each :
59 * @param list.label {string} text to show on button hover
50 * @param list.label {string} text to show on button hover
60 * @param list.icon {string} icon to choose from [Font Awesome](http://fortawesome.github.io/Font-Awesome)
51 * @param list.icon {string} icon to choose from [Font Awesome](http://fortawesome.github.io/Font-Awesome)
61 * @param list.callback {function} function to be called on button click
52 * @param list.callback {function} function to be called on button click
62 * @param [list.id] {String} id to give to the button
53 * @param [list.id] {String} id to give to the button
63 * @param [group_id] {String} optionnal id to give to the group
54 * @param [group_id] {String} optionnal id to give to the group
64 *
55 *
65 */
56 */
66 ToolBar.prototype.add_buttons_group = function (list, group_id) {
57 ToolBar.prototype.add_buttons_group = function (list, group_id) {
67 var btn_group = $('<div/>').addClass("btn-group");
58 var btn_group = $('<div/>').addClass("btn-group");
68 if( group_id !== undefined ) {
59 if( group_id !== undefined ) {
69 btn_group.attr('id',group_id);
60 btn_group.attr('id',group_id);
70 }
61 }
71 var el;
62 var el;
72 for(var i=0; i < list.length; i++) {
63 for(var i=0; i < list.length; i++) {
73 el = list[i];
64 el = list[i];
74 var button = $('<button/>')
65 var button = $('<button/>')
75 .addClass('btn btn-default')
66 .addClass('btn btn-default')
76 .attr("title", el.label)
67 .attr("title", el.label)
77 .append(
68 .append(
78 $("<i/>").addClass(el.icon)
69 $("<i/>").addClass(el.icon)
79 );
70 );
80 var id = el.id;
71 var id = el.id;
81 if( id !== undefined )
72 if( id !== undefined )
82 button.attr('id',id);
73 button.attr('id',id);
83 var fun = el.callback;
74 var fun = el.callback;
84 button.click(fun);
75 button.click(fun);
85 btn_group.append(button);
76 btn_group.append(button);
86 }
77 }
87 $(this.selector).append(btn_group);
78 $(this.selector).append(btn_group);
88 };
79 };
89
80
90 ToolBar.prototype.style = function () {
81 ToolBar.prototype.style = function () {
91 this.element.addClass('border-box-sizing')
82 this.element.addClass('border-box-sizing')
92 .addClass('toolbar');
83 .addClass('toolbar');
93 };
84 };
94
85
95 /**
86 /**
96 * Show and hide toolbar
87 * Show and hide toolbar
97 * @method toggle
88 * @method toggle
98 */
89 */
99 ToolBar.prototype.toggle = function () {
90 ToolBar.prototype.toggle = function () {
100 this.element.toggle();
91 this.element.toggle();
101 if (IPython.layout_manager !== undefined) {
92 if (this.layout_manager !== undefined) {
102 IPython.layout_manager.do_resize();
93 this.layout_manager.do_resize();
103 }
94 }
104 };
95 };
105
96
106
97 // Backwards compatability.
107 IPython.ToolBar = ToolBar;
98 IPython.ToolBar = ToolBar;
108
99
109 return IPython;
100 return Toolbar;
110
101 });
111 }(IPython));
@@ -1,114 +1,114 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 var ipython = ipython || {};
4 var ipython = ipython || {};
5 require([
5 require([
6 'base/js/namespace',
6 'base/js/namespace',
7 'components/jquery/jquery.min',
7 'components/jquery/jquery.min',
8 'base/js/events',
8 'base/js/events',
9 'base/js/page',
9 'base/js/page',
10 'base/js/utils',
10 'base/js/utils',
11 'tree/js/notebooklist',
11 'tree/js/notebooklist',
12 'tree/js/clusterlist',
12 'tree/js/clusterlist',
13 'tree/js/sessionlist',
13 'tree/js/sessionlist',
14 'tree/js/kernellist',
14 'tree/js/kernellist',
15 'auth/js/loginwidget',
15 'auth/js/loginwidget',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
17 'components/bootstrap/js/bootstrap.min',
17 'components/bootstrap/js/bootstrap.min',
18 ], function(
18 ], function(
19 IPython,
19 IPython,
20 $,
20 $,
21 Events,
21 Events,
22 Page,
22 Page,
23 Utils,
23 Utils,
24 NotebookList,
24 NotebookList,
25 ClusterList,
25 ClusterList,
26 SesssionList,
26 SesssionList,
27 KernelList,
27 KernelList,
28 LoginWidget){
28 LoginWidget){
29
29
30 page = new Page();
30 page = new Page();
31
31
32 var opts = {
32 var opts = {
33 base_url: Utils.get_body_data("baseUrl"),
33 base_url: Utils.get_body_data("baseUrl"),
34 notebook_path: Utils.get_body_data("notebookPath"),
34 notebook_path: Utils.get_body_data("notebookPath"),
35 };
35 };
36 events = new Events();
36 events = $([new Events()]);
37 session_list = new SesssionList(opts, events);
37 session_list = new SesssionList(opts, events);
38 notebook_list = new NotebookList('#notebook_list', opts, undefined, session_list);
38 notebook_list = new NotebookList('#notebook_list', opts, undefined, session_list);
39 cluster_list = new ClusterList('#cluster_list', opts);
39 cluster_list = new ClusterList('#cluster_list', opts);
40 kernel_list = new KernelList('#running_list', opts, session_list);
40 kernel_list = new KernelList('#running_list', opts, session_list);
41 login_widget = new LoginWidget('#login_widget', opts);
41 login_widget = new LoginWidget('#login_widget', opts);
42
42
43 $('#new_notebook').button().click(function (e) {
43 $('#new_notebook').button().click(function (e) {
44 notebook_list.new_notebook();
44 notebook_list.new_notebook();
45 });
45 });
46
46
47 var interval_id=0;
47 var interval_id=0;
48 // auto refresh every xx secondes, no need to be fast,
48 // auto refresh every xx secondes, no need to be fast,
49 // update is done at least when page get focus
49 // update is done at least when page get focus
50 var time_refresh = 60; // in sec
50 var time_refresh = 60; // in sec
51
51
52 var enable_autorefresh = function(){
52 var enable_autorefresh = function(){
53 //refresh immediately , then start interval
53 //refresh immediately , then start interval
54 if($('.upload_button').length === 0)
54 if($('.upload_button').length === 0)
55 {
55 {
56 session_list.load_sessions();
56 session_list.load_sessions();
57 cluster_list.load_list();
57 cluster_list.load_list();
58 }
58 }
59 if (!interval_id){
59 if (!interval_id){
60 interval_id = setInterval(function(){
60 interval_id = setInterval(function(){
61 if($('.upload_button').length === 0)
61 if($('.upload_button').length === 0)
62 {
62 {
63 session_list.load_sessions();
63 session_list.load_sessions();
64 cluster_list.load_list();
64 cluster_list.load_list();
65 }
65 }
66 }, time_refresh*1000);
66 }, time_refresh*1000);
67 }
67 }
68 };
68 };
69
69
70 var disable_autorefresh = function(){
70 var disable_autorefresh = function(){
71 clearInterval(interval_id);
71 clearInterval(interval_id);
72 interval_id = 0;
72 interval_id = 0;
73 };
73 };
74
74
75 // stop autorefresh when page lose focus
75 // stop autorefresh when page lose focus
76 $(window).blur(function() {
76 $(window).blur(function() {
77 disable_autorefresh();
77 disable_autorefresh();
78 });
78 });
79
79
80 //re-enable when page get focus back
80 //re-enable when page get focus back
81 $(window).focus(function() {
81 $(window).focus(function() {
82 enable_autorefresh();
82 enable_autorefresh();
83 });
83 });
84
84
85 // finally start it, it will refresh immediately
85 // finally start it, it will refresh immediately
86 enable_autorefresh();
86 enable_autorefresh();
87
87
88 page.show();
88 page.show();
89 events.trigger('app_initialized.DashboardApp');
89 events.trigger('app_initialized.DashboardApp');
90
90
91 // bound the upload method to the on change of the file select list
91 // bound the upload method to the on change of the file select list
92 $("#alternate_upload").change(function (event){
92 $("#alternate_upload").change(function (event){
93 notebook_list.handleFilesUpload(event,'form');
93 notebook_list.handleFilesUpload(event,'form');
94 });
94 });
95
95
96 // set hash on tab click
96 // set hash on tab click
97 $("#tabs").find("a").click(function() {
97 $("#tabs").find("a").click(function() {
98 window.location.hash = $(this).attr("href");
98 window.location.hash = $(this).attr("href");
99 });
99 });
100
100
101 // load tab if url hash
101 // load tab if url hash
102 if (window.location.hash) {
102 if (window.location.hash) {
103 $("#tabs").find("a[href=" + window.location.hash + "]").click();
103 $("#tabs").find("a[href=" + window.location.hash + "]").click();
104 }
104 }
105
105
106 // For backwards compatability.
106 // For backwards compatability.
107 ipython.page = page;
107 ipython.page = page;
108 ipython.notebook_list = notebook_list;
108 ipython.notebook_list = notebook_list;
109 ipython.cluster_list = cluster_list;
109 ipython.cluster_list = cluster_list;
110 ipython.session_list = session_list;
110 ipython.session_list = session_list;
111 ipython.kernel_list = kernel_list;
111 ipython.kernel_list = kernel_list;
112 ipython.login_widget = login_widget;
112 ipython.login_widget = login_widget;
113 ipython.events = events;
113 ipython.events = events;
114 });
114 });
General Comments 0
You need to be logged in to leave comments. Login now