##// END OF EJS Templates
Changed the display order of rich output in the live notebook to match that of nbconvert. This makes more sense than the other way around due to the need for notebooks already shared on the web to remain consistant. Conversely local notebooks can simply be edited to match the new order.
damontallen -
Show More
@@ -1,980 +1,980
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 'jqueryui',
6 'jqueryui',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/security',
8 'base/js/security',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 'components/marked/lib/marked',
11 'components/marked/lib/marked',
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
13 "use strict";
13 "use strict";
14
14
15 /**
15 /**
16 * @class OutputArea
16 * @class OutputArea
17 *
17 *
18 * @constructor
18 * @constructor
19 */
19 */
20
20
21 var OutputArea = function (options) {
21 var OutputArea = function (options) {
22 this.selector = options.selector;
22 this.selector = options.selector;
23 this.events = options.events;
23 this.events = options.events;
24 this.keyboard_manager = options.keyboard_manager;
24 this.keyboard_manager = options.keyboard_manager;
25 this.wrapper = $(options.selector);
25 this.wrapper = $(options.selector);
26 this.outputs = [];
26 this.outputs = [];
27 this.collapsed = false;
27 this.collapsed = false;
28 this.scrolled = false;
28 this.scrolled = false;
29 this.trusted = true;
29 this.trusted = true;
30 this.clear_queued = null;
30 this.clear_queued = null;
31 if (options.prompt_area === undefined) {
31 if (options.prompt_area === undefined) {
32 this.prompt_area = true;
32 this.prompt_area = true;
33 } else {
33 } else {
34 this.prompt_area = options.prompt_area;
34 this.prompt_area = options.prompt_area;
35 }
35 }
36 this.create_elements();
36 this.create_elements();
37 this.style();
37 this.style();
38 this.bind_events();
38 this.bind_events();
39 };
39 };
40
40
41
41
42 /**
42 /**
43 * Class prototypes
43 * Class prototypes
44 **/
44 **/
45
45
46 OutputArea.prototype.create_elements = function () {
46 OutputArea.prototype.create_elements = function () {
47 this.element = $("<div/>");
47 this.element = $("<div/>");
48 this.collapse_button = $("<div/>");
48 this.collapse_button = $("<div/>");
49 this.prompt_overlay = $("<div/>");
49 this.prompt_overlay = $("<div/>");
50 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.prompt_overlay);
51 this.wrapper.append(this.element);
51 this.wrapper.append(this.element);
52 this.wrapper.append(this.collapse_button);
52 this.wrapper.append(this.collapse_button);
53 };
53 };
54
54
55
55
56 OutputArea.prototype.style = function () {
56 OutputArea.prototype.style = function () {
57 this.collapse_button.hide();
57 this.collapse_button.hide();
58 this.prompt_overlay.hide();
58 this.prompt_overlay.hide();
59
59
60 this.wrapper.addClass('output_wrapper');
60 this.wrapper.addClass('output_wrapper');
61 this.element.addClass('output');
61 this.element.addClass('output');
62
62
63 this.collapse_button.addClass("btn btn-default output_collapsed");
63 this.collapse_button.addClass("btn btn-default output_collapsed");
64 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.attr('title', 'click to expand output');
65 this.collapse_button.text('. . .');
65 this.collapse_button.text('. . .');
66
66
67 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.addClass('out_prompt_overlay prompt');
68 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
69
69
70 this.collapse();
70 this.collapse();
71 };
71 };
72
72
73 /**
73 /**
74 * Should the OutputArea scroll?
74 * Should the OutputArea scroll?
75 * Returns whether the height (in lines) exceeds a threshold.
75 * Returns whether the height (in lines) exceeds a threshold.
76 *
76 *
77 * @private
77 * @private
78 * @method _should_scroll
78 * @method _should_scroll
79 * @param [lines=100]{Integer}
79 * @param [lines=100]{Integer}
80 * @return {Bool}
80 * @return {Bool}
81 *
81 *
82 */
82 */
83 OutputArea.prototype._should_scroll = function (lines) {
83 OutputArea.prototype._should_scroll = function (lines) {
84 if (lines <=0 ){ return; }
84 if (lines <=0 ){ return; }
85 if (!lines) {
85 if (!lines) {
86 lines = 100;
86 lines = 100;
87 }
87 }
88 // line-height from http://stackoverflow.com/questions/1185151
88 // line-height from http://stackoverflow.com/questions/1185151
89 var fontSize = this.element.css('font-size');
89 var fontSize = this.element.css('font-size');
90 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
91
91
92 return (this.element.height() > lines * lineHeight);
92 return (this.element.height() > lines * lineHeight);
93 };
93 };
94
94
95
95
96 OutputArea.prototype.bind_events = function () {
96 OutputArea.prototype.bind_events = function () {
97 var that = this;
97 var that = this;
98 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
99 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99 this.prompt_overlay.click(function () { that.toggle_scroll(); });
100
100
101 this.element.resize(function () {
101 this.element.resize(function () {
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
103 if ( utils.browser[0] === "Firefox" ) {
103 if ( utils.browser[0] === "Firefox" ) {
104 return;
104 return;
105 }
105 }
106 // maybe scroll output,
106 // maybe scroll output,
107 // if it's grown large enough and hasn't already been scrolled.
107 // if it's grown large enough and hasn't already been scrolled.
108 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
109 that.scroll_area();
109 that.scroll_area();
110 }
110 }
111 });
111 });
112 this.collapse_button.click(function () {
112 this.collapse_button.click(function () {
113 that.expand();
113 that.expand();
114 });
114 });
115 };
115 };
116
116
117
117
118 OutputArea.prototype.collapse = function () {
118 OutputArea.prototype.collapse = function () {
119 if (!this.collapsed) {
119 if (!this.collapsed) {
120 this.element.hide();
120 this.element.hide();
121 this.prompt_overlay.hide();
121 this.prompt_overlay.hide();
122 if (this.element.html()){
122 if (this.element.html()){
123 this.collapse_button.show();
123 this.collapse_button.show();
124 }
124 }
125 this.collapsed = true;
125 this.collapsed = true;
126 }
126 }
127 };
127 };
128
128
129
129
130 OutputArea.prototype.expand = function () {
130 OutputArea.prototype.expand = function () {
131 if (this.collapsed) {
131 if (this.collapsed) {
132 this.collapse_button.hide();
132 this.collapse_button.hide();
133 this.element.show();
133 this.element.show();
134 this.prompt_overlay.show();
134 this.prompt_overlay.show();
135 this.collapsed = false;
135 this.collapsed = false;
136 }
136 }
137 };
137 };
138
138
139
139
140 OutputArea.prototype.toggle_output = function () {
140 OutputArea.prototype.toggle_output = function () {
141 if (this.collapsed) {
141 if (this.collapsed) {
142 this.expand();
142 this.expand();
143 } else {
143 } else {
144 this.collapse();
144 this.collapse();
145 }
145 }
146 };
146 };
147
147
148
148
149 OutputArea.prototype.scroll_area = function () {
149 OutputArea.prototype.scroll_area = function () {
150 this.element.addClass('output_scroll');
150 this.element.addClass('output_scroll');
151 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
152 this.scrolled = true;
152 this.scrolled = true;
153 };
153 };
154
154
155
155
156 OutputArea.prototype.unscroll_area = function () {
156 OutputArea.prototype.unscroll_area = function () {
157 this.element.removeClass('output_scroll');
157 this.element.removeClass('output_scroll');
158 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
159 this.scrolled = false;
159 this.scrolled = false;
160 };
160 };
161
161
162 /**
162 /**
163 *
163 *
164 * Scroll OutputArea if height supperior than a threshold (in lines).
164 * Scroll OutputArea if height supperior than a threshold (in lines).
165 *
165 *
166 * Threshold is a maximum number of lines. If unspecified, defaults to
166 * Threshold is a maximum number of lines. If unspecified, defaults to
167 * OutputArea.minimum_scroll_threshold.
167 * OutputArea.minimum_scroll_threshold.
168 *
168 *
169 * Negative threshold will prevent the OutputArea from ever scrolling.
169 * Negative threshold will prevent the OutputArea from ever scrolling.
170 *
170 *
171 * @method scroll_if_long
171 * @method scroll_if_long
172 *
172 *
173 * @param [lines=20]{Number} Default to 20 if not set,
173 * @param [lines=20]{Number} Default to 20 if not set,
174 * behavior undefined for value of `0`.
174 * behavior undefined for value of `0`.
175 *
175 *
176 **/
176 **/
177 OutputArea.prototype.scroll_if_long = function (lines) {
177 OutputArea.prototype.scroll_if_long = function (lines) {
178 var n = lines | OutputArea.minimum_scroll_threshold;
178 var n = lines | OutputArea.minimum_scroll_threshold;
179 if(n <= 0){
179 if(n <= 0){
180 return;
180 return;
181 }
181 }
182
182
183 if (this._should_scroll(n)) {
183 if (this._should_scroll(n)) {
184 // only allow scrolling long-enough output
184 // only allow scrolling long-enough output
185 this.scroll_area();
185 this.scroll_area();
186 }
186 }
187 };
187 };
188
188
189
189
190 OutputArea.prototype.toggle_scroll = function () {
190 OutputArea.prototype.toggle_scroll = function () {
191 if (this.scrolled) {
191 if (this.scrolled) {
192 this.unscroll_area();
192 this.unscroll_area();
193 } else {
193 } else {
194 // only allow scrolling long-enough output
194 // only allow scrolling long-enough output
195 this.scroll_if_long();
195 this.scroll_if_long();
196 }
196 }
197 };
197 };
198
198
199
199
200 // typeset with MathJax if MathJax is available
200 // typeset with MathJax if MathJax is available
201 OutputArea.prototype.typeset = function () {
201 OutputArea.prototype.typeset = function () {
202 if (window.MathJax){
202 if (window.MathJax){
203 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
203 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
204 }
204 }
205 };
205 };
206
206
207
207
208 OutputArea.prototype.handle_output = function (msg) {
208 OutputArea.prototype.handle_output = function (msg) {
209 var json = {};
209 var json = {};
210 var msg_type = json.output_type = msg.header.msg_type;
210 var msg_type = json.output_type = msg.header.msg_type;
211 var content = msg.content;
211 var content = msg.content;
212 if (msg_type === "stream") {
212 if (msg_type === "stream") {
213 json.text = content.text;
213 json.text = content.text;
214 json.name = content.name;
214 json.name = content.name;
215 } else if (msg_type === "display_data") {
215 } else if (msg_type === "display_data") {
216 json.data = content.data;
216 json.data = content.data;
217 json.output_type = msg_type;
217 json.output_type = msg_type;
218 json.metadata = content.metadata;
218 json.metadata = content.metadata;
219 } else if (msg_type === "execute_result") {
219 } else if (msg_type === "execute_result") {
220 json.data = content.data;
220 json.data = content.data;
221 json.output_type = msg_type;
221 json.output_type = msg_type;
222 json.metadata = content.metadata;
222 json.metadata = content.metadata;
223 json.execution_count = content.execution_count;
223 json.execution_count = content.execution_count;
224 } else if (msg_type === "error") {
224 } else if (msg_type === "error") {
225 json.ename = content.ename;
225 json.ename = content.ename;
226 json.evalue = content.evalue;
226 json.evalue = content.evalue;
227 json.traceback = content.traceback;
227 json.traceback = content.traceback;
228 } else {
228 } else {
229 console.log("unhandled output message", msg);
229 console.log("unhandled output message", msg);
230 return;
230 return;
231 }
231 }
232 this.append_output(json);
232 this.append_output(json);
233 };
233 };
234
234
235
235
236 OutputArea.output_types = [
236 OutputArea.output_types = [
237 'application/javascript',
237 'application/javascript',
238 'text/html',
238 'text/html',
239 'text/markdown',
239 'text/markdown',
240 'text/latex',
240 'text/latex',
241 'image/svg+xml',
241 'image/svg+xml',
242 'image/png',
242 'image/png',
243 'image/jpeg',
243 'image/jpeg',
244 'application/pdf',
244 'application/pdf',
245 'text/plain'
245 'text/plain'
246 ];
246 ];
247
247
248 OutputArea.prototype.validate_mimebundle = function (json) {
248 OutputArea.prototype.validate_mimebundle = function (json) {
249 /**
249 /**
250 * scrub invalid outputs
250 * scrub invalid outputs
251 */
251 */
252 var data = json.data;
252 var data = json.data;
253 $.map(OutputArea.output_types, function(key){
253 $.map(OutputArea.output_types, function(key){
254 if (key !== 'application/json' &&
254 if (key !== 'application/json' &&
255 data[key] !== undefined &&
255 data[key] !== undefined &&
256 typeof data[key] !== 'string'
256 typeof data[key] !== 'string'
257 ) {
257 ) {
258 console.log("Invalid type for " + key, data[key]);
258 console.log("Invalid type for " + key, data[key]);
259 delete data[key];
259 delete data[key];
260 }
260 }
261 });
261 });
262 return json;
262 return json;
263 };
263 };
264
264
265 OutputArea.prototype.append_output = function (json) {
265 OutputArea.prototype.append_output = function (json) {
266 this.expand();
266 this.expand();
267
267
268 // Clear the output if clear is queued.
268 // Clear the output if clear is queued.
269 var needs_height_reset = false;
269 var needs_height_reset = false;
270 if (this.clear_queued) {
270 if (this.clear_queued) {
271 this.clear_output(false);
271 this.clear_output(false);
272 needs_height_reset = true;
272 needs_height_reset = true;
273 }
273 }
274
274
275 var record_output = true;
275 var record_output = true;
276 switch(json.output_type) {
276 switch(json.output_type) {
277 case 'execute_result':
277 case 'execute_result':
278 json = this.validate_mimebundle(json);
278 json = this.validate_mimebundle(json);
279 this.append_execute_result(json);
279 this.append_execute_result(json);
280 break;
280 break;
281 case 'stream':
281 case 'stream':
282 // append_stream might have merged the output with earlier stream output
282 // append_stream might have merged the output with earlier stream output
283 record_output = this.append_stream(json);
283 record_output = this.append_stream(json);
284 break;
284 break;
285 case 'error':
285 case 'error':
286 this.append_error(json);
286 this.append_error(json);
287 break;
287 break;
288 case 'display_data':
288 case 'display_data':
289 // append handled below
289 // append handled below
290 json = this.validate_mimebundle(json);
290 json = this.validate_mimebundle(json);
291 break;
291 break;
292 default:
292 default:
293 console.log("unrecognized output type: " + json.output_type);
293 console.log("unrecognized output type: " + json.output_type);
294 this.append_unrecognized(json);
294 this.append_unrecognized(json);
295 }
295 }
296
296
297 // We must release the animation fixed height in a callback since Gecko
297 // We must release the animation fixed height in a callback since Gecko
298 // (FireFox) doesn't render the image immediately as the data is
298 // (FireFox) doesn't render the image immediately as the data is
299 // available.
299 // available.
300 var that = this;
300 var that = this;
301 var handle_appended = function ($el) {
301 var handle_appended = function ($el) {
302 /**
302 /**
303 * Only reset the height to automatic if the height is currently
303 * Only reset the height to automatic if the height is currently
304 * fixed (done by wait=True flag on clear_output).
304 * fixed (done by wait=True flag on clear_output).
305 */
305 */
306 if (needs_height_reset) {
306 if (needs_height_reset) {
307 that.element.height('');
307 that.element.height('');
308 }
308 }
309 that.element.trigger('resize');
309 that.element.trigger('resize');
310 };
310 };
311 if (json.output_type === 'display_data') {
311 if (json.output_type === 'display_data') {
312 this.append_display_data(json, handle_appended);
312 this.append_display_data(json, handle_appended);
313 } else {
313 } else {
314 handle_appended();
314 handle_appended();
315 }
315 }
316
316
317 if (record_output) {
317 if (record_output) {
318 this.outputs.push(json);
318 this.outputs.push(json);
319 }
319 }
320 };
320 };
321
321
322
322
323 OutputArea.prototype.create_output_area = function () {
323 OutputArea.prototype.create_output_area = function () {
324 var oa = $("<div/>").addClass("output_area");
324 var oa = $("<div/>").addClass("output_area");
325 if (this.prompt_area) {
325 if (this.prompt_area) {
326 oa.append($('<div/>').addClass('prompt'));
326 oa.append($('<div/>').addClass('prompt'));
327 }
327 }
328 return oa;
328 return oa;
329 };
329 };
330
330
331
331
332 function _get_metadata_key(metadata, key, mime) {
332 function _get_metadata_key(metadata, key, mime) {
333 var mime_md = metadata[mime];
333 var mime_md = metadata[mime];
334 // mime-specific higher priority
334 // mime-specific higher priority
335 if (mime_md && mime_md[key] !== undefined) {
335 if (mime_md && mime_md[key] !== undefined) {
336 return mime_md[key];
336 return mime_md[key];
337 }
337 }
338 // fallback on global
338 // fallback on global
339 return metadata[key];
339 return metadata[key];
340 }
340 }
341
341
342 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
342 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
343 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
343 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
344 if (_get_metadata_key(md, 'isolated', mime)) {
344 if (_get_metadata_key(md, 'isolated', mime)) {
345 // Create an iframe to isolate the subarea from the rest of the
345 // Create an iframe to isolate the subarea from the rest of the
346 // document
346 // document
347 var iframe = $('<iframe/>').addClass('box-flex1');
347 var iframe = $('<iframe/>').addClass('box-flex1');
348 iframe.css({'height':1, 'width':'100%', 'display':'block'});
348 iframe.css({'height':1, 'width':'100%', 'display':'block'});
349 iframe.attr('frameborder', 0);
349 iframe.attr('frameborder', 0);
350 iframe.attr('scrolling', 'auto');
350 iframe.attr('scrolling', 'auto');
351
351
352 // Once the iframe is loaded, the subarea is dynamically inserted
352 // Once the iframe is loaded, the subarea is dynamically inserted
353 iframe.on('load', function() {
353 iframe.on('load', function() {
354 // Workaround needed by Firefox, to properly render svg inside
354 // Workaround needed by Firefox, to properly render svg inside
355 // iframes, see http://stackoverflow.com/questions/10177190/
355 // iframes, see http://stackoverflow.com/questions/10177190/
356 // svg-dynamically-added-to-iframe-does-not-render-correctly
356 // svg-dynamically-added-to-iframe-does-not-render-correctly
357 this.contentDocument.open();
357 this.contentDocument.open();
358
358
359 // Insert the subarea into the iframe
359 // Insert the subarea into the iframe
360 // We must directly write the html. When using Jquery's append
360 // We must directly write the html. When using Jquery's append
361 // method, javascript is evaluated in the parent document and
361 // method, javascript is evaluated in the parent document and
362 // not in the iframe document. At this point, subarea doesn't
362 // not in the iframe document. At this point, subarea doesn't
363 // contain any user content.
363 // contain any user content.
364 this.contentDocument.write(subarea.html());
364 this.contentDocument.write(subarea.html());
365
365
366 this.contentDocument.close();
366 this.contentDocument.close();
367
367
368 var body = this.contentDocument.body;
368 var body = this.contentDocument.body;
369 // Adjust the iframe height automatically
369 // Adjust the iframe height automatically
370 iframe.height(body.scrollHeight + 'px');
370 iframe.height(body.scrollHeight + 'px');
371 });
371 });
372
372
373 // Elements should be appended to the inner subarea and not to the
373 // Elements should be appended to the inner subarea and not to the
374 // iframe
374 // iframe
375 iframe.append = function(that) {
375 iframe.append = function(that) {
376 subarea.append(that);
376 subarea.append(that);
377 };
377 };
378
378
379 return iframe;
379 return iframe;
380 } else {
380 } else {
381 return subarea;
381 return subarea;
382 }
382 }
383 };
383 };
384
384
385
385
386 OutputArea.prototype._append_javascript_error = function (err, element) {
386 OutputArea.prototype._append_javascript_error = function (err, element) {
387 /**
387 /**
388 * display a message when a javascript error occurs in display output
388 * display a message when a javascript error occurs in display output
389 */
389 */
390 var msg = "Javascript error adding output!";
390 var msg = "Javascript error adding output!";
391 if ( element === undefined ) return;
391 if ( element === undefined ) return;
392 element
392 element
393 .append($('<div/>').text(msg).addClass('js-error'))
393 .append($('<div/>').text(msg).addClass('js-error'))
394 .append($('<div/>').text(err.toString()).addClass('js-error'))
394 .append($('<div/>').text(err.toString()).addClass('js-error'))
395 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
395 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
396 };
396 };
397
397
398 OutputArea.prototype._safe_append = function (toinsert) {
398 OutputArea.prototype._safe_append = function (toinsert) {
399 /**
399 /**
400 * safely append an item to the document
400 * safely append an item to the document
401 * this is an object created by user code,
401 * this is an object created by user code,
402 * and may have errors, which should not be raised
402 * and may have errors, which should not be raised
403 * under any circumstances.
403 * under any circumstances.
404 */
404 */
405 try {
405 try {
406 this.element.append(toinsert);
406 this.element.append(toinsert);
407 } catch(err) {
407 } catch(err) {
408 console.log(err);
408 console.log(err);
409 // Create an actual output_area and output_subarea, which creates
409 // Create an actual output_area and output_subarea, which creates
410 // the prompt area and the proper indentation.
410 // the prompt area and the proper indentation.
411 var toinsert = this.create_output_area();
411 var toinsert = this.create_output_area();
412 var subarea = $('<div/>').addClass('output_subarea');
412 var subarea = $('<div/>').addClass('output_subarea');
413 toinsert.append(subarea);
413 toinsert.append(subarea);
414 this._append_javascript_error(err, subarea);
414 this._append_javascript_error(err, subarea);
415 this.element.append(toinsert);
415 this.element.append(toinsert);
416 }
416 }
417
417
418 // Notify others of changes.
418 // Notify others of changes.
419 this.element.trigger('changed');
419 this.element.trigger('changed');
420 };
420 };
421
421
422
422
423 OutputArea.prototype.append_execute_result = function (json) {
423 OutputArea.prototype.append_execute_result = function (json) {
424 var n = json.execution_count || ' ';
424 var n = json.execution_count || ' ';
425 var toinsert = this.create_output_area();
425 var toinsert = this.create_output_area();
426 if (this.prompt_area) {
426 if (this.prompt_area) {
427 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
427 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
428 }
428 }
429 var inserted = this.append_mime_type(json, toinsert);
429 var inserted = this.append_mime_type(json, toinsert);
430 if (inserted) {
430 if (inserted) {
431 inserted.addClass('output_result');
431 inserted.addClass('output_result');
432 }
432 }
433 this._safe_append(toinsert);
433 this._safe_append(toinsert);
434 // If we just output latex, typeset it.
434 // If we just output latex, typeset it.
435 if ((json.data['text/latex'] !== undefined) ||
435 if ((json.data['text/latex'] !== undefined) ||
436 (json.data['text/html'] !== undefined) ||
436 (json.data['text/html'] !== undefined) ||
437 (json.data['text/markdown'] !== undefined)) {
437 (json.data['text/markdown'] !== undefined)) {
438 this.typeset();
438 this.typeset();
439 }
439 }
440 };
440 };
441
441
442
442
443 OutputArea.prototype.append_error = function (json) {
443 OutputArea.prototype.append_error = function (json) {
444 var tb = json.traceback;
444 var tb = json.traceback;
445 if (tb !== undefined && tb.length > 0) {
445 if (tb !== undefined && tb.length > 0) {
446 var s = '';
446 var s = '';
447 var len = tb.length;
447 var len = tb.length;
448 for (var i=0; i<len; i++) {
448 for (var i=0; i<len; i++) {
449 s = s + tb[i] + '\n';
449 s = s + tb[i] + '\n';
450 }
450 }
451 s = s + '\n';
451 s = s + '\n';
452 var toinsert = this.create_output_area();
452 var toinsert = this.create_output_area();
453 var append_text = OutputArea.append_map['text/plain'];
453 var append_text = OutputArea.append_map['text/plain'];
454 if (append_text) {
454 if (append_text) {
455 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
455 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
456 }
456 }
457 this._safe_append(toinsert);
457 this._safe_append(toinsert);
458 }
458 }
459 };
459 };
460
460
461
461
462 OutputArea.prototype.append_stream = function (json) {
462 OutputArea.prototype.append_stream = function (json) {
463 var text = json.text;
463 var text = json.text;
464 var subclass = "output_"+json.name;
464 var subclass = "output_"+json.name;
465 if (this.outputs.length > 0){
465 if (this.outputs.length > 0){
466 // have at least one output to consider
466 // have at least one output to consider
467 var last = this.outputs[this.outputs.length-1];
467 var last = this.outputs[this.outputs.length-1];
468 if (last.output_type == 'stream' && json.name == last.name){
468 if (last.output_type == 'stream' && json.name == last.name){
469 // latest output was in the same stream,
469 // latest output was in the same stream,
470 // so append directly into its pre tag
470 // so append directly into its pre tag
471 // escape ANSI & HTML specials:
471 // escape ANSI & HTML specials:
472 last.text = utils.fixCarriageReturn(last.text + json.text);
472 last.text = utils.fixCarriageReturn(last.text + json.text);
473 var pre = this.element.find('div.'+subclass).last().find('pre');
473 var pre = this.element.find('div.'+subclass).last().find('pre');
474 var html = utils.fixConsole(last.text);
474 var html = utils.fixConsole(last.text);
475 // The only user content injected with this HTML call is
475 // The only user content injected with this HTML call is
476 // escaped by the fixConsole() method.
476 // escaped by the fixConsole() method.
477 pre.html(html);
477 pre.html(html);
478 // return false signals that we merged this output with the previous one,
478 // return false signals that we merged this output with the previous one,
479 // and the new output shouldn't be recorded.
479 // and the new output shouldn't be recorded.
480 return false;
480 return false;
481 }
481 }
482 }
482 }
483
483
484 if (!text.replace("\r", "")) {
484 if (!text.replace("\r", "")) {
485 // text is nothing (empty string, \r, etc.)
485 // text is nothing (empty string, \r, etc.)
486 // so don't append any elements, which might add undesirable space
486 // so don't append any elements, which might add undesirable space
487 // return true to indicate the output should be recorded.
487 // return true to indicate the output should be recorded.
488 return true;
488 return true;
489 }
489 }
490
490
491 // If we got here, attach a new div
491 // If we got here, attach a new div
492 var toinsert = this.create_output_area();
492 var toinsert = this.create_output_area();
493 var append_text = OutputArea.append_map['text/plain'];
493 var append_text = OutputArea.append_map['text/plain'];
494 if (append_text) {
494 if (append_text) {
495 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
495 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
496 }
496 }
497 this._safe_append(toinsert);
497 this._safe_append(toinsert);
498 return true;
498 return true;
499 };
499 };
500
500
501
501
502 OutputArea.prototype.append_unrecognized = function (json) {
502 OutputArea.prototype.append_unrecognized = function (json) {
503 var that = this;
503 var that = this;
504 var toinsert = this.create_output_area();
504 var toinsert = this.create_output_area();
505 var subarea = $('<div/>').addClass('output_subarea output_unrecognized');
505 var subarea = $('<div/>').addClass('output_subarea output_unrecognized');
506 toinsert.append(subarea);
506 toinsert.append(subarea);
507 subarea.append(
507 subarea.append(
508 $("<a>")
508 $("<a>")
509 .attr("href", "#")
509 .attr("href", "#")
510 .text("Unrecognized output: " + json.output_type)
510 .text("Unrecognized output: " + json.output_type)
511 .click(function () {
511 .click(function () {
512 that.events.trigger('unrecognized_output.OutputArea', {output: json})
512 that.events.trigger('unrecognized_output.OutputArea', {output: json})
513 })
513 })
514 );
514 );
515 this._safe_append(toinsert);
515 this._safe_append(toinsert);
516 };
516 };
517
517
518
518
519 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
519 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
520 var toinsert = this.create_output_area();
520 var toinsert = this.create_output_area();
521 if (this.append_mime_type(json, toinsert, handle_inserted)) {
521 if (this.append_mime_type(json, toinsert, handle_inserted)) {
522 this._safe_append(toinsert);
522 this._safe_append(toinsert);
523 // If we just output latex, typeset it.
523 // If we just output latex, typeset it.
524 if ((json.data['text/latex'] !== undefined) ||
524 if ((json.data['text/latex'] !== undefined) ||
525 (json.data['text/html'] !== undefined) ||
525 (json.data['text/html'] !== undefined) ||
526 (json.data['text/markdown'] !== undefined)) {
526 (json.data['text/markdown'] !== undefined)) {
527 this.typeset();
527 this.typeset();
528 }
528 }
529 }
529 }
530 };
530 };
531
531
532
532
533 OutputArea.safe_outputs = {
533 OutputArea.safe_outputs = {
534 'text/plain' : true,
534 'text/plain' : true,
535 'text/latex' : true,
535 'text/latex' : true,
536 'image/png' : true,
536 'image/png' : true,
537 'image/jpeg' : true
537 'image/jpeg' : true
538 };
538 };
539
539
540 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
540 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
541 for (var i=0; i < OutputArea.display_order.length; i++) {
541 for (var i=0; i < OutputArea.display_order.length; i++) {
542 var type = OutputArea.display_order[i];
542 var type = OutputArea.display_order[i];
543 var append = OutputArea.append_map[type];
543 var append = OutputArea.append_map[type];
544 if ((json.data[type] !== undefined) && append) {
544 if ((json.data[type] !== undefined) && append) {
545 var value = json.data[type];
545 var value = json.data[type];
546 if (!this.trusted && !OutputArea.safe_outputs[type]) {
546 if (!this.trusted && !OutputArea.safe_outputs[type]) {
547 // not trusted, sanitize HTML
547 // not trusted, sanitize HTML
548 if (type==='text/html' || type==='text/svg') {
548 if (type==='text/html' || type==='text/svg') {
549 value = security.sanitize_html(value);
549 value = security.sanitize_html(value);
550 } else {
550 } else {
551 // don't display if we don't know how to sanitize it
551 // don't display if we don't know how to sanitize it
552 console.log("Ignoring untrusted " + type + " output.");
552 console.log("Ignoring untrusted " + type + " output.");
553 continue;
553 continue;
554 }
554 }
555 }
555 }
556 var md = json.metadata || {};
556 var md = json.metadata || {};
557 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
557 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
558 // Since only the png and jpeg mime types call the inserted
558 // Since only the png and jpeg mime types call the inserted
559 // callback, if the mime type is something other we must call the
559 // callback, if the mime type is something other we must call the
560 // inserted callback only when the element is actually inserted
560 // inserted callback only when the element is actually inserted
561 // into the DOM. Use a timeout of 0 to do this.
561 // into the DOM. Use a timeout of 0 to do this.
562 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
562 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
563 setTimeout(handle_inserted, 0);
563 setTimeout(handle_inserted, 0);
564 }
564 }
565 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
565 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
566 return toinsert;
566 return toinsert;
567 }
567 }
568 }
568 }
569 return null;
569 return null;
570 };
570 };
571
571
572
572
573 var append_html = function (html, md, element) {
573 var append_html = function (html, md, element) {
574 var type = 'text/html';
574 var type = 'text/html';
575 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
575 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
576 this.keyboard_manager.register_events(toinsert);
576 this.keyboard_manager.register_events(toinsert);
577 toinsert.append(html);
577 toinsert.append(html);
578 element.append(toinsert);
578 element.append(toinsert);
579 return toinsert;
579 return toinsert;
580 };
580 };
581
581
582
582
583 var append_markdown = function(markdown, md, element) {
583 var append_markdown = function(markdown, md, element) {
584 var type = 'text/markdown';
584 var type = 'text/markdown';
585 var toinsert = this.create_output_subarea(md, "output_markdown", type);
585 var toinsert = this.create_output_subarea(md, "output_markdown", type);
586 var text_and_math = mathjaxutils.remove_math(markdown);
586 var text_and_math = mathjaxutils.remove_math(markdown);
587 var text = text_and_math[0];
587 var text = text_and_math[0];
588 var math = text_and_math[1];
588 var math = text_and_math[1];
589 marked(text, function (err, html) {
589 marked(text, function (err, html) {
590 html = mathjaxutils.replace_math(html, math);
590 html = mathjaxutils.replace_math(html, math);
591 toinsert.append(html);
591 toinsert.append(html);
592 });
592 });
593 element.append(toinsert);
593 element.append(toinsert);
594 return toinsert;
594 return toinsert;
595 };
595 };
596
596
597
597
598 var append_javascript = function (js, md, element) {
598 var append_javascript = function (js, md, element) {
599 /**
599 /**
600 * We just eval the JS code, element appears in the local scope.
600 * We just eval the JS code, element appears in the local scope.
601 */
601 */
602 var type = 'application/javascript';
602 var type = 'application/javascript';
603 var toinsert = this.create_output_subarea(md, "output_javascript", type);
603 var toinsert = this.create_output_subarea(md, "output_javascript", type);
604 this.keyboard_manager.register_events(toinsert);
604 this.keyboard_manager.register_events(toinsert);
605 element.append(toinsert);
605 element.append(toinsert);
606
606
607 // Fix for ipython/issues/5293, make sure `element` is the area which
607 // Fix for ipython/issues/5293, make sure `element` is the area which
608 // output can be inserted into at the time of JS execution.
608 // output can be inserted into at the time of JS execution.
609 element = toinsert;
609 element = toinsert;
610 try {
610 try {
611 eval(js);
611 eval(js);
612 } catch(err) {
612 } catch(err) {
613 console.log(err);
613 console.log(err);
614 this._append_javascript_error(err, toinsert);
614 this._append_javascript_error(err, toinsert);
615 }
615 }
616 return toinsert;
616 return toinsert;
617 };
617 };
618
618
619
619
620 var append_text = function (data, md, element) {
620 var append_text = function (data, md, element) {
621 var type = 'text/plain';
621 var type = 'text/plain';
622 var toinsert = this.create_output_subarea(md, "output_text", type);
622 var toinsert = this.create_output_subarea(md, "output_text", type);
623 // escape ANSI & HTML specials in plaintext:
623 // escape ANSI & HTML specials in plaintext:
624 data = utils.fixConsole(data);
624 data = utils.fixConsole(data);
625 data = utils.fixCarriageReturn(data);
625 data = utils.fixCarriageReturn(data);
626 data = utils.autoLinkUrls(data);
626 data = utils.autoLinkUrls(data);
627 // The only user content injected with this HTML call is
627 // The only user content injected with this HTML call is
628 // escaped by the fixConsole() method.
628 // escaped by the fixConsole() method.
629 toinsert.append($("<pre/>").html(data));
629 toinsert.append($("<pre/>").html(data));
630 element.append(toinsert);
630 element.append(toinsert);
631 return toinsert;
631 return toinsert;
632 };
632 };
633
633
634
634
635 var append_svg = function (svg_html, md, element) {
635 var append_svg = function (svg_html, md, element) {
636 var type = 'image/svg+xml';
636 var type = 'image/svg+xml';
637 var toinsert = this.create_output_subarea(md, "output_svg", type);
637 var toinsert = this.create_output_subarea(md, "output_svg", type);
638
638
639 // Get the svg element from within the HTML.
639 // Get the svg element from within the HTML.
640 var svg = $('<div />').html(svg_html).find('svg');
640 var svg = $('<div />').html(svg_html).find('svg');
641 var svg_area = $('<div />');
641 var svg_area = $('<div />');
642 var width = svg.attr('width');
642 var width = svg.attr('width');
643 var height = svg.attr('height');
643 var height = svg.attr('height');
644 svg
644 svg
645 .width('100%')
645 .width('100%')
646 .height('100%');
646 .height('100%');
647 svg_area
647 svg_area
648 .width(width)
648 .width(width)
649 .height(height);
649 .height(height);
650
650
651 // The jQuery resize handlers don't seem to work on the svg element.
651 // The jQuery resize handlers don't seem to work on the svg element.
652 // When the svg renders completely, measure it's size and set the parent
652 // When the svg renders completely, measure it's size and set the parent
653 // div to that size. Then set the svg to 100% the size of the parent
653 // div to that size. Then set the svg to 100% the size of the parent
654 // div and make the parent div resizable.
654 // div and make the parent div resizable.
655 this._dblclick_to_reset_size(svg_area, true, false);
655 this._dblclick_to_reset_size(svg_area, true, false);
656
656
657 svg_area.append(svg);
657 svg_area.append(svg);
658 toinsert.append(svg_area);
658 toinsert.append(svg_area);
659 element.append(toinsert);
659 element.append(toinsert);
660
660
661 return toinsert;
661 return toinsert;
662 };
662 };
663
663
664 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
664 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
665 /**
665 /**
666 * Add a resize handler to an element
666 * Add a resize handler to an element
667 *
667 *
668 * img: jQuery element
668 * img: jQuery element
669 * immediately: bool=False
669 * immediately: bool=False
670 * Wait for the element to load before creating the handle.
670 * Wait for the element to load before creating the handle.
671 * resize_parent: bool=True
671 * resize_parent: bool=True
672 * Should the parent of the element be resized when the element is
672 * Should the parent of the element be resized when the element is
673 * reset (by double click).
673 * reset (by double click).
674 */
674 */
675 var callback = function (){
675 var callback = function (){
676 var h0 = img.height();
676 var h0 = img.height();
677 var w0 = img.width();
677 var w0 = img.width();
678 if (!(h0 && w0)) {
678 if (!(h0 && w0)) {
679 // zero size, don't make it resizable
679 // zero size, don't make it resizable
680 return;
680 return;
681 }
681 }
682 img.resizable({
682 img.resizable({
683 aspectRatio: true,
683 aspectRatio: true,
684 autoHide: true
684 autoHide: true
685 });
685 });
686 img.dblclick(function () {
686 img.dblclick(function () {
687 // resize wrapper & image together for some reason:
687 // resize wrapper & image together for some reason:
688 img.height(h0);
688 img.height(h0);
689 img.width(w0);
689 img.width(w0);
690 if (resize_parent === undefined || resize_parent) {
690 if (resize_parent === undefined || resize_parent) {
691 img.parent().height(h0);
691 img.parent().height(h0);
692 img.parent().width(w0);
692 img.parent().width(w0);
693 }
693 }
694 });
694 });
695 };
695 };
696
696
697 if (immediately) {
697 if (immediately) {
698 callback();
698 callback();
699 } else {
699 } else {
700 img.on("load", callback);
700 img.on("load", callback);
701 }
701 }
702 };
702 };
703
703
704 var set_width_height = function (img, md, mime) {
704 var set_width_height = function (img, md, mime) {
705 /**
705 /**
706 * set width and height of an img element from metadata
706 * set width and height of an img element from metadata
707 */
707 */
708 var height = _get_metadata_key(md, 'height', mime);
708 var height = _get_metadata_key(md, 'height', mime);
709 if (height !== undefined) img.attr('height', height);
709 if (height !== undefined) img.attr('height', height);
710 var width = _get_metadata_key(md, 'width', mime);
710 var width = _get_metadata_key(md, 'width', mime);
711 if (width !== undefined) img.attr('width', width);
711 if (width !== undefined) img.attr('width', width);
712 };
712 };
713
713
714 var append_png = function (png, md, element, handle_inserted) {
714 var append_png = function (png, md, element, handle_inserted) {
715 var type = 'image/png';
715 var type = 'image/png';
716 var toinsert = this.create_output_subarea(md, "output_png", type);
716 var toinsert = this.create_output_subarea(md, "output_png", type);
717 var img = $("<img/>");
717 var img = $("<img/>");
718 if (handle_inserted !== undefined) {
718 if (handle_inserted !== undefined) {
719 img.on('load', function(){
719 img.on('load', function(){
720 handle_inserted(img);
720 handle_inserted(img);
721 });
721 });
722 }
722 }
723 img[0].src = 'data:image/png;base64,'+ png;
723 img[0].src = 'data:image/png;base64,'+ png;
724 set_width_height(img, md, 'image/png');
724 set_width_height(img, md, 'image/png');
725 this._dblclick_to_reset_size(img);
725 this._dblclick_to_reset_size(img);
726 toinsert.append(img);
726 toinsert.append(img);
727 element.append(toinsert);
727 element.append(toinsert);
728 return toinsert;
728 return toinsert;
729 };
729 };
730
730
731
731
732 var append_jpeg = function (jpeg, md, element, handle_inserted) {
732 var append_jpeg = function (jpeg, md, element, handle_inserted) {
733 var type = 'image/jpeg';
733 var type = 'image/jpeg';
734 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
734 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
735 var img = $("<img/>");
735 var img = $("<img/>");
736 if (handle_inserted !== undefined) {
736 if (handle_inserted !== undefined) {
737 img.on('load', function(){
737 img.on('load', function(){
738 handle_inserted(img);
738 handle_inserted(img);
739 });
739 });
740 }
740 }
741 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
741 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
742 set_width_height(img, md, 'image/jpeg');
742 set_width_height(img, md, 'image/jpeg');
743 this._dblclick_to_reset_size(img);
743 this._dblclick_to_reset_size(img);
744 toinsert.append(img);
744 toinsert.append(img);
745 element.append(toinsert);
745 element.append(toinsert);
746 return toinsert;
746 return toinsert;
747 };
747 };
748
748
749
749
750 var append_pdf = function (pdf, md, element) {
750 var append_pdf = function (pdf, md, element) {
751 var type = 'application/pdf';
751 var type = 'application/pdf';
752 var toinsert = this.create_output_subarea(md, "output_pdf", type);
752 var toinsert = this.create_output_subarea(md, "output_pdf", type);
753 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
753 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
754 a.attr('target', '_blank');
754 a.attr('target', '_blank');
755 a.text('View PDF');
755 a.text('View PDF');
756 toinsert.append(a);
756 toinsert.append(a);
757 element.append(toinsert);
757 element.append(toinsert);
758 return toinsert;
758 return toinsert;
759 };
759 };
760
760
761 var append_latex = function (latex, md, element) {
761 var append_latex = function (latex, md, element) {
762 /**
762 /**
763 * This method cannot do the typesetting because the latex first has to
763 * This method cannot do the typesetting because the latex first has to
764 * be on the page.
764 * be on the page.
765 */
765 */
766 var type = 'text/latex';
766 var type = 'text/latex';
767 var toinsert = this.create_output_subarea(md, "output_latex", type);
767 var toinsert = this.create_output_subarea(md, "output_latex", type);
768 toinsert.append(latex);
768 toinsert.append(latex);
769 element.append(toinsert);
769 element.append(toinsert);
770 return toinsert;
770 return toinsert;
771 };
771 };
772
772
773
773
774 OutputArea.prototype.append_raw_input = function (msg) {
774 OutputArea.prototype.append_raw_input = function (msg) {
775 var that = this;
775 var that = this;
776 this.expand();
776 this.expand();
777 var content = msg.content;
777 var content = msg.content;
778 var area = this.create_output_area();
778 var area = this.create_output_area();
779
779
780 // disable any other raw_inputs, if they are left around
780 // disable any other raw_inputs, if they are left around
781 $("div.output_subarea.raw_input_container").remove();
781 $("div.output_subarea.raw_input_container").remove();
782
782
783 var input_type = content.password ? 'password' : 'text';
783 var input_type = content.password ? 'password' : 'text';
784
784
785 area.append(
785 area.append(
786 $("<div/>")
786 $("<div/>")
787 .addClass("box-flex1 output_subarea raw_input_container")
787 .addClass("box-flex1 output_subarea raw_input_container")
788 .append(
788 .append(
789 $("<span/>")
789 $("<span/>")
790 .addClass("raw_input_prompt")
790 .addClass("raw_input_prompt")
791 .text(content.prompt)
791 .text(content.prompt)
792 )
792 )
793 .append(
793 .append(
794 $("<input/>")
794 $("<input/>")
795 .addClass("raw_input")
795 .addClass("raw_input")
796 .attr('type', input_type)
796 .attr('type', input_type)
797 .attr("size", 47)
797 .attr("size", 47)
798 .keydown(function (event, ui) {
798 .keydown(function (event, ui) {
799 // make sure we submit on enter,
799 // make sure we submit on enter,
800 // and don't re-execute the *cell* on shift-enter
800 // and don't re-execute the *cell* on shift-enter
801 if (event.which === keyboard.keycodes.enter) {
801 if (event.which === keyboard.keycodes.enter) {
802 that._submit_raw_input();
802 that._submit_raw_input();
803 return false;
803 return false;
804 }
804 }
805 })
805 })
806 )
806 )
807 );
807 );
808
808
809 this.element.append(area);
809 this.element.append(area);
810 var raw_input = area.find('input.raw_input');
810 var raw_input = area.find('input.raw_input');
811 // Register events that enable/disable the keyboard manager while raw
811 // Register events that enable/disable the keyboard manager while raw
812 // input is focused.
812 // input is focused.
813 this.keyboard_manager.register_events(raw_input);
813 this.keyboard_manager.register_events(raw_input);
814 // Note, the following line used to read raw_input.focus().focus().
814 // Note, the following line used to read raw_input.focus().focus().
815 // This seemed to be needed otherwise only the cell would be focused.
815 // This seemed to be needed otherwise only the cell would be focused.
816 // But with the modal UI, this seems to work fine with one call to focus().
816 // But with the modal UI, this seems to work fine with one call to focus().
817 raw_input.focus();
817 raw_input.focus();
818 };
818 };
819
819
820 OutputArea.prototype._submit_raw_input = function (evt) {
820 OutputArea.prototype._submit_raw_input = function (evt) {
821 var container = this.element.find("div.raw_input_container");
821 var container = this.element.find("div.raw_input_container");
822 var theprompt = container.find("span.raw_input_prompt");
822 var theprompt = container.find("span.raw_input_prompt");
823 var theinput = container.find("input.raw_input");
823 var theinput = container.find("input.raw_input");
824 var value = theinput.val();
824 var value = theinput.val();
825 var echo = value;
825 var echo = value;
826 // don't echo if it's a password
826 // don't echo if it's a password
827 if (theinput.attr('type') == 'password') {
827 if (theinput.attr('type') == 'password') {
828 echo = 'Β·Β·Β·Β·Β·Β·Β·Β·';
828 echo = 'Β·Β·Β·Β·Β·Β·Β·Β·';
829 }
829 }
830 var content = {
830 var content = {
831 output_type : 'stream',
831 output_type : 'stream',
832 name : 'stdout',
832 name : 'stdout',
833 text : theprompt.text() + echo + '\n'
833 text : theprompt.text() + echo + '\n'
834 };
834 };
835 // remove form container
835 // remove form container
836 container.parent().remove();
836 container.parent().remove();
837 // replace with plaintext version in stdout
837 // replace with plaintext version in stdout
838 this.append_output(content, false);
838 this.append_output(content, false);
839 this.events.trigger('send_input_reply.Kernel', value);
839 this.events.trigger('send_input_reply.Kernel', value);
840 };
840 };
841
841
842
842
843 OutputArea.prototype.handle_clear_output = function (msg) {
843 OutputArea.prototype.handle_clear_output = function (msg) {
844 /**
844 /**
845 * msg spec v4 had stdout, stderr, display keys
845 * msg spec v4 had stdout, stderr, display keys
846 * v4.1 replaced these with just wait
846 * v4.1 replaced these with just wait
847 * The default behavior is the same (stdout=stderr=display=True, wait=False),
847 * The default behavior is the same (stdout=stderr=display=True, wait=False),
848 * so v4 messages will still be properly handled,
848 * so v4 messages will still be properly handled,
849 * except for the rarely used clearing less than all output.
849 * except for the rarely used clearing less than all output.
850 */
850 */
851 this.clear_output(msg.content.wait || false);
851 this.clear_output(msg.content.wait || false);
852 };
852 };
853
853
854
854
855 OutputArea.prototype.clear_output = function(wait) {
855 OutputArea.prototype.clear_output = function(wait) {
856 if (wait) {
856 if (wait) {
857
857
858 // If a clear is queued, clear before adding another to the queue.
858 // If a clear is queued, clear before adding another to the queue.
859 if (this.clear_queued) {
859 if (this.clear_queued) {
860 this.clear_output(false);
860 this.clear_output(false);
861 }
861 }
862
862
863 this.clear_queued = true;
863 this.clear_queued = true;
864 } else {
864 } else {
865
865
866 // Fix the output div's height if the clear_output is waiting for
866 // Fix the output div's height if the clear_output is waiting for
867 // new output (it is being used in an animation).
867 // new output (it is being used in an animation).
868 if (this.clear_queued) {
868 if (this.clear_queued) {
869 var height = this.element.height();
869 var height = this.element.height();
870 this.element.height(height);
870 this.element.height(height);
871 this.clear_queued = false;
871 this.clear_queued = false;
872 }
872 }
873
873
874 // Clear all
874 // Clear all
875 // Remove load event handlers from img tags because we don't want
875 // Remove load event handlers from img tags because we don't want
876 // them to fire if the image is never added to the page.
876 // them to fire if the image is never added to the page.
877 this.element.find('img').off('load');
877 this.element.find('img').off('load');
878 this.element.html("");
878 this.element.html("");
879
879
880 // Notify others of changes.
880 // Notify others of changes.
881 this.element.trigger('changed');
881 this.element.trigger('changed');
882
882
883 this.outputs = [];
883 this.outputs = [];
884 this.trusted = true;
884 this.trusted = true;
885 this.unscroll_area();
885 this.unscroll_area();
886 return;
886 return;
887 }
887 }
888 };
888 };
889
889
890
890
891 // JSON serialization
891 // JSON serialization
892
892
893 OutputArea.prototype.fromJSON = function (outputs, metadata) {
893 OutputArea.prototype.fromJSON = function (outputs, metadata) {
894 var len = outputs.length;
894 var len = outputs.length;
895 metadata = metadata || {};
895 metadata = metadata || {};
896
896
897 for (var i=0; i<len; i++) {
897 for (var i=0; i<len; i++) {
898 this.append_output(outputs[i]);
898 this.append_output(outputs[i]);
899 }
899 }
900
900
901 if (metadata.collapsed !== undefined) {
901 if (metadata.collapsed !== undefined) {
902 this.collapsed = metadata.collapsed;
902 this.collapsed = metadata.collapsed;
903 if (metadata.collapsed) {
903 if (metadata.collapsed) {
904 this.collapse_output();
904 this.collapse_output();
905 }
905 }
906 }
906 }
907 if (metadata.autoscroll !== undefined) {
907 if (metadata.autoscroll !== undefined) {
908 this.collapsed = metadata.collapsed;
908 this.collapsed = metadata.collapsed;
909 if (metadata.collapsed) {
909 if (metadata.collapsed) {
910 this.collapse_output();
910 this.collapse_output();
911 } else {
911 } else {
912 this.expand_output();
912 this.expand_output();
913 }
913 }
914 }
914 }
915 };
915 };
916
916
917
917
918 OutputArea.prototype.toJSON = function () {
918 OutputArea.prototype.toJSON = function () {
919 return this.outputs;
919 return this.outputs;
920 };
920 };
921
921
922 /**
922 /**
923 * Class properties
923 * Class properties
924 **/
924 **/
925
925
926 /**
926 /**
927 * Threshold to trigger autoscroll when the OutputArea is resized,
927 * Threshold to trigger autoscroll when the OutputArea is resized,
928 * typically when new outputs are added.
928 * typically when new outputs are added.
929 *
929 *
930 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
930 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
931 * unless it is < 0, in which case autoscroll will never be triggered
931 * unless it is < 0, in which case autoscroll will never be triggered
932 *
932 *
933 * @property auto_scroll_threshold
933 * @property auto_scroll_threshold
934 * @type Number
934 * @type Number
935 * @default 100
935 * @default 100
936 *
936 *
937 **/
937 **/
938 OutputArea.auto_scroll_threshold = 100;
938 OutputArea.auto_scroll_threshold = 100;
939
939
940 /**
940 /**
941 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
941 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
942 * shorter than this are never scrolled.
942 * shorter than this are never scrolled.
943 *
943 *
944 * @property minimum_scroll_threshold
944 * @property minimum_scroll_threshold
945 * @type Number
945 * @type Number
946 * @default 20
946 * @default 20
947 *
947 *
948 **/
948 **/
949 OutputArea.minimum_scroll_threshold = 20;
949 OutputArea.minimum_scroll_threshold = 20;
950
950
951
951
952 OutputArea.display_order = [
952 OutputArea.display_order = [
953 'application/javascript',
953 'application/javascript',
954 'text/html',
954 'text/html',
955 'text/markdown',
955 'text/markdown',
956 'text/latex',
957 'image/svg+xml',
956 'image/svg+xml',
957 'text/latex',
958 'image/png',
958 'image/png',
959 'image/jpeg',
959 'image/jpeg',
960 'application/pdf',
960 'application/pdf',
961 'text/plain'
961 'text/plain'
962 ];
962 ];
963
963
964 OutputArea.append_map = {
964 OutputArea.append_map = {
965 "text/plain" : append_text,
965 "text/plain" : append_text,
966 "text/html" : append_html,
966 "text/html" : append_html,
967 "text/markdown": append_markdown,
967 "text/markdown": append_markdown,
968 "image/svg+xml" : append_svg,
968 "image/svg+xml" : append_svg,
969 "image/png" : append_png,
969 "image/png" : append_png,
970 "image/jpeg" : append_jpeg,
970 "image/jpeg" : append_jpeg,
971 "text/latex" : append_latex,
971 "text/latex" : append_latex,
972 "application/javascript" : append_javascript,
972 "application/javascript" : append_javascript,
973 "application/pdf" : append_pdf
973 "application/pdf" : append_pdf
974 };
974 };
975
975
976 // For backwards compatability.
976 // For backwards compatability.
977 IPython.OutputArea = OutputArea;
977 IPython.OutputArea = OutputArea;
978
978
979 return {'OutputArea': OutputArea};
979 return {'OutputArea': OutputArea};
980 });
980 });
General Comments 0
You need to be logged in to leave comments. Login now