utils.js 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. define([
  4. 'jquery',
  5. 'codemirror/lib/codemirror',
  6. 'moment',
  7. 'underscore',
  8. // silently upgrades CodeMirror
  9. 'codemirror/mode/meta',
  10. ], function($, CodeMirror, moment, _){
  11. "use strict";
  12. // keep track of which extensions have been loaded already
  13. var extensions_loaded = [];
  14. /**
  15. * Whether or not an extension has been loaded
  16. * @param {string} extension - name of the extension
  17. * @return {boolean} true if loaded already
  18. */
  19. var is_loaded = function(extension) {
  20. var ext_path = "nbextensions/" + extension;
  21. return extensions_loaded.indexOf(ext_path) >= 0;
  22. };
  23. /**
  24. * Load a single extension.
  25. * @param {string} extension - extension path.
  26. * @return {Promise} that resolves to an extension module handle
  27. */
  28. var load_extension = function (extension) {
  29. return new Promise(function(resolve, reject) {
  30. var ext_path = "nbextensions/" + extension;
  31. requirejs([ext_path], function(module) {
  32. if (!is_loaded(extension)) {
  33. console.log("Loading extension: " + extension);
  34. if (module && module.load_ipython_extension) {
  35. Promise.resolve(module.load_ipython_extension()).then(function() {
  36. resolve(module);
  37. }).catch(reject);
  38. }
  39. extensions_loaded.push(ext_path);
  40. } else {
  41. console.log("Loaded extension already: " + extension);
  42. resolve(module);
  43. }
  44. }, function(err) {
  45. reject(err);
  46. });
  47. });
  48. };
  49. /**
  50. * Load multiple extensions.
  51. * Takes n-args, where each arg is a string path to the extension.
  52. * @return {Promise} that resolves to a list of loaded module handles.
  53. */
  54. var load_extensions = function () {
  55. console.log('load_extensions', arguments);
  56. return Promise.all(Array.prototype.map.call(arguments, load_extension)).catch(function(err) {
  57. console.error("Failed to load extension" + (err.requireModules.length>1?'s':'') + ":", err.requireModules, err);
  58. });
  59. };
  60. /**
  61. * Return a list of extensions that should be active
  62. * The config for nbextensions comes in as a dict where keys are
  63. * nbextensions paths and the values are a bool indicating if it
  64. * should be active. This returns a list of nbextension paths
  65. * where the value is true
  66. */
  67. function filter_extensions(nbext_config) {
  68. var active = [];
  69. Object.keys(nbext_config).forEach(function (nbext) {
  70. if (nbext_config[nbext]) {active.push(nbext);}
  71. });
  72. return active;
  73. }
  74. /**
  75. * Wait for a config section to load, and then load the extensions specified
  76. * in a 'load_extensions' key inside it.
  77. */
  78. function load_extensions_from_config(section) {
  79. return section.loaded.then(function() {
  80. if (section.data.load_extensions) {
  81. var active = filter_extensions(section.data.load_extensions);
  82. return load_extensions.apply(this, active);
  83. }
  84. }).catch(utils.reject('Could not load nbextensions from ' + section.section_name + ' config file'));
  85. }
  86. //============================================================================
  87. // Cross-browser RegEx Split
  88. //============================================================================
  89. // This code has been MODIFIED from the code licensed below to not replace the
  90. // default browser split. The license is reproduced here.
  91. // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
  92. /*!
  93. * Cross-Browser Split 1.1.1
  94. * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
  95. * Available under the MIT License
  96. * ECMAScript compliant, uniform cross-browser split method
  97. */
  98. /**
  99. * Splits a string into an array of strings using a regex or string
  100. * separator. Matches of the separator are not included in the result array.
  101. * However, if `separator` is a regex that contains capturing groups,
  102. * backreferences are spliced into the result each time `separator` is
  103. * matched. Fixes browser bugs compared to the native
  104. * `String.prototype.split` and can be used reliably cross-browser.
  105. * @param {String} str String to split.
  106. * @param {RegExp} separator Regex to use for separating
  107. * the string.
  108. * @param {Number} [limit] Maximum number of items to include in the result
  109. * array.
  110. * @returns {Array} Array of substrings.
  111. * @example
  112. *
  113. * // Basic use
  114. * regex_split('a b c d', ' ');
  115. * // -> ['a', 'b', 'c', 'd']
  116. *
  117. * // With limit
  118. * regex_split('a b c d', ' ', 2);
  119. * // -> ['a', 'b']
  120. *
  121. * // Backreferences in result array
  122. * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
  123. * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
  124. */
  125. var regex_split = function (str, separator, limit) {
  126. var output = [],
  127. flags = (separator.ignoreCase ? "i" : "") +
  128. (separator.multiline ? "m" : "") +
  129. (separator.extended ? "x" : "") + // Proposed for ES6
  130. (separator.sticky ? "y" : ""), // Firefox 3+
  131. lastLastIndex = 0,
  132. separator2, match, lastIndex, lastLength;
  133. // Make `global` and avoid `lastIndex` issues by working with a copy
  134. separator = new RegExp(separator.source, flags + "g");
  135. var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
  136. if (!compliantExecNpcg) {
  137. // Doesn't need flags gy, but they don't hurt
  138. separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
  139. }
  140. /* Values for `limit`, per the spec:
  141. * If undefined: 4294967295 // Math.pow(2, 32) - 1
  142. * If 0, Infinity, or NaN: 0
  143. * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
  144. * If negative number: 4294967296 - Math.floor(Math.abs(limit))
  145. * If other: Type-convert, then use the above rules
  146. */
  147. limit = typeof(limit) === "undefined" ?
  148. -1 >>> 0 : // Math.pow(2, 32) - 1
  149. limit >>> 0; // ToUint32(limit)
  150. for (match = separator.exec(str); match; match = separator.exec(str)) {
  151. // `separator.lastIndex` is not reliable cross-browser
  152. lastIndex = match.index + match[0].length;
  153. if (lastIndex > lastLastIndex) {
  154. output.push(str.slice(lastLastIndex, match.index));
  155. // Fix browsers whose `exec` methods don't consistently return `undefined` for
  156. // nonparticipating capturing groups
  157. if (!compliantExecNpcg && match.length > 1) {
  158. match[0].replace(separator2, function () {
  159. for (var i = 1; i < arguments.length - 2; i++) {
  160. if (typeof(arguments[i]) === "undefined") {
  161. match[i] = undefined;
  162. }
  163. }
  164. });
  165. }
  166. if (match.length > 1 && match.index < str.length) {
  167. Array.prototype.push.apply(output, match.slice(1));
  168. }
  169. lastLength = match[0].length;
  170. lastLastIndex = lastIndex;
  171. if (output.length >= limit) {
  172. break;
  173. }
  174. }
  175. if (separator.lastIndex === match.index) {
  176. separator.lastIndex++; // Avoid an infinite loop
  177. }
  178. }
  179. if (lastLastIndex === str.length) {
  180. if (lastLength || !separator.test("")) {
  181. output.push("");
  182. }
  183. } else {
  184. output.push(str.slice(lastLastIndex));
  185. }
  186. return output.length > limit ? output.slice(0, limit) : output;
  187. };
  188. //============================================================================
  189. // End contributed Cross-browser RegEx Split
  190. //============================================================================
  191. var uuid = function () {
  192. /**
  193. * http://www.ietf.org/rfc/rfc4122.txt
  194. */
  195. var s = [];
  196. var hexDigits = "0123456789abcdef";
  197. for (var i = 0; i < 32; i++) {
  198. s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
  199. }
  200. s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
  201. s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
  202. var uuid = s.join("");
  203. return uuid;
  204. };
  205. var _ANSI_COLORS = [
  206. "ansi-black",
  207. "ansi-red",
  208. "ansi-green",
  209. "ansi-yellow",
  210. "ansi-blue",
  211. "ansi-magenta",
  212. "ansi-cyan",
  213. "ansi-white",
  214. "ansi-black-intense",
  215. "ansi-red-intense",
  216. "ansi-green-intense",
  217. "ansi-yellow-intense",
  218. "ansi-blue-intense",
  219. "ansi-magenta-intense",
  220. "ansi-cyan-intense",
  221. "ansi-white-intense",
  222. ];
  223. function _pushColoredChunk(chunk, fg, bg, bold, underline, inverse, out) {
  224. if (chunk) {
  225. var classes = [];
  226. var styles = [];
  227. if (bold && typeof fg === "number" && 0 <= fg && fg < 8) {
  228. fg += 8; // Bold text uses "intense" colors
  229. }
  230. if (inverse) {
  231. [fg, bg] = [bg, fg];
  232. }
  233. if (typeof fg === "number") {
  234. classes.push(_ANSI_COLORS[fg] + "-fg");
  235. } else if (fg.length) {
  236. styles.push("color: rgb(" + fg + ")");
  237. } else if (inverse) {
  238. classes.push("ansi-default-inverse-fg");
  239. }
  240. if (typeof bg === "number") {
  241. classes.push(_ANSI_COLORS[bg] + "-bg");
  242. } else if (bg.length) {
  243. styles.push("background-color: rgb(" + bg + ")");
  244. } else if (inverse) {
  245. classes.push("ansi-default-inverse-bg");
  246. }
  247. if (bold) {
  248. classes.push("ansi-bold");
  249. }
  250. if (underline) {
  251. classes.push("ansi-underline");
  252. }
  253. if (classes.length || styles.length) {
  254. out.push("<span");
  255. if (classes.length) {
  256. out.push(' class="' + classes.join(" ") + '"');
  257. }
  258. if (styles.length) {
  259. out.push(' style="' + styles.join("; ") + '"');
  260. }
  261. out.push(">");
  262. out.push(chunk);
  263. out.push("</span>");
  264. } else {
  265. out.push(chunk);
  266. }
  267. }
  268. }
  269. function _getExtendedColors(numbers) {
  270. var r, g, b;
  271. var n = numbers.shift();
  272. if (n === 2 && numbers.length >= 3) {
  273. // 24-bit RGB
  274. r = numbers.shift();
  275. g = numbers.shift();
  276. b = numbers.shift();
  277. if ([r, g, b].some(function (c) { return c < 0 || 255 < c; })) {
  278. throw new RangeError("Invalid range for RGB colors");
  279. }
  280. } else if (n === 5 && numbers.length >= 1) {
  281. // 256 colors
  282. var idx = numbers.shift();
  283. if (idx < 0) {
  284. throw new RangeError("Color index must be >= 0");
  285. } else if (idx < 16) {
  286. // 16 default terminal colors
  287. return idx;
  288. } else if (idx < 232) {
  289. // 6x6x6 color cube, see https://stackoverflow.com/a/27165165/500098
  290. r = Math.floor((idx - 16) / 36);
  291. r = r > 0 ? 55 + r * 40 : 0;
  292. g = Math.floor(((idx - 16) % 36) / 6);
  293. g = g > 0 ? 55 + g * 40 : 0;
  294. b = (idx - 16) % 6;
  295. b = b > 0 ? 55 + b * 40 : 0;
  296. } else if (idx < 256) {
  297. // grayscale, see https://stackoverflow.com/a/27165165/500098
  298. r = g = b = (idx - 232) * 10 + 8;
  299. } else {
  300. throw new RangeError("Color index must be < 256");
  301. }
  302. } else {
  303. throw new RangeError("Invalid extended color specification");
  304. }
  305. return [r, g, b];
  306. }
  307. function _ansispan(str) {
  308. var ansi_re = /\x1b\[(.*?)([@-~])/g;
  309. var fg = [];
  310. var bg = [];
  311. var bold = false;
  312. var underline = false;
  313. var inverse = false;
  314. var match;
  315. var out = [];
  316. var numbers = [];
  317. var start = 0;
  318. str += "\x1b[m"; // Ensure markup for trailing text
  319. while ((match = ansi_re.exec(str))) {
  320. if (match[2] === "m") {
  321. var items = match[1].split(";");
  322. for (var i = 0; i < items.length; i++) {
  323. var item = items[i];
  324. if (item === "") {
  325. numbers.push(0);
  326. } else if (item.search(/^\d+$/) !== -1) {
  327. numbers.push(parseInt(item));
  328. } else {
  329. // Ignored: Invalid color specification
  330. numbers.length = 0;
  331. break;
  332. }
  333. }
  334. } else {
  335. // Ignored: Not a color code
  336. }
  337. var chunk = str.substring(start, match.index);
  338. _pushColoredChunk(chunk, fg, bg, bold, underline, inverse, out);
  339. start = ansi_re.lastIndex;
  340. while (numbers.length) {
  341. var n = numbers.shift();
  342. switch (n) {
  343. case 0:
  344. fg = bg = [];
  345. bold = false;
  346. underline = false;
  347. inverse = false;
  348. break;
  349. case 1:
  350. case 5:
  351. bold = true;
  352. break;
  353. case 4:
  354. underline = true;
  355. break;
  356. case 7:
  357. inverse = true;
  358. break;
  359. case 21:
  360. case 22:
  361. bold = false;
  362. break;
  363. case 24:
  364. underline = false;
  365. break;
  366. case 27:
  367. inverse = false;
  368. break;
  369. case 30:
  370. case 31:
  371. case 32:
  372. case 33:
  373. case 34:
  374. case 35:
  375. case 36:
  376. case 37:
  377. fg = n - 30;
  378. break;
  379. case 38:
  380. try {
  381. fg = _getExtendedColors(numbers);
  382. } catch(e) {
  383. numbers.length = 0;
  384. }
  385. break;
  386. case 39:
  387. fg = [];
  388. break;
  389. case 40:
  390. case 41:
  391. case 42:
  392. case 43:
  393. case 44:
  394. case 45:
  395. case 46:
  396. case 47:
  397. bg = n - 40;
  398. break;
  399. case 48:
  400. try {
  401. bg = _getExtendedColors(numbers);
  402. } catch(e) {
  403. numbers.length = 0;
  404. }
  405. break;
  406. case 49:
  407. bg = [];
  408. break;
  409. case 90:
  410. case 91:
  411. case 92:
  412. case 93:
  413. case 94:
  414. case 95:
  415. case 96:
  416. case 97:
  417. fg = n - 90 + 8;
  418. break;
  419. case 100:
  420. case 101:
  421. case 102:
  422. case 103:
  423. case 104:
  424. case 105:
  425. case 106:
  426. case 107:
  427. bg = n - 100 + 8;
  428. break;
  429. default:
  430. // Unknown codes are ignored
  431. }
  432. }
  433. }
  434. return out.join("");
  435. }
  436. // Transform ANSI color escape codes into HTML <span> tags with CSS
  437. // classes such as "ansi-green-intense-fg".
  438. // The actual colors used are set in the CSS file.
  439. // This is supposed to have the same behavior as nbconvert.filters.ansi2html()
  440. function fixConsole(txt) {
  441. txt = _.escape(txt);
  442. // color ansi codes (and remove non-color escape sequences)
  443. txt = _ansispan(txt);
  444. return txt;
  445. }
  446. // Remove chunks that should be overridden by the effect of
  447. // carriage return characters
  448. function fixCarriageReturn(txt) {
  449. txt = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
  450. while (txt.search(/\r[^$]/g) > -1) {
  451. var base = txt.match(/^(.*)\r+/m)[1];
  452. var insert = txt.match(/\r+(.*)$/m)[1];
  453. insert = insert + base.slice(insert.length, base.length);
  454. txt = txt.replace(/\r+.*$/m, '\r').replace(/^.*\r/m, insert);
  455. }
  456. return txt;
  457. }
  458. // Remove characters that are overridden by backspace characters
  459. function fixBackspace(txt) {
  460. var tmp = txt;
  461. do {
  462. txt = tmp;
  463. // Cancel out anything-but-newline followed by backspace
  464. tmp = txt.replace(/[^\n]\x08/gm, '');
  465. } while (tmp.length < txt.length);
  466. return txt;
  467. }
  468. // Remove characters overridden by backspace and carriage return
  469. function fixOverwrittenChars(txt) {
  470. return fixCarriageReturn(fixBackspace(txt));
  471. }
  472. // Locate any URLs and convert them to an anchor tag
  473. function autoLinkUrls(txt) {
  474. return txt.replace(/(^|\s)(https?|ftp)(:[^'"<>\s]+)/gi,
  475. "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
  476. }
  477. var points_to_pixels = function (points) {
  478. /**
  479. * A reasonably good way of converting between points and pixels.
  480. */
  481. var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
  482. $('body').append(test);
  483. var pixel_per_point = test.width()/10000;
  484. test.remove();
  485. return Math.floor(points*pixel_per_point);
  486. };
  487. var always_new = function (constructor) {
  488. /**
  489. * wrapper around contructor to avoid requiring `var a = new constructor()`
  490. * useful for passing constructors as callbacks,
  491. * not for programmer laziness.
  492. * from https://programmers.stackexchange.com/questions/118798
  493. */
  494. return function () {
  495. var obj = Object.create(constructor.prototype);
  496. constructor.apply(obj, arguments);
  497. return obj;
  498. };
  499. };
  500. var url_path_join = function () {
  501. /**
  502. * join a sequence of url components with '/'
  503. */
  504. var url = '';
  505. for (var i = 0; i < arguments.length; i++) {
  506. if (arguments[i] === '') {
  507. continue;
  508. }
  509. if (url.length > 0 && url[url.length-1] != '/') {
  510. url = url + '/' + arguments[i];
  511. } else {
  512. url = url + arguments[i];
  513. }
  514. }
  515. url = url.replace(/\/\/+/, '/');
  516. return url;
  517. };
  518. var url_path_split = function (path) {
  519. /**
  520. * Like os.path.split for URLs.
  521. * Always returns two strings, the directory path and the base filename
  522. */
  523. var idx = path.lastIndexOf('/');
  524. if (idx === -1) {
  525. return ['', path];
  526. } else {
  527. return [ path.slice(0, idx), path.slice(idx + 1) ];
  528. }
  529. };
  530. var parse_url = function (url) {
  531. /**
  532. * an `a` element with an href allows attr-access to the parsed segments of a URL
  533. * a = parse_url("http://localhost:8888/path/name#hash")
  534. * a.protocol = "http:"
  535. * a.host = "localhost:8888"
  536. * a.hostname = "localhost"
  537. * a.port = 8888
  538. * a.pathname = "/path/name"
  539. * a.hash = "#hash"
  540. */
  541. var a = document.createElement("a");
  542. a.href = url;
  543. return a;
  544. };
  545. var encode_uri_components = function (uri) {
  546. /**
  547. * encode just the components of a multi-segment uri,
  548. * leaving '/' separators
  549. */
  550. return uri.split('/').map(encodeURIComponent).join('/');
  551. };
  552. var url_join_encode = function () {
  553. /**
  554. * join a sequence of url components with '/',
  555. * encoding each component with encodeURIComponent
  556. */
  557. return encode_uri_components(url_path_join.apply(null, arguments));
  558. };
  559. var splitext = function (filename) {
  560. /**
  561. * mimic Python os.path.splitext
  562. * Returns ['base', '.ext']
  563. */
  564. var idx = filename.lastIndexOf('.');
  565. if (idx > 0) {
  566. return [filename.slice(0, idx), filename.slice(idx)];
  567. } else {
  568. return [filename, ''];
  569. }
  570. };
  571. var escape_html = function (text) {
  572. /**
  573. * escape text to HTML
  574. */
  575. return $("<div/>").text(text).html();
  576. };
  577. var get_body_data = function(key) {
  578. /**
  579. * get a url-encoded item from body.data and decode it
  580. * we should never have any encoded URLs anywhere else in code
  581. * until we are building an actual request
  582. */
  583. var val = $('body').data(key);
  584. if (typeof val === 'undefined')
  585. return val;
  586. return decodeURIComponent(val);
  587. };
  588. var to_absolute_cursor_pos = function (cm, cursor) {
  589. console.warn('`utils.to_absolute_cursor_pos(cm, pos)` is deprecated. Use `cm.indexFromPos(cursor)`');
  590. return cm.indexFromPos(cursor);
  591. };
  592. var from_absolute_cursor_pos = function (cm, cursor_pos) {
  593. console.warn('`utils.from_absolute_cursor_pos(cm, pos)` is deprecated. Use `cm.posFromIndex(index)`');
  594. return cm.posFromIndex(cursor_pos);
  595. };
  596. // https://stackoverflow.com/questions/2400935/browser-detection-in-javascript
  597. var browser = (function() {
  598. if (typeof navigator === 'undefined') {
  599. // navigator undefined in node
  600. return 'None';
  601. }
  602. var N= navigator.appName, ua= navigator.userAgent, tem;
  603. var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
  604. if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
  605. M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
  606. return M;
  607. })();
  608. // https://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
  609. var platform = (function () {
  610. if (typeof navigator === 'undefined') {
  611. // navigator undefined in node
  612. return 'None';
  613. }
  614. var OSName="None";
  615. if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
  616. if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
  617. if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
  618. if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
  619. return OSName;
  620. })();
  621. var get_url_param = function (name) {
  622. // get a URL parameter. I cannot believe we actually need this.
  623. // Based on https://stackoverflow.com/a/25359264/938949
  624. var match = new RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
  625. if (match){
  626. return decodeURIComponent(match[1] || '');
  627. }
  628. };
  629. var is_or_has = function (a, b) {
  630. /**
  631. * Is b a child of a or a itself?
  632. */
  633. return a.has(b).length !==0 || a.is(b);
  634. };
  635. var is_focused = function (e) {
  636. /**
  637. * Is element e, or one of its children focused?
  638. */
  639. e = $(e);
  640. var target = $(document.activeElement);
  641. if (target.length > 0) {
  642. if (is_or_has(e, target)) {
  643. return true;
  644. } else {
  645. return false;
  646. }
  647. } else {
  648. return false;
  649. }
  650. };
  651. var mergeopt = function(_class, options, overwrite){
  652. options = options || {};
  653. overwrite = overwrite || {};
  654. return $.extend(true, {}, _class.options_default, options, overwrite);
  655. };
  656. var ajax_error_msg = function (jqXHR) {
  657. /**
  658. * Return a JSON error message if there is one,
  659. * otherwise the basic HTTP status text.
  660. */
  661. if (jqXHR.responseJSON && jqXHR.responseJSON.traceback) {
  662. return jqXHR.responseJSON.traceback;
  663. } else if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
  664. return jqXHR.responseJSON.message;
  665. } else {
  666. return jqXHR.statusText;
  667. }
  668. };
  669. var log_ajax_error = function (jqXHR, status, error) {
  670. /**
  671. * log ajax failures with informative messages
  672. */
  673. var msg = "API request failed (" + jqXHR.status + "): ";
  674. console.log(jqXHR);
  675. msg += ajax_error_msg(jqXHR);
  676. console.log(msg);
  677. };
  678. var requireCodeMirrorMode = function (mode, callback, errback) {
  679. /**
  680. * find a predefined mode or detect from CM metadata then
  681. * require and callback with the resolveable mode string: mime or
  682. * custom name
  683. */
  684. var modename = (typeof mode == "string") ? mode :
  685. mode.mode || mode.name;
  686. // simplest, cheapest check by mode name: mode may also have config
  687. if (CodeMirror.modes.hasOwnProperty(modename)) {
  688. // return the full mode object, if it has a name
  689. callback(mode.name ? mode : modename);
  690. return;
  691. }
  692. // *somehow* get back a CM.modeInfo-like object that has .mode and
  693. // .mime
  694. var info = (mode && mode.mode && mode.mime && mode) ||
  695. CodeMirror.findModeByName(modename) ||
  696. CodeMirror.findModeByExtension(modename.split(".").slice(-1)) ||
  697. CodeMirror.findModeByMIME(modename) ||
  698. {mode: modename, mime: modename};
  699. requirejs([
  700. // might want to use CodeMirror.modeURL here
  701. ['codemirror/mode', info.mode, info.mode].join('/'),
  702. ], function() {
  703. // return the original mode, as from a kernelspec on first load
  704. // or the mimetype, as for most highlighting
  705. callback(mode.name ? mode : info.mime);
  706. }, errback
  707. );
  708. };
  709. /** Error type for wrapped XHR errors. */
  710. var XHR_ERROR = 'XhrError';
  711. /**
  712. * Wraps an AJAX error as an Error object.
  713. */
  714. var wrap_ajax_error = function (jqXHR, status, error) {
  715. var wrapped_error = new Error(ajax_error_msg(jqXHR));
  716. wrapped_error.name = XHR_ERROR;
  717. // provide xhr response
  718. wrapped_error.xhr = jqXHR;
  719. wrapped_error.xhr_status = status;
  720. wrapped_error.xhr_error = error;
  721. return wrapped_error;
  722. };
  723. var ajax = function (url, settings) {
  724. // like $.ajax, but ensure XSRF or Authorization header is set
  725. if (typeof url === "object") {
  726. // called with single argument: $.ajax({url: '...'})
  727. settings = url;
  728. url = settings.url;
  729. delete settings.url;
  730. }
  731. settings = _add_auth_header(settings);
  732. return $.ajax(url, settings);
  733. };
  734. var _get_cookie = function (name) {
  735. // from tornado docs: http://www.tornadoweb.org/en/stable/guide/security.html
  736. var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
  737. return r ? r[1] : undefined;
  738. }
  739. var _add_auth_header = function (settings) {
  740. /**
  741. * Adds auth header to jquery ajax settings
  742. */
  743. settings = settings || {};
  744. if (!settings.headers) {
  745. settings.headers = {};
  746. }
  747. if (!settings.headers.Authorization) {
  748. var xsrf_token = _get_cookie('_xsrf');
  749. if (xsrf_token) {
  750. settings.headers['X-XSRFToken'] = xsrf_token;
  751. }
  752. }
  753. return settings;
  754. };
  755. var promising_ajax = function(url, settings) {
  756. /**
  757. * Like $.ajax, but returning an ES6 promise. success and error settings
  758. * will be ignored.
  759. */
  760. settings = settings || {};
  761. return new Promise(function(resolve, reject) {
  762. settings.success = function(data, status, jqXHR) {
  763. resolve(data);
  764. };
  765. settings.error = function(jqXHR, status, error) {
  766. log_ajax_error(jqXHR, status, error);
  767. reject(wrap_ajax_error(jqXHR, status, error));
  768. };
  769. ajax(url, settings);
  770. });
  771. };
  772. var WrappedError = function(message, error){
  773. /**
  774. * Wrappable Error class
  775. *
  776. * The Error class doesn't actually act on `this`. Instead it always
  777. * returns a new instance of Error. Here we capture that instance so we
  778. * can apply it's properties to `this`.
  779. */
  780. var tmp = Error.apply(this, [message]);
  781. // Copy the properties of the error over to this.
  782. var properties = Object.getOwnPropertyNames(tmp);
  783. for (var i = 0; i < properties.length; i++) {
  784. this[properties[i]] = tmp[properties[i]];
  785. }
  786. // Keep a stack of the original error messages.
  787. if (error instanceof WrappedError) {
  788. this.error_stack = error.error_stack;
  789. } else {
  790. this.error_stack = [error];
  791. }
  792. this.error_stack.push(tmp);
  793. return this;
  794. };
  795. WrappedError.prototype = Object.create(Error.prototype, {});
  796. var load_class = function(class_name, module_name, registry) {
  797. /**
  798. * Tries to load a class
  799. *
  800. * Tries to load a class from a module using require.js, if a module
  801. * is specified, otherwise tries to load a class from the global
  802. * registry, if the global registry is provided.
  803. */
  804. return new Promise(function(resolve, reject) {
  805. // Try loading the view module using require.js
  806. if (module_name) {
  807. requirejs([module_name], function(module) {
  808. if (module[class_name] === undefined) {
  809. reject(new Error('Class '+class_name+' not found in module '+module_name));
  810. } else {
  811. resolve(module[class_name]);
  812. }
  813. }, reject);
  814. } else {
  815. if (registry && registry[class_name]) {
  816. resolve(registry[class_name]);
  817. } else {
  818. reject(new Error('Class '+class_name+' not found in registry '));
  819. }
  820. }
  821. });
  822. };
  823. var resolve_promises_dict = function(d) {
  824. /**
  825. * Resolve a promiseful dictionary.
  826. * Returns a single Promise.
  827. */
  828. var keys = Object.keys(d);
  829. var values = [];
  830. keys.forEach(function(key) {
  831. values.push(d[key]);
  832. });
  833. return Promise.all(values).then(function(v) {
  834. d = {};
  835. for(var i=0; i<keys.length; i++) {
  836. d[keys[i]] = v[i];
  837. }
  838. return d;
  839. });
  840. };
  841. var reject = function(message, log) {
  842. /**
  843. * Creates a wrappable Promise rejection function.
  844. *
  845. * Creates a function that returns a Promise.reject with a new WrappedError
  846. * that has the provided message and wraps the original error that
  847. * caused the promise to reject.
  848. */
  849. return function(error) {
  850. var wrapped_error = new WrappedError(message, error);
  851. if (log) {
  852. console.error(message, " -- ", error);
  853. }
  854. return Promise.reject(wrapped_error);
  855. };
  856. };
  857. var typeset = function(element, text) {
  858. /**
  859. * Apply MathJax rendering to an element, and optionally set its text
  860. *
  861. * If MathJax is not available, make no changes.
  862. *
  863. * Returns the output any number of typeset elements, or undefined if
  864. * MathJax was not available.
  865. *
  866. * Parameters
  867. * ----------
  868. * element: Node, NodeList, or jQuery selection
  869. * text: option string
  870. */
  871. var $el = element.jquery ? element : $(element);
  872. if(arguments.length > 1){
  873. $el.text(text);
  874. }
  875. if(!window.MathJax){
  876. return;
  877. }
  878. $el.map(function(){
  879. // MathJax takes a DOM node: $.map makes `this` the context
  880. MathJax.Hub.Queue(["Typeset", MathJax.Hub, this]);
  881. try {
  882. MathJax.Hub.Queue(
  883. ["Require", MathJax.Ajax, "[MathJax]/extensions/TeX/AMSmath.js"],
  884. function() { MathJax.InputJax.TeX.resetEquationNumbers(); }
  885. );
  886. } catch (e) {
  887. console.error("Error queueing resetEquationNumbers:", e);
  888. }
  889. });
  890. };
  891. var parse_b64_data_uri = function(uri) {
  892. /**
  893. * Parses a base64 encoded data-uri to extract mimetype and the
  894. * base64 string.
  895. *
  896. * For example, given 'data:image/png;base64,iVBORw', it will return
  897. * ["image/png", "iVBORw"]
  898. *
  899. * Parameters
  900. */
  901. // For performance reasons, the non-greedy ? qualifiers are crucial so
  902. // that the matcher stops early on big blobs. Without them, it will try
  903. // to match the whole blob which can take ages
  904. var regex = /^data:(.+?\/.+?);base64,/;
  905. var matches = uri.match(regex);
  906. var mime = matches[1];
  907. // matches[0] contains the whole data-uri prefix
  908. var b64_data = uri.slice(matches[0].length);
  909. return [mime, b64_data];
  910. };
  911. var time = {};
  912. time.milliseconds = {};
  913. time.milliseconds.s = 1000;
  914. time.milliseconds.m = 60 * time.milliseconds.s;
  915. time.milliseconds.h = 60 * time.milliseconds.m;
  916. time.milliseconds.d = 24 * time.milliseconds.h;
  917. time.thresholds = {
  918. // moment.js thresholds in milliseconds
  919. s: moment.relativeTimeThreshold('s') * time.milliseconds.s,
  920. m: moment.relativeTimeThreshold('m') * time.milliseconds.m,
  921. h: moment.relativeTimeThreshold('h') * time.milliseconds.h,
  922. d: moment.relativeTimeThreshold('d') * time.milliseconds.d,
  923. };
  924. time.timeout_from_dt = function (dt) {
  925. /** compute a timeout based on dt
  926. input and output both in milliseconds
  927. use moment's relative time thresholds:
  928. - 10 seconds if in 'seconds ago' territory
  929. - 1 minute if in 'minutes ago'
  930. - 1 hour otherwise
  931. */
  932. if (dt < time.thresholds.s) {
  933. return 10 * time.milliseconds.s;
  934. } else if (dt < time.thresholds.m) {
  935. return time.milliseconds.m;
  936. } else {
  937. return time.milliseconds.h;
  938. }
  939. };
  940. var format_datetime = function(date) {
  941. var text = moment(date).fromNow();
  942. return text === 'a few seconds ago' ? 'seconds ago' : text;
  943. };
  944. var datetime_sort_helper = function(a, b, order) {
  945. if (moment(a).isBefore(moment(b))) {
  946. return (order == 1) ? -1 : 1;
  947. } else if (moment(a).isSame(moment(b))) {
  948. return 0;
  949. } else {
  950. return (order == 1) ? 1 : -1;
  951. }
  952. };
  953. /**
  954. source: https://github.com/sindresorhus/pretty-bytes
  955. The MIT License (MIT)
  956. Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
  957. Permission is hereby granted, free of charge, to any person obtaining a copy
  958. of this software and associated documentation files (the "Software"), to deal
  959. in the Software without restriction, including without limitation the rights
  960. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  961. copies of the Software, and to permit persons to whom the Software is
  962. furnished to do so, subject to the following conditions:
  963. The above copyright notice and this permission notice shall be included in
  964. all copies or substantial portions of the Software.
  965. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  966. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  967. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  968. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  969. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  970. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  971. THE SOFTWARE.
  972. **/
  973. var format_filesize = function(num) {
  974. if (num === undefined || num === null)
  975. return;
  976. var UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  977. if (!Number.isFinite(num)) {
  978. console.error("Expected finite number, got ", typeof(num) + ": " + num);
  979. }
  980. var neg = num < 0;
  981. if (neg) {
  982. num = -num;
  983. }
  984. if (num < 1) {
  985. return (neg ? '-' : '') + num + ' B';
  986. }
  987. var exponent = Math.min(Math.floor(Math.log10(num) / 3), UNITS.length - 1);
  988. var numStr = Number((num / Math.pow(1000, exponent)).toPrecision(3));
  989. var unit = UNITS[exponent];
  990. return (neg ? '-' : '') + numStr + ' ' + unit;
  991. }
  992. // javascript stores text as utf16 and string indices use "code units",
  993. // which stores high-codepoint characters as "surrogate pairs",
  994. // which occupy two indices in the javascript string.
  995. // We need to translate cursor_pos in the protocol (in characters)
  996. // to js offset (with surrogate pairs taking two spots).
  997. function js_idx_to_char_idx (js_idx, text) {
  998. var char_idx = js_idx;
  999. for (var i = 0; i + 1 < text.length && i < js_idx; i++) {
  1000. var char_code = text.charCodeAt(i);
  1001. // check for surrogate pair
  1002. if (char_code >= 0xD800 && char_code <= 0xDBFF) {
  1003. var next_char_code = text.charCodeAt(i+1);
  1004. if (next_char_code >= 0xDC00 && next_char_code <= 0xDFFF) {
  1005. char_idx--;
  1006. i++;
  1007. }
  1008. }
  1009. }
  1010. return char_idx;
  1011. }
  1012. function char_idx_to_js_idx (char_idx, text) {
  1013. var js_idx = char_idx;
  1014. for (var i = 0; i + 1 < text.length && i < js_idx; i++) {
  1015. var char_code = text.charCodeAt(i);
  1016. // check for surrogate pair
  1017. if (char_code >= 0xD800 && char_code <= 0xDBFF) {
  1018. var next_char_code = text.charCodeAt(i+1);
  1019. if (next_char_code >= 0xDC00 && next_char_code <= 0xDFFF) {
  1020. js_idx++;
  1021. i++;
  1022. }
  1023. }
  1024. }
  1025. return js_idx;
  1026. }
  1027. if ('𝐚'.length === 1) {
  1028. // If javascript fixes string indices of non-BMP characters,
  1029. // don't keep shifting offsets to compensate for surrogate pairs
  1030. char_idx_to_js_idx = js_idx_to_char_idx = function (idx, text) { return idx; };
  1031. }
  1032. // Test if a drag'n'drop event contains a file (as opposed to an HTML
  1033. // element/text from the document)
  1034. var dnd_contain_file = function(event) {
  1035. // As per the HTML5 drag'n'drop spec, the dataTransfer.types should
  1036. // contain one "Files" type if a file is being dragged
  1037. // https://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#dom-datatransfer-types
  1038. if (event.dataTransfer.types) {
  1039. for (var i = 0; i < event.dataTransfer.types.length; i++) {
  1040. if (event.dataTransfer.types[i] == "Files") {
  1041. return true;
  1042. }
  1043. }
  1044. }
  1045. return false;
  1046. };
  1047. var throttle = function(fn, time) {
  1048. var pending = null;
  1049. return function () {
  1050. if (pending) return;
  1051. pending = setTimeout(run, time);
  1052. return function () {
  1053. clearTimeout(pending);
  1054. pending = null;
  1055. }
  1056. }
  1057. function run () {
  1058. pending = null;
  1059. fn();
  1060. }
  1061. }
  1062. var change_favicon = function (src) {
  1063. var link = document.createElement('link'),
  1064. oldLink = document.getElementById('favicon');
  1065. link.id = 'favicon';
  1066. link.type = 'image/x-icon';
  1067. link.rel = 'shortcut icon';
  1068. link.href = utils.url_path_join(utils.get_body_data('baseUrl'), src);
  1069. if (oldLink && (link.href === oldLink.href)) {
  1070. // This favicon is already set, don't modify the DOM.
  1071. return;
  1072. }
  1073. if (oldLink) document.head.removeChild(oldLink);
  1074. document.head.appendChild(link);
  1075. };
  1076. var utils = {
  1077. throttle: throttle,
  1078. is_loaded: is_loaded,
  1079. load_extension: load_extension,
  1080. load_extensions: load_extensions,
  1081. filter_extensions: filter_extensions,
  1082. load_extensions_from_config: load_extensions_from_config,
  1083. regex_split : regex_split,
  1084. uuid : uuid,
  1085. fixConsole : fixConsole,
  1086. fixCarriageReturn : fixCarriageReturn,
  1087. fixBackspace : fixBackspace,
  1088. fixOverwrittenChars: fixOverwrittenChars,
  1089. autoLinkUrls : autoLinkUrls,
  1090. points_to_pixels : points_to_pixels,
  1091. get_body_data : get_body_data,
  1092. parse_url : parse_url,
  1093. url_path_split : url_path_split,
  1094. url_path_join : url_path_join,
  1095. url_join_encode : url_join_encode,
  1096. encode_uri_components : encode_uri_components,
  1097. splitext : splitext,
  1098. escape_html : escape_html,
  1099. always_new : always_new,
  1100. to_absolute_cursor_pos : to_absolute_cursor_pos,
  1101. from_absolute_cursor_pos : from_absolute_cursor_pos,
  1102. browser : browser,
  1103. platform: platform,
  1104. get_url_param: get_url_param,
  1105. is_or_has : is_or_has,
  1106. is_focused : is_focused,
  1107. mergeopt: mergeopt,
  1108. requireCodeMirrorMode : requireCodeMirrorMode,
  1109. XHR_ERROR : XHR_ERROR,
  1110. ajax : ajax,
  1111. ajax_error_msg : ajax_error_msg,
  1112. log_ajax_error : log_ajax_error,
  1113. wrap_ajax_error : wrap_ajax_error,
  1114. promising_ajax : promising_ajax,
  1115. WrappedError: WrappedError,
  1116. load_class: load_class,
  1117. resolve_promises_dict: resolve_promises_dict,
  1118. reject: reject,
  1119. typeset: typeset,
  1120. parse_b64_data_uri: parse_b64_data_uri,
  1121. time: time,
  1122. format_datetime: format_datetime,
  1123. format_filesize: format_filesize,
  1124. datetime_sort_helper: datetime_sort_helper,
  1125. dnd_contain_file: dnd_contain_file,
  1126. js_idx_to_char_idx: js_idx_to_char_idx,
  1127. char_idx_to_js_idx: char_idx_to_js_idx,
  1128. _ansispan:_ansispan,
  1129. change_favicon: change_favicon
  1130. };
  1131. return utils;
  1132. });