##// END OF EJS Templates
Merge pull request #7763 from ctaf42/cg/utils/get_body_data...
Min RK -
r20415:ff9803fb merge
parent child Browse files
Show More
@@ -1,884 +1,887 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 'jquery',
7 7 'codemirror/lib/codemirror',
8 8 'moment',
9 9 // silently upgrades CodeMirror
10 10 'codemirror/mode/meta',
11 11 ], function(IPython, $, CodeMirror, moment){
12 12 "use strict";
13 13
14 14 var load_extensions = function () {
15 15 // load one or more IPython notebook extensions with requirejs
16 16
17 17 var extensions = [];
18 18 var extension_names = arguments;
19 19 for (var i = 0; i < extension_names.length; i++) {
20 20 extensions.push("nbextensions/" + arguments[i]);
21 21 }
22 22
23 23 require(extensions,
24 24 function () {
25 25 for (var i = 0; i < arguments.length; i++) {
26 26 var ext = arguments[i];
27 27 var ext_name = extension_names[i];
28 28 // success callback
29 29 console.log("Loaded extension: " + ext_name);
30 30 if (ext && ext.load_ipython_extension !== undefined) {
31 31 ext.load_ipython_extension();
32 32 }
33 33 }
34 34 },
35 35 function (err) {
36 36 // failure callback
37 37 console.log("Failed to load extension(s):", err.requireModules, err);
38 38 }
39 39 );
40 40 };
41 41
42 42 IPython.load_extensions = load_extensions;
43 43
44 44 /**
45 45 * Wait for a config section to load, and then load the extensions specified
46 46 * in a 'load_extensions' key inside it.
47 47 */
48 48 function load_extensions_from_config(section) {
49 49 section.loaded.then(function() {
50 50 if (section.data.load_extensions) {
51 51 var nbextension_paths = Object.getOwnPropertyNames(
52 52 section.data.load_extensions);
53 53 load_extensions.apply(this, nbextension_paths);
54 54 }
55 55 });
56 56 }
57 57
58 58 //============================================================================
59 59 // Cross-browser RegEx Split
60 60 //============================================================================
61 61
62 62 // This code has been MODIFIED from the code licensed below to not replace the
63 63 // default browser split. The license is reproduced here.
64 64
65 65 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
66 66 /*!
67 67 * Cross-Browser Split 1.1.1
68 68 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
69 69 * Available under the MIT License
70 70 * ECMAScript compliant, uniform cross-browser split method
71 71 */
72 72
73 73 /**
74 74 * Splits a string into an array of strings using a regex or string
75 75 * separator. Matches of the separator are not included in the result array.
76 76 * However, if `separator` is a regex that contains capturing groups,
77 77 * backreferences are spliced into the result each time `separator` is
78 78 * matched. Fixes browser bugs compared to the native
79 79 * `String.prototype.split` and can be used reliably cross-browser.
80 80 * @param {String} str String to split.
81 81 * @param {RegExp} separator Regex to use for separating
82 82 * the string.
83 83 * @param {Number} [limit] Maximum number of items to include in the result
84 84 * array.
85 85 * @returns {Array} Array of substrings.
86 86 * @example
87 87 *
88 88 * // Basic use
89 89 * regex_split('a b c d', ' ');
90 90 * // -> ['a', 'b', 'c', 'd']
91 91 *
92 92 * // With limit
93 93 * regex_split('a b c d', ' ', 2);
94 94 * // -> ['a', 'b']
95 95 *
96 96 * // Backreferences in result array
97 97 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
98 98 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
99 99 */
100 100 var regex_split = function (str, separator, limit) {
101 101 var output = [],
102 102 flags = (separator.ignoreCase ? "i" : "") +
103 103 (separator.multiline ? "m" : "") +
104 104 (separator.extended ? "x" : "") + // Proposed for ES6
105 105 (separator.sticky ? "y" : ""), // Firefox 3+
106 106 lastLastIndex = 0,
107 107 separator2, match, lastIndex, lastLength;
108 108 // Make `global` and avoid `lastIndex` issues by working with a copy
109 109 separator = new RegExp(separator.source, flags + "g");
110 110
111 111 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
112 112 if (!compliantExecNpcg) {
113 113 // Doesn't need flags gy, but they don't hurt
114 114 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
115 115 }
116 116 /* Values for `limit`, per the spec:
117 117 * If undefined: 4294967295 // Math.pow(2, 32) - 1
118 118 * If 0, Infinity, or NaN: 0
119 119 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
120 120 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
121 121 * If other: Type-convert, then use the above rules
122 122 */
123 123 limit = typeof(limit) === "undefined" ?
124 124 -1 >>> 0 : // Math.pow(2, 32) - 1
125 125 limit >>> 0; // ToUint32(limit)
126 126 for (match = separator.exec(str); match; match = separator.exec(str)) {
127 127 // `separator.lastIndex` is not reliable cross-browser
128 128 lastIndex = match.index + match[0].length;
129 129 if (lastIndex > lastLastIndex) {
130 130 output.push(str.slice(lastLastIndex, match.index));
131 131 // Fix browsers whose `exec` methods don't consistently return `undefined` for
132 132 // nonparticipating capturing groups
133 133 if (!compliantExecNpcg && match.length > 1) {
134 134 match[0].replace(separator2, function () {
135 135 for (var i = 1; i < arguments.length - 2; i++) {
136 136 if (typeof(arguments[i]) === "undefined") {
137 137 match[i] = undefined;
138 138 }
139 139 }
140 140 });
141 141 }
142 142 if (match.length > 1 && match.index < str.length) {
143 143 Array.prototype.push.apply(output, match.slice(1));
144 144 }
145 145 lastLength = match[0].length;
146 146 lastLastIndex = lastIndex;
147 147 if (output.length >= limit) {
148 148 break;
149 149 }
150 150 }
151 151 if (separator.lastIndex === match.index) {
152 152 separator.lastIndex++; // Avoid an infinite loop
153 153 }
154 154 }
155 155 if (lastLastIndex === str.length) {
156 156 if (lastLength || !separator.test("")) {
157 157 output.push("");
158 158 }
159 159 } else {
160 160 output.push(str.slice(lastLastIndex));
161 161 }
162 162 return output.length > limit ? output.slice(0, limit) : output;
163 163 };
164 164
165 165 //============================================================================
166 166 // End contributed Cross-browser RegEx Split
167 167 //============================================================================
168 168
169 169
170 170 var uuid = function () {
171 171 /**
172 172 * http://www.ietf.org/rfc/rfc4122.txt
173 173 */
174 174 var s = [];
175 175 var hexDigits = "0123456789ABCDEF";
176 176 for (var i = 0; i < 32; i++) {
177 177 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
178 178 }
179 179 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
180 180 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
181 181
182 182 var uuid = s.join("");
183 183 return uuid;
184 184 };
185 185
186 186
187 187 //Fix raw text to parse correctly in crazy XML
188 188 function xmlencode(string) {
189 189 return string.replace(/\&/g,'&'+'amp;')
190 190 .replace(/</g,'&'+'lt;')
191 191 .replace(/>/g,'&'+'gt;')
192 192 .replace(/\'/g,'&'+'apos;')
193 193 .replace(/\"/g,'&'+'quot;')
194 194 .replace(/`/g,'&'+'#96;');
195 195 }
196 196
197 197
198 198 //Map from terminal commands to CSS classes
199 199 var ansi_colormap = {
200 200 "01":"ansibold",
201 201
202 202 "30":"ansiblack",
203 203 "31":"ansired",
204 204 "32":"ansigreen",
205 205 "33":"ansiyellow",
206 206 "34":"ansiblue",
207 207 "35":"ansipurple",
208 208 "36":"ansicyan",
209 209 "37":"ansigray",
210 210
211 211 "40":"ansibgblack",
212 212 "41":"ansibgred",
213 213 "42":"ansibggreen",
214 214 "43":"ansibgyellow",
215 215 "44":"ansibgblue",
216 216 "45":"ansibgpurple",
217 217 "46":"ansibgcyan",
218 218 "47":"ansibggray"
219 219 };
220 220
221 221 function _process_numbers(attrs, numbers) {
222 222 // process ansi escapes
223 223 var n = numbers.shift();
224 224 if (ansi_colormap[n]) {
225 225 if ( ! attrs["class"] ) {
226 226 attrs["class"] = ansi_colormap[n];
227 227 } else {
228 228 attrs["class"] += " " + ansi_colormap[n];
229 229 }
230 230 } else if (n == "38" || n == "48") {
231 231 // VT100 256 color or 24 bit RGB
232 232 if (numbers.length < 2) {
233 233 console.log("Not enough fields for VT100 color", numbers);
234 234 return;
235 235 }
236 236
237 237 var index_or_rgb = numbers.shift();
238 238 var r,g,b;
239 239 if (index_or_rgb == "5") {
240 240 // 256 color
241 241 var idx = parseInt(numbers.shift(), 10);
242 242 if (idx < 16) {
243 243 // indexed ANSI
244 244 // ignore bright / non-bright distinction
245 245 idx = idx % 8;
246 246 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
247 247 if ( ! attrs["class"] ) {
248 248 attrs["class"] = ansiclass;
249 249 } else {
250 250 attrs["class"] += " " + ansiclass;
251 251 }
252 252 return;
253 253 } else if (idx < 232) {
254 254 // 216 color 6x6x6 RGB
255 255 idx = idx - 16;
256 256 b = idx % 6;
257 257 g = Math.floor(idx / 6) % 6;
258 258 r = Math.floor(idx / 36) % 6;
259 259 // convert to rgb
260 260 r = (r * 51);
261 261 g = (g * 51);
262 262 b = (b * 51);
263 263 } else {
264 264 // grayscale
265 265 idx = idx - 231;
266 266 // it's 1-24 and should *not* include black or white,
267 267 // so a 26 point scale
268 268 r = g = b = Math.floor(idx * 256 / 26);
269 269 }
270 270 } else if (index_or_rgb == "2") {
271 271 // Simple 24 bit RGB
272 272 if (numbers.length > 3) {
273 273 console.log("Not enough fields for RGB", numbers);
274 274 return;
275 275 }
276 276 r = numbers.shift();
277 277 g = numbers.shift();
278 278 b = numbers.shift();
279 279 } else {
280 280 console.log("unrecognized control", numbers);
281 281 return;
282 282 }
283 283 if (r !== undefined) {
284 284 // apply the rgb color
285 285 var line;
286 286 if (n == "38") {
287 287 line = "color: ";
288 288 } else {
289 289 line = "background-color: ";
290 290 }
291 291 line = line + "rgb(" + r + "," + g + "," + b + ");";
292 292 if ( !attrs.style ) {
293 293 attrs.style = line;
294 294 } else {
295 295 attrs.style += " " + line;
296 296 }
297 297 }
298 298 }
299 299 }
300 300
301 301 function ansispan(str) {
302 302 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
303 303 // regular ansi escapes (using the table above)
304 304 var is_open = false;
305 305 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
306 306 if (!pattern) {
307 307 // [(01|22|39|)m close spans
308 308 if (is_open) {
309 309 is_open = false;
310 310 return "</span>";
311 311 } else {
312 312 return "";
313 313 }
314 314 } else {
315 315 is_open = true;
316 316
317 317 // consume sequence of color escapes
318 318 var numbers = pattern.match(/\d+/g);
319 319 var attrs = {};
320 320 while (numbers.length > 0) {
321 321 _process_numbers(attrs, numbers);
322 322 }
323 323
324 324 var span = "<span ";
325 325 Object.keys(attrs).map(function (attr) {
326 326 span = span + " " + attr + '="' + attrs[attr] + '"';
327 327 });
328 328 return span + ">";
329 329 }
330 330 });
331 331 }
332 332
333 333 // Transform ANSI color escape codes into HTML <span> tags with css
334 334 // classes listed in the above ansi_colormap object. The actual color used
335 335 // are set in the css file.
336 336 function fixConsole(txt) {
337 337 txt = xmlencode(txt);
338 338
339 339 // Strip all ANSI codes that are not color related. Matches
340 340 // all ANSI codes that do not end with "m".
341 341 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
342 342 txt = txt.replace(ignored_re, "");
343 343
344 344 // color ansi codes
345 345 txt = ansispan(txt);
346 346 return txt;
347 347 }
348 348
349 349 // Remove chunks that should be overridden by the effect of
350 350 // carriage return characters
351 351 function fixCarriageReturn(txt) {
352 352 var tmp = txt;
353 353 do {
354 354 txt = tmp;
355 355 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
356 356 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
357 357 } while (tmp.length < txt.length);
358 358 return txt;
359 359 }
360 360
361 361 // Locate any URLs and convert them to a anchor tag
362 362 function autoLinkUrls(txt) {
363 363 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
364 364 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
365 365 }
366 366
367 367 var points_to_pixels = function (points) {
368 368 /**
369 369 * A reasonably good way of converting between points and pixels.
370 370 */
371 371 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
372 372 $('body').append(test);
373 373 var pixel_per_point = test.width()/10000;
374 374 test.remove();
375 375 return Math.floor(points*pixel_per_point);
376 376 };
377 377
378 378 var always_new = function (constructor) {
379 379 /**
380 380 * wrapper around contructor to avoid requiring `var a = new constructor()`
381 381 * useful for passing constructors as callbacks,
382 382 * not for programmer laziness.
383 383 * from http://programmers.stackexchange.com/questions/118798
384 384 */
385 385 return function () {
386 386 var obj = Object.create(constructor.prototype);
387 387 constructor.apply(obj, arguments);
388 388 return obj;
389 389 };
390 390 };
391 391
392 392 var url_path_join = function () {
393 393 /**
394 394 * join a sequence of url components with '/'
395 395 */
396 396 var url = '';
397 397 for (var i = 0; i < arguments.length; i++) {
398 398 if (arguments[i] === '') {
399 399 continue;
400 400 }
401 401 if (url.length > 0 && url[url.length-1] != '/') {
402 402 url = url + '/' + arguments[i];
403 403 } else {
404 404 url = url + arguments[i];
405 405 }
406 406 }
407 407 url = url.replace(/\/\/+/, '/');
408 408 return url;
409 409 };
410 410
411 411 var url_path_split = function (path) {
412 412 /**
413 413 * Like os.path.split for URLs.
414 414 * Always returns two strings, the directory path and the base filename
415 415 */
416 416
417 417 var idx = path.lastIndexOf('/');
418 418 if (idx === -1) {
419 419 return ['', path];
420 420 } else {
421 421 return [ path.slice(0, idx), path.slice(idx + 1) ];
422 422 }
423 423 };
424 424
425 425 var parse_url = function (url) {
426 426 /**
427 427 * an `a` element with an href allows attr-access to the parsed segments of a URL
428 428 * a = parse_url("http://localhost:8888/path/name#hash")
429 429 * a.protocol = "http:"
430 430 * a.host = "localhost:8888"
431 431 * a.hostname = "localhost"
432 432 * a.port = 8888
433 433 * a.pathname = "/path/name"
434 434 * a.hash = "#hash"
435 435 */
436 436 var a = document.createElement("a");
437 437 a.href = url;
438 438 return a;
439 439 };
440 440
441 441 var encode_uri_components = function (uri) {
442 442 /**
443 443 * encode just the components of a multi-segment uri,
444 444 * leaving '/' separators
445 445 */
446 446 return uri.split('/').map(encodeURIComponent).join('/');
447 447 };
448 448
449 449 var url_join_encode = function () {
450 450 /**
451 451 * join a sequence of url components with '/',
452 452 * encoding each component with encodeURIComponent
453 453 */
454 454 return encode_uri_components(url_path_join.apply(null, arguments));
455 455 };
456 456
457 457
458 458 var splitext = function (filename) {
459 459 /**
460 460 * mimic Python os.path.splitext
461 461 * Returns ['base', '.ext']
462 462 */
463 463 var idx = filename.lastIndexOf('.');
464 464 if (idx > 0) {
465 465 return [filename.slice(0, idx), filename.slice(idx)];
466 466 } else {
467 467 return [filename, ''];
468 468 }
469 469 };
470 470
471 471
472 472 var escape_html = function (text) {
473 473 /**
474 474 * escape text to HTML
475 475 */
476 476 return $("<div/>").text(text).html();
477 477 };
478 478
479 479
480 480 var get_body_data = function(key) {
481 481 /**
482 482 * get a url-encoded item from body.data and decode it
483 483 * we should never have any encoded URLs anywhere else in code
484 484 * until we are building an actual request
485 485 */
486 return decodeURIComponent($('body').data(key));
486 var val = $('body').data(key);
487 if (!val)
488 return val;
489 return decodeURIComponent(val);
487 490 };
488 491
489 492 var to_absolute_cursor_pos = function (cm, cursor) {
490 493 /**
491 494 * get the absolute cursor position from CodeMirror's col, ch
492 495 */
493 496 if (!cursor) {
494 497 cursor = cm.getCursor();
495 498 }
496 499 var cursor_pos = cursor.ch;
497 500 for (var i = 0; i < cursor.line; i++) {
498 501 cursor_pos += cm.getLine(i).length + 1;
499 502 }
500 503 return cursor_pos;
501 504 };
502 505
503 506 var from_absolute_cursor_pos = function (cm, cursor_pos) {
504 507 /**
505 508 * turn absolute cursor postion into CodeMirror col, ch cursor
506 509 */
507 510 var i, line;
508 511 var offset = 0;
509 512 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
510 513 if (offset + line.length < cursor_pos) {
511 514 offset += line.length + 1;
512 515 } else {
513 516 return {
514 517 line : i,
515 518 ch : cursor_pos - offset,
516 519 };
517 520 }
518 521 }
519 522 // reached end, return endpoint
520 523 return {
521 524 ch : line.length - 1,
522 525 line : i - 1,
523 526 };
524 527 };
525 528
526 529 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
527 530 var browser = (function() {
528 531 if (typeof navigator === 'undefined') {
529 532 // navigator undefined in node
530 533 return 'None';
531 534 }
532 535 var N= navigator.appName, ua= navigator.userAgent, tem;
533 536 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
534 537 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
535 538 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
536 539 return M;
537 540 })();
538 541
539 542 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
540 543 var platform = (function () {
541 544 if (typeof navigator === 'undefined') {
542 545 // navigator undefined in node
543 546 return 'None';
544 547 }
545 548 var OSName="None";
546 549 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
547 550 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
548 551 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
549 552 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
550 553 return OSName;
551 554 })();
552 555
553 556 var get_url_param = function (name) {
554 557 // get a URL parameter. I cannot believe we actually need this.
555 558 // Based on http://stackoverflow.com/a/25359264/938949
556 559 var match = new RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
557 560 if (match){
558 561 return decodeURIComponent(match[1] || '');
559 562 }
560 563 };
561 564
562 565 var is_or_has = function (a, b) {
563 566 /**
564 567 * Is b a child of a or a itself?
565 568 */
566 569 return a.has(b).length !==0 || a.is(b);
567 570 };
568 571
569 572 var is_focused = function (e) {
570 573 /**
571 574 * Is element e, or one of its children focused?
572 575 */
573 576 e = $(e);
574 577 var target = $(document.activeElement);
575 578 if (target.length > 0) {
576 579 if (is_or_has(e, target)) {
577 580 return true;
578 581 } else {
579 582 return false;
580 583 }
581 584 } else {
582 585 return false;
583 586 }
584 587 };
585 588
586 589 var mergeopt = function(_class, options, overwrite){
587 590 options = options || {};
588 591 overwrite = overwrite || {};
589 592 return $.extend(true, {}, _class.options_default, options, overwrite);
590 593 };
591 594
592 595 var ajax_error_msg = function (jqXHR) {
593 596 /**
594 597 * Return a JSON error message if there is one,
595 598 * otherwise the basic HTTP status text.
596 599 */
597 600 if (jqXHR.responseJSON && jqXHR.responseJSON.traceback) {
598 601 return jqXHR.responseJSON.traceback;
599 602 } else if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
600 603 return jqXHR.responseJSON.message;
601 604 } else {
602 605 return jqXHR.statusText;
603 606 }
604 607 };
605 608 var log_ajax_error = function (jqXHR, status, error) {
606 609 /**
607 610 * log ajax failures with informative messages
608 611 */
609 612 var msg = "API request failed (" + jqXHR.status + "): ";
610 613 console.log(jqXHR);
611 614 msg += ajax_error_msg(jqXHR);
612 615 console.log(msg);
613 616 };
614 617
615 618 var requireCodeMirrorMode = function (mode, callback, errback) {
616 619 /**
617 620 * find a predefined mode or detect from CM metadata then
618 621 * require and callback with the resolveable mode string: mime or
619 622 * custom name
620 623 */
621 624
622 625 var modename = (typeof mode == "string") ? mode :
623 626 mode.mode || mode.name;
624 627
625 628 // simplest, cheapest check by mode name: mode may also have config
626 629 if (CodeMirror.modes.hasOwnProperty(modename)) {
627 630 // return the full mode object, if it has a name
628 631 callback(mode.name ? mode : modename);
629 632 return;
630 633 }
631 634
632 635 // *somehow* get back a CM.modeInfo-like object that has .mode and
633 636 // .mime
634 637 var info = (mode && mode.mode && mode.mime && mode) ||
635 638 CodeMirror.findModeByName(modename) ||
636 639 CodeMirror.findModeByExtension(modename.split(".").slice(-1)) ||
637 640 CodeMirror.findModeByMIME(modename) ||
638 641 {mode: modename, mime: modename};
639 642
640 643 require([
641 644 // might want to use CodeMirror.modeURL here
642 645 ['codemirror/mode', info.mode, info.mode].join('/'),
643 646 ], function() {
644 647 // return the original mode, as from a kernelspec on first load
645 648 // or the mimetype, as for most highlighting
646 649 callback(mode.name ? mode : info.mime);
647 650 }, errback
648 651 );
649 652 };
650 653
651 654 /** Error type for wrapped XHR errors. */
652 655 var XHR_ERROR = 'XhrError';
653 656
654 657 /**
655 658 * Wraps an AJAX error as an Error object.
656 659 */
657 660 var wrap_ajax_error = function (jqXHR, status, error) {
658 661 var wrapped_error = new Error(ajax_error_msg(jqXHR));
659 662 wrapped_error.name = XHR_ERROR;
660 663 // provide xhr response
661 664 wrapped_error.xhr = jqXHR;
662 665 wrapped_error.xhr_status = status;
663 666 wrapped_error.xhr_error = error;
664 667 return wrapped_error;
665 668 };
666 669
667 670 var promising_ajax = function(url, settings) {
668 671 /**
669 672 * Like $.ajax, but returning an ES6 promise. success and error settings
670 673 * will be ignored.
671 674 */
672 675 settings = settings || {};
673 676 return new Promise(function(resolve, reject) {
674 677 settings.success = function(data, status, jqXHR) {
675 678 resolve(data);
676 679 };
677 680 settings.error = function(jqXHR, status, error) {
678 681 log_ajax_error(jqXHR, status, error);
679 682 reject(wrap_ajax_error(jqXHR, status, error));
680 683 };
681 684 $.ajax(url, settings);
682 685 });
683 686 };
684 687
685 688 var WrappedError = function(message, error){
686 689 /**
687 690 * Wrappable Error class
688 691 *
689 692 * The Error class doesn't actually act on `this`. Instead it always
690 693 * returns a new instance of Error. Here we capture that instance so we
691 694 * can apply it's properties to `this`.
692 695 */
693 696 var tmp = Error.apply(this, [message]);
694 697
695 698 // Copy the properties of the error over to this.
696 699 var properties = Object.getOwnPropertyNames(tmp);
697 700 for (var i = 0; i < properties.length; i++) {
698 701 this[properties[i]] = tmp[properties[i]];
699 702 }
700 703
701 704 // Keep a stack of the original error messages.
702 705 if (error instanceof WrappedError) {
703 706 this.error_stack = error.error_stack;
704 707 } else {
705 708 this.error_stack = [error];
706 709 }
707 710 this.error_stack.push(tmp);
708 711
709 712 return this;
710 713 };
711 714
712 715 WrappedError.prototype = Object.create(Error.prototype, {});
713 716
714 717
715 718 var load_class = function(class_name, module_name, registry) {
716 719 /**
717 720 * Tries to load a class
718 721 *
719 722 * Tries to load a class from a module using require.js, if a module
720 723 * is specified, otherwise tries to load a class from the global
721 724 * registry, if the global registry is provided.
722 725 */
723 726 return new Promise(function(resolve, reject) {
724 727
725 728 // Try loading the view module using require.js
726 729 if (module_name) {
727 730 require([module_name], function(module) {
728 731 if (module[class_name] === undefined) {
729 732 reject(new Error('Class '+class_name+' not found in module '+module_name));
730 733 } else {
731 734 resolve(module[class_name]);
732 735 }
733 736 }, reject);
734 737 } else {
735 738 if (registry && registry[class_name]) {
736 739 resolve(registry[class_name]);
737 740 } else {
738 741 reject(new Error('Class '+class_name+' not found in registry '));
739 742 }
740 743 }
741 744 });
742 745 };
743 746
744 747 var resolve_promises_dict = function(d) {
745 748 /**
746 749 * Resolve a promiseful dictionary.
747 750 * Returns a single Promise.
748 751 */
749 752 var keys = Object.keys(d);
750 753 var values = [];
751 754 keys.forEach(function(key) {
752 755 values.push(d[key]);
753 756 });
754 757 return Promise.all(values).then(function(v) {
755 758 d = {};
756 759 for(var i=0; i<keys.length; i++) {
757 760 d[keys[i]] = v[i];
758 761 }
759 762 return d;
760 763 });
761 764 };
762 765
763 766 var reject = function(message, log) {
764 767 /**
765 768 * Creates a wrappable Promise rejection function.
766 769 *
767 770 * Creates a function that returns a Promise.reject with a new WrappedError
768 771 * that has the provided message and wraps the original error that
769 772 * caused the promise to reject.
770 773 */
771 774 return function(error) {
772 775 var wrapped_error = new WrappedError(message, error);
773 776 if (log) console.error(wrapped_error);
774 777 return Promise.reject(wrapped_error);
775 778 };
776 779 };
777 780
778 781 var typeset = function(element, text) {
779 782 /**
780 783 * Apply MathJax rendering to an element, and optionally set its text
781 784 *
782 785 * If MathJax is not available, make no changes.
783 786 *
784 787 * Returns the output any number of typeset elements, or undefined if
785 788 * MathJax was not available.
786 789 *
787 790 * Parameters
788 791 * ----------
789 792 * element: Node, NodeList, or jQuery selection
790 793 * text: option string
791 794 */
792 795 var $el = element.jquery ? element : $(element);
793 796 if(arguments.length > 1){
794 797 $el.text(text);
795 798 }
796 799 if(!window.MathJax){
797 800 return;
798 801 }
799 802 return $el.map(function(){
800 803 // MathJax takes a DOM node: $.map makes `this` the context
801 804 return MathJax.Hub.Queue(["Typeset", MathJax.Hub, this]);
802 805 });
803 806 };
804 807
805 808 var time = {};
806 809 time.milliseconds = {};
807 810 time.milliseconds.s = 1000;
808 811 time.milliseconds.m = 60 * time.milliseconds.s;
809 812 time.milliseconds.h = 60 * time.milliseconds.m;
810 813 time.milliseconds.d = 24 * time.milliseconds.h;
811 814
812 815 time.thresholds = {
813 816 // moment.js thresholds in milliseconds
814 817 s: moment.relativeTimeThreshold('s') * time.milliseconds.s,
815 818 m: moment.relativeTimeThreshold('m') * time.milliseconds.m,
816 819 h: moment.relativeTimeThreshold('h') * time.milliseconds.h,
817 820 d: moment.relativeTimeThreshold('d') * time.milliseconds.d,
818 821 };
819 822
820 823 time.timeout_from_dt = function (dt) {
821 824 /** compute a timeout based on dt
822 825
823 826 input and output both in milliseconds
824 827
825 828 use moment's relative time thresholds:
826 829
827 830 - 10 seconds if in 'seconds ago' territory
828 831 - 1 minute if in 'minutes ago'
829 832 - 1 hour otherwise
830 833 */
831 834 if (dt < time.thresholds.s) {
832 835 return 10 * time.milliseconds.s;
833 836 } else if (dt < time.thresholds.m) {
834 837 return time.milliseconds.m;
835 838 } else {
836 839 return time.milliseconds.h;
837 840 }
838 841 };
839 842
840 843 var utils = {
841 844 load_extensions: load_extensions,
842 845 load_extensions_from_config: load_extensions_from_config,
843 846 regex_split : regex_split,
844 847 uuid : uuid,
845 848 fixConsole : fixConsole,
846 849 fixCarriageReturn : fixCarriageReturn,
847 850 autoLinkUrls : autoLinkUrls,
848 851 points_to_pixels : points_to_pixels,
849 852 get_body_data : get_body_data,
850 853 parse_url : parse_url,
851 854 url_path_split : url_path_split,
852 855 url_path_join : url_path_join,
853 856 url_join_encode : url_join_encode,
854 857 encode_uri_components : encode_uri_components,
855 858 splitext : splitext,
856 859 escape_html : escape_html,
857 860 always_new : always_new,
858 861 to_absolute_cursor_pos : to_absolute_cursor_pos,
859 862 from_absolute_cursor_pos : from_absolute_cursor_pos,
860 863 browser : browser,
861 864 platform: platform,
862 865 get_url_param: get_url_param,
863 866 is_or_has : is_or_has,
864 867 is_focused : is_focused,
865 868 mergeopt: mergeopt,
866 869 ajax_error_msg : ajax_error_msg,
867 870 log_ajax_error : log_ajax_error,
868 871 requireCodeMirrorMode : requireCodeMirrorMode,
869 872 XHR_ERROR : XHR_ERROR,
870 873 wrap_ajax_error : wrap_ajax_error,
871 874 promising_ajax : promising_ajax,
872 875 WrappedError: WrappedError,
873 876 load_class: load_class,
874 877 resolve_promises_dict: resolve_promises_dict,
875 878 reject: reject,
876 879 typeset: typeset,
877 880 time: time,
878 881 };
879 882
880 883 // Backwards compatability.
881 884 IPython.utils = utils;
882 885
883 886 return utils;
884 887 });
General Comments 0
You need to be logged in to leave comments. Login now