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