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