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