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