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