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