Guest User

Untitled

a guest
Aug 16th, 2017
55
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 182.55 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Live counting extension
  3. // @description Live Counting
  4. // @author /u/
  5. // @namespace none lol
  6. // @include *://*.reddit.com/live/*
  7. // @exclude *://*.reddit.com/live/create*
  8. // @exclude *://*.reddit.com/live/*/edit*
  9. // @exclude *://*.reddit.com/live/*/updates/*
  10. // @exclude *://*.reddit.com/live/*/contributors*
  11. // ==/UserScript==
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Your code here...
  18. /**
  19. /**
  20. * LIVE COUNTING EXTENSION V1.5.3
  21. * (THIS CODE WAS GENERATED FROM THE TYPESCRIPT .TS FILES IN THE SRC DIRECTORY)
  22. */
  23. // CONSTANTS
  24. // Extension version
  25. var VERSION = 'v1.5.3';
  26. // Client's username
  27. var USER = $('#header .user a[href]').html();
  28. var chu = 0;
  29. // Thread ID
  30. var THREAD = (function () {
  31. var components = window.location.pathname.split('/');
  32. for (var i = components.length - 1; i >= 0; i--) {
  33. var component = components[i].trim();
  34. if (component.length > 0)
  35. return component.replace(/^.*\/([^/]*)/, "$1");
  36. }
  37. })();
  38. ////////////////
  39. // Cookies.ts //
  40. ////////////////
  41. // Adapted TypeScript version of the js-cookie library (https://github.com/js-cookie/js-cookie)
  42. var Cookies = (function () {
  43. function extend() {
  44. var args = [];
  45. for (var _i = 0; _i < arguments.length; _i++) {
  46. args[_i] = arguments[_i];
  47. }
  48. var i = 0;
  49. var result = {};
  50. for (; i < args.length; i++) {
  51. var attributes = args[i];
  52. for (var key in attributes) {
  53. result[key] = attributes[key];
  54. }
  55. }
  56. return result;
  57. }
  58. function init(converter) {
  59. var api = function (key, value, attributes) {
  60. var result;
  61. if (typeof document === 'undefined') {
  62. return;
  63. }
  64. // Write
  65. if (arguments.length > 1) {
  66. attributes = extend({
  67. path: '/'
  68. }, api.defaults, attributes);
  69. if (typeof attributes.expires === 'number') {
  70. var expires = new Date();
  71. expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
  72. attributes.expires = expires;
  73. }
  74. try {
  75. result = JSON.stringify(value);
  76. if (/^[\{\[]/.test(result)) {
  77. value = result;
  78. }
  79. }
  80. catch (e) { }
  81. if (!converter.write) {
  82. value = encodeURIComponent(String(value))
  83. .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
  84. }
  85. else {
  86. value = converter.write(value, key);
  87. }
  88. key = encodeURIComponent(String(key));
  89. key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
  90. key = key.replace(/[\(\)]/g, encodeURI);
  91. return (document.cookie = [
  92. key, '=', value,
  93. attributes.expires ? '; expires=' + attributes.expires.toUTCString() : '',
  94. attributes.path ? '; path=' + attributes.path : '',
  95. attributes.domain ? '; domain=' + attributes.domain : '',
  96. attributes.secure ? '; secure' : ''
  97. ].join(''));
  98. }
  99. // Read
  100. if (!key) {
  101. result = {};
  102. }
  103. // To prevent the for loop in the first place assign an empty array
  104. // in case there are no cookies at all. Also prevents odd result when
  105. // calling "get()"
  106. var cookies = document.cookie ? document.cookie.split('; ') : [];
  107. var rdecode = /(%[0-9A-Z]{2})+/g;
  108. var i = 0;
  109. for (; i < cookies.length; i++) {
  110. var parts = cookies[i].split('=');
  111. var cookie = parts.slice(1).join('=');
  112. if (cookie.charAt(0) === '"') {
  113. cookie = cookie.slice(1, -1);
  114. }
  115. try {
  116. var name = parts[0].replace(rdecode, decodeURIComponent);
  117. cookie = converter.read ?
  118. converter.read(cookie, name) : converter(cookie, name) ||
  119. cookie.replace(rdecode, decodeURIComponent);
  120. if (this.json) {
  121. try {
  122. cookie = JSON.parse(cookie);
  123. }
  124. catch (e) { }
  125. }
  126. if (key === name) {
  127. result = cookie;
  128. break;
  129. }
  130. if (!key) {
  131. result[name] = cookie;
  132. }
  133. }
  134. catch (e) { }
  135. }
  136. return result;
  137. };
  138. api.set = api;
  139. api.get = function (key) {
  140. return api.call(api, key);
  141. };
  142. api.getJSON = function () {
  143. return api.apply({
  144. json: true
  145. }, [].slice.call(arguments));
  146. };
  147. api.defaults = {};
  148. api.remove = function (key, attributes) {
  149. api(key, '', extend(attributes, {
  150. expires: -1
  151. }));
  152. };
  153. api.withConverter = init;
  154. return api;
  155. }
  156. return init(function () { });
  157. })();
  158. ////////////////
  159. // SnuOwnd.ts //
  160. ////////////////
  161. // Adapted TypeScript version of the SnuOwnd markdown parser (https://github.com/gamefreak/snuownd)
  162. var SnuOwnd = {};
  163. (function (exports) {
  164. function _isspace(c) { return c == ' ' || c == '\n'; }
  165. function isspace(c) { return /[\x09-\x0d ]/.test(c); }
  166. function isalnum(c) { return /[A-Za-z0-9]/.test(c); }
  167. function isalpha(c) { return /[A-Za-z]/.test(c); }
  168. function isdigit(c) { return /[0-9]/.test(c); }
  169. function isxdigit(c) { return /[0-9a-fA-F]/.test(c); }
  170. function ispunct(c) { return /[\x20-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]/.test(c); }
  171. function urlHexCode(number) {
  172. var hex_str = '0123456789ABCDEF';
  173. return '%' + hex_str[(number & 0xf0) >> 4] + hex_str[(number & 0x0f) >> 0];
  174. }
  175. function escapeUTF8Char(char) {
  176. var code = char.charCodeAt(0);
  177. if (code < 0x80) {
  178. return urlHexCode(code);
  179. }
  180. else if ((code > 0x7f) && (code < 0x0800)) {
  181. var seq = urlHexCode(code >> 6 & 0xff | 0xc0);
  182. seq += urlHexCode(code >> 0 & 0x3f | 0x80);
  183. return seq;
  184. }
  185. else {
  186. var seq = urlHexCode(code >> 12 & 0xff | 0xe0);
  187. seq += urlHexCode(code >> 6 & 0x3f | 0x80);
  188. seq += urlHexCode(code >> 0 & 0x3f | 0x80);
  189. return seq;
  190. }
  191. }
  192. function find_block_tag(str) {
  193. var wordList = [
  194. 'p', 'dl', 'div', 'math',
  195. 'table', 'ul', 'del', 'form',
  196. 'blockquote', 'figure', 'ol', 'fieldset',
  197. 'h1', 'h6', 'pre', 'script',
  198. 'h5', 'noscript', 'style', 'iframe',
  199. 'h4', 'ins', 'h3', 'h2'
  200. ];
  201. if (wordList.indexOf(str.toLowerCase()) != -1) {
  202. return str.toLowerCase();
  203. }
  204. return '';
  205. }
  206. function sdhtml_is_tag(tag_data, tagname) {
  207. var i;
  208. var closed = 0;
  209. var tag_size = tag_data.length;
  210. if (tag_size < 3 || tag_data[0] != '<')
  211. return HTML_TAG_NONE;
  212. i = 1;
  213. if (tag_data[i] == '/') {
  214. closed = 1;
  215. i++;
  216. }
  217. var tagname_c = 0;
  218. for (; i < tag_size; ++i, ++tagname_c) {
  219. if (tagname_c >= tagname.length)
  220. break;
  221. if (tag_data[i] != tagname[tagname_c])
  222. return HTML_TAG_NONE;
  223. }
  224. if (i == tag_size)
  225. return HTML_TAG_NONE;
  226. if (isspace(tag_data[i]) || tag_data[i] == '>')
  227. return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;
  228. return HTML_TAG_NONE;
  229. }
  230. function unscape_text(out, src) {
  231. var i = 0, org;
  232. while (i < src.s.length) {
  233. org = i;
  234. while (i < src.s.length && src.s[i] != '\\')
  235. i++;
  236. if (i > org)
  237. out.s += src.s.slice(org, i);
  238. if (i + 1 >= src.s.length)
  239. break;
  240. out.s += src.s[i + 1];
  241. i += 2;
  242. }
  243. }
  244. // Parsers tend to choke on entities with values greater than this
  245. var MAX_NUM_ENTITY_VAL = 0x10ffff;
  246. function is_valid_numeric_entity(entity_val) {
  247. /* Some XML parsers will choke on entities with certain
  248. * values (mostly control characters.)
  249. *
  250. * According to lxml these are all problematic:
  251. *
  252. * [xrange(0, 8),
  253. * xrange(11, 12),
  254. * xrange(14, 31),
  255. * xrange(55296, 57343),
  256. * xrange(65534, 65535)]
  257. */
  258. return (entity_val > 8
  259. && (entity_val !== 11 && entity_val > 12)
  260. && (entity_val < 14 || entity_val > 31)
  261. && (entity_val < 55296 || entity_val > 57343)
  262. && (entity_val !== 65534 && entity_val !== 65535)
  263. && entity_val <= MAX_NUM_ENTITY_VAL);
  264. }
  265. // Any numeric entity longer than this is obviously above MAX_ENTITY_CHAR
  266. // used to avoid dealing with overflows.
  267. var MAX_NUM_ENTITY_LEN = 7;
  268. var VALID_ENTITIES = [
  269. '&AElig;', '&Aacute;', '&Acirc;', '&Agrave;', '&Alpha;', '&Aring;',
  270. '&Atilde;', '&Auml;', '&Beta;', '&Ccedil;', '&Chi;', '&Dagger;', '&Delta;',
  271. '&ETH;', '&Eacute;', '&Ecirc;', '&Egrave;', '&Epsilon;', '&Eta;', '&Euml;',
  272. '&Gamma;', '&Iacute;', '&Icirc;', '&Igrave;', '&Iota;', '&Iuml;', '&Kappa;',
  273. '&Lambda;', '&Mu;', '&Ntilde;', '&Nu;', '&OElig;', '&Oacute;', '&Ocirc;',
  274. '&Ograve;', '&Omega;', '&Omicron;', '&Oslash;', '&Otilde;', '&Ouml;', '&Phi;',
  275. '&Pi;', '&Prime;', '&Psi;', '&Rho;', '&Scaron;', '&Sigma;', '&THORN;', '&Tau;',
  276. '&Theta;', '&Uacute;', '&Ucirc;', '&Ugrave;', '&Upsilon;', '&Uuml;', '&Xi;',
  277. '&Yacute;', '&Yuml;', '&Zeta;', '&aacute;', '&acirc;', '&acute;', '&aelig;',
  278. '&agrave;', '&alefsym;', '&alpha;', '&amp;', '&and;', '&ang;', '&apos;',
  279. '&aring;', '&asymp;', '&atilde;', '&auml;', '&bdquo;', '&beta;', '&brvbar;',
  280. '&bull;', '&cap;', '&ccedil;', '&cedil;', '&cent;', '&chi;', '&circ;',
  281. '&clubs;', '&cong;', '&copy;', '&crarr;', '&cup;', '&curren;', '&dArr;',
  282. '&dagger;', '&darr;', '&deg;', '&delta;', '&diams;', '&divide;',
  283. '&eacute;', '&ecirc;', '&egrave;', '&empty;', '&emsp;', '&ensp;',
  284. '&epsilon;', '&equiv;', '&eta;', '&eth;', '&euml;', '&euro;', '&exist;',
  285. '&fnof;', '&forall;', '&frac12;', '&frac14;', '&frac34;', '&frasl;',
  286. '&gamma;', '&ge;', '&gt;', '&hArr;', '&harr;', '&hearts;', '&hellip;',
  287. '&iacute;', '&icirc;', '&iexcl;', '&igrave;', '&image;',
  288. '&infin;',
  289. '&int;', '&iota;', '&iquest;', '&isin;', '&iuml;', '&kappa;', '&lArr;',
  290. '&lambda;', '&lang;', '&laquo;', '&larr;', '&lceil;', '&ldquo;', '&le;',
  291. '&lfloor;', '&lowast;', '&loz;', '&lrm;', '&lsaquo;', '&lsquo;', '&lt;',
  292. '&macr;', '&mdash;', '&micro;', '&middot;', '&minus;', '&mu;', '&nabla;',
  293. '&nbsp;', '&ndash;', '&ne;', '&ni;', '&not;', '&notin;', '&nsub;', '&ntilde;',
  294. '&nu;', '&oacute;', '&ocirc;', '&oelig;', '&ograve;', '&oline;', '&omega;',
  295. '&omicron;', '&oplus;', '&or;', '&ordf;', '&ordm;', '&oslash;', '&otilde;',
  296. '&otimes;', '&ouml;', '&para;', '&part;', '&permil;', '&perp;', '&phi;',
  297. '&pi;', '&piv;', '&plusmn;', '&pound;', '&prime;', '&prod;', '&prop;',
  298. '&psi;', '&quot;', '&rArr;', '&radic;', '&rang;', '&raquo;', '&rarr;',
  299. '&rceil;', '&rdquo;', '&real;', '&reg;', '&rfloor;', '&rho;', '&rlm;',
  300. '&rsaquo;', '&rsquo;', '&sbquo;', '&scaron;', '&sdot;', '&sect;', '&shy;',
  301. '&sigma;', '&sigmaf;', '&sim;', '&spades;', '&sub;', '&sube;', '&sum;',
  302. '&sup1;', '&sup2;', '&sup3;', '&sup;', '&supe;', '&szlig;', '&tau;',
  303. '&there4;', '&theta;', '&thetasym;', '&thinsp;', '&thorn;', '&tilde;',
  304. '&times;', '&trade;', '&uArr;', '&uacute;', '&uarr;', '&ucirc;', '&ugrave;',
  305. '&uml;', '&upsih;', '&upsilon;', '&uuml;', '&weierp;', '&xi;', '&yacute;',
  306. '&yen;', '&yuml;', '&zeta;', '&zwj;', '&zwnj;',
  307. ];
  308. /**
  309. * According to the OWASP rules:
  310. *
  311. * & --> &amp;
  312. * < --> &lt;
  313. * > --> &gt;
  314. * " --> &quot;
  315. * ' --> &#x27; &apos; is not recommended
  316. * / --> &#x2F; forward slash is included as it helps end an HTML entity
  317. *
  318. */
  319. var HTML_ESCAPE_TABLE = [
  320. 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 7, 7, 0, 7, 7,
  321. 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
  322. 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4,
  323. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0,
  324. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  325. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  326. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  327. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  328. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  329. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  330. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  331. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  332. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  333. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  334. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  335. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  336. ];
  337. var HTML_ESCAPES = ["", "&quot;", "&amp;", "&#39;", "&#47;", "&lt;", "&gt;", "" /* throw out control characters */];
  338. function escape_html(out, src, secure) {
  339. var i = 0, org, esc = 0;
  340. while (i < src.length) {
  341. org = i;
  342. while (i < src.length && !(esc = HTML_ESCAPE_TABLE[src.charCodeAt(i)]))
  343. i++;
  344. if (i > org)
  345. out.s += src.slice(org, i);
  346. /* escaping */
  347. if (i >= src.length)
  348. break;
  349. /* The forward slash is only escaped in secure mode */
  350. if (src[i] == '/' && !secure) {
  351. out.s += '/';
  352. }
  353. else if (src[i] == '\'') {
  354. out.s += '\'';
  355. }
  356. else if (src[i] == '"') {
  357. out.s += '"';
  358. }
  359. else if (HTML_ESCAPE_TABLE[src.charCodeAt(i)] == 7) {
  360. }
  361. else {
  362. out.s += HTML_ESCAPES[esc];
  363. }
  364. i++;
  365. }
  366. }
  367. /*
  368. * The following characters will not be escaped:
  369. *
  370. * -_.+!*'(),%#@?=;:/,+&$ alphanum
  371. *
  372. * Note that this character set is the addition of:
  373. *
  374. * - The characters which are safe to be in an URL
  375. * - The characters which are *not* safe to be in
  376. * an URL because they are RESERVED characters.
  377. *
  378. * We asume (lazily) that any RESERVED char that
  379. * appears inside an URL is actually meant to
  380. * have its native function (i.e. as an URL
  381. * component/separator) and hence needs no escaping.
  382. *
  383. * There are two exceptions: the chacters & (amp)
  384. * and ' (single quote) do not appear in the table.
  385. * They are meant to appear in the URL as components,
  386. * yet they require special HTML-entity escaping
  387. * to generate valid HTML markup.
  388. *
  389. * All other characters will be escaped to %XX.
  390. *
  391. */
  392. var HREF_SAFE = [
  393. 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2,
  394. 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  395. 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
  396. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
  397. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  398. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
  399. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  400. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
  401. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  402. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  403. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  404. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  405. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  406. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  407. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  408. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  409. ];
  410. function escape_href(out, src) {
  411. var i = 0, org;
  412. while (i < src.length) {
  413. org = i;
  414. /* Skip by characters that don't need special processing */
  415. while (i < src.length && HREF_SAFE[src.charCodeAt(i)] === 1)
  416. i++;
  417. if (i > org)
  418. out.s += src.slice(org, i);
  419. /* escaping */
  420. if (i >= src.length)
  421. break;
  422. /* throw out control characters */
  423. if (HREF_SAFE[src.charCodeAt(i)] == 2) {
  424. i++;
  425. continue;
  426. }
  427. switch (src[i]) {
  428. /* amp appears all the time in URLs, but needs
  429. * HTML-entity escaping to be inside an href */
  430. case '&':
  431. out.s += '&amp;';
  432. break;
  433. /* the single quote is a valid URL character
  434. * according to the standard; it needs HTML
  435. * entity escaping too */
  436. case '\'':
  437. out.s += '&#x27;';
  438. break;
  439. /* the space can be escaped to %20 or a plus
  440. * sign. we're going with the generic escape
  441. * for now. the plus thing is more commonly seen
  442. * when building GET strings */
  443. /*
  444. //This was disabled
  445. case ' ':
  446. out.s += '+'
  447. break;
  448. //*/
  449. /* every other character goes with a %XX escaping */
  450. default:
  451. out.s += escapeUTF8Char(src[i]);
  452. }
  453. i++;
  454. }
  455. }
  456. // function autolink_delim(uint8_t *data, size_t link_end, size_t offset, size_t size)
  457. function autolink_delim(data_, link_end, offset, size) {
  458. var data = data_.slice(offset);
  459. var cclose;
  460. var copen = 0;
  461. var i;
  462. for (i = 0; i < link_end; ++i)
  463. if (data[i] == '<') {
  464. link_end = i;
  465. break;
  466. }
  467. while (link_end > 0) {
  468. var c = data[link_end - 1];
  469. if (c === "\0")
  470. break;
  471. if ("?!.,".indexOf(c) !== -1) {
  472. link_end--;
  473. }
  474. else if (c === ';') {
  475. var new_end = link_end - 2;
  476. while (new_end > 0 && isalpha(data[new_end]))
  477. new_end--;
  478. if (new_end < link_end - 2 && data[new_end] == '&')
  479. link_end = new_end;
  480. else
  481. link_end--;
  482. }
  483. else
  484. break;
  485. }
  486. if (link_end == 0)
  487. return 0;
  488. cclose = data[link_end - 1];
  489. switch (cclose) {
  490. case '"':
  491. copen = '"';
  492. break;
  493. case '\'':
  494. copen = '\'';
  495. break;
  496. case ')':
  497. copen = '(';
  498. break;
  499. case ']':
  500. copen = '[';
  501. break;
  502. case '}':
  503. copen = '{';
  504. break;
  505. }
  506. if (copen != 0) {
  507. var closing = 0;
  508. var opening = 0;
  509. var j = 0;
  510. /* Try to close the final punctuation sign in this same line;
  511. * if we managed to close it outside of the URL, that means that it's
  512. * not part of the URL. If it closes inside the URL, that means it
  513. * is part of the URL.
  514. *
  515. * Examples:
  516. *
  517. * foo http://www.pokemon.com/Pikachu_(Electric) bar
  518. * => http://www.pokemon.com/Pikachu_(Electric)
  519. *
  520. * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
  521. * => http://www.pokemon.com/Pikachu_(Electric)
  522. *
  523. * foo http://www.pokemon.com/Pikachu_(Electric)) bar
  524. * => http://www.pokemon.com/Pikachu_(Electric))
  525. *
  526. * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
  527. * => foo http://www.pokemon.com/Pikachu_(Electric)
  528. */
  529. while (j < link_end) {
  530. if (data[j] == copen)
  531. opening++;
  532. else if (data[j] == cclose)
  533. closing++;
  534. j++;
  535. }
  536. if (closing != opening)
  537. link_end--;
  538. }
  539. return link_end;
  540. }
  541. function check_domain(data, allow_short) {
  542. var i, np = 0;
  543. if (!isalnum(data[0]))
  544. return 0;
  545. for (i = 1; i < data.length - 1; ++i) {
  546. if (data[i] == '.')
  547. np++;
  548. else if (!isalnum(data[i]) && data[i] != '-')
  549. break;
  550. }
  551. /* a valid domain needs to have at least a dot.
  552. * that's as far as we get */
  553. if (allow_short) {
  554. /* We don't need a valid domain in the strict sence (with
  555. * at least one dot; so just make sure it's composed of valid
  556. * domain characters and return the length of the valid
  557. * sequence. */
  558. return i;
  559. }
  560. else {
  561. return np ? i : 0;
  562. }
  563. }
  564. function sd_autolink_issafe(link) {
  565. var valid_uris = [
  566. "http://", "https://", "ftp://", "mailto://",
  567. "/", "git://", "steam://", "irc://", "news://", "mumble://",
  568. "ssh://", "ircs://", "ts3server://", "#"
  569. ];
  570. var i;
  571. for (i = 0; i < valid_uris.length; ++i) {
  572. var len = valid_uris[i].length;
  573. if (link.length > len &&
  574. link.toLowerCase().indexOf(valid_uris[i]) == 0 &&
  575. /[A-Za-z0-9#\/?]/.test(link[len]))
  576. return 1;
  577. }
  578. return 0;
  579. }
  580. function sd_autolink__url(rewind_p, link, data_, offset, size, flags) {
  581. var data = data_.slice(offset);
  582. var link_end, rewind = 0, domain_len;
  583. if (size < 4 || data_[offset + 1] != '/' || data_[offset + 2] != '/')
  584. return 0;
  585. while (rewind < offset && isalpha(data_[offset - rewind - 1]))
  586. rewind++;
  587. if (!sd_autolink_issafe(data_.substr(offset - rewind, size + rewind)))
  588. return 0;
  589. link_end = "://".length;
  590. domain_len = check_domain(data.slice(link_end), flags & SD_AUTOLINK_SHORT_DOMAINS);
  591. if (domain_len == 0)
  592. return 0;
  593. link_end += domain_len;
  594. while (link_end < size && !isspace(data_[offset + link_end]))
  595. link_end++;
  596. link_end = autolink_delim(data_, link_end, offset, size);
  597. if (link_end == 0)
  598. return 0;
  599. //TODO
  600. link.s += data_.substr(offset - rewind, link_end + rewind);
  601. rewind_p.p = rewind;
  602. return link_end;
  603. }
  604. function sd_autolink__subreddit(rewind_p, link, data_, offset, size) {
  605. var data = data_.slice(offset);
  606. var link_end;
  607. var allMinus = false;
  608. if (size < 3)
  609. return 0;
  610. /* make sure this / is part of /r/ */
  611. if (data.indexOf('/r/') != 0)
  612. return 0;
  613. link_end = "/r/".length;
  614. if (data.substr(link_end - 1, 4).toLowerCase() == "all-") {
  615. allMinus = true;
  616. }
  617. do {
  618. var start = link_end;
  619. var max_length = 24;
  620. /* special case: /r/reddit.com (the only subreddit with a '.') */
  621. if (size >= link_end + 10 && data.substr(link_end, 10).toLowerCase() == 'reddit.com') {
  622. link_end += 10;
  623. max_length = 10;
  624. }
  625. else {
  626. /* If not the special case make sure it starts with (t:)?[A-Za-z0-9] */
  627. /* support autolinking to timereddits, /r/t:when (1 April 2012) */
  628. if (size > link_end + 2 && data.substr(link_end, 2) == 't:')
  629. link_end += 2; /* Jump over the 't:' */
  630. /* the first character of a subreddit name must be a letter or digit */
  631. if (!isalnum(data[link_end]))
  632. return 0;
  633. link_end += 1;
  634. }
  635. /* consume valid characters ([A-Za-z0-9_]) until we run out */
  636. while (link_end < size && (isalnum(data[link_end]) ||
  637. data[link_end] == '_'))
  638. link_end++;
  639. /* valid subreddit names are between 3 and 21 characters, with
  640. * some subreddits having 2-character names. Don't bother with
  641. * autolinking for anything outside this length range.
  642. * (chksrname function in reddit/.../validator.py) */
  643. if (link_end - start < 2 || link_end - start > max_length)
  644. return 0;
  645. } while (link_end < size && (data[link_end] == '+' || (allMinus && data[link_end] == '-')) && link_end++);
  646. if (link_end < size && data[link_end] == '/') {
  647. while (link_end < size && (isalnum(data[link_end]) ||
  648. data[link_end] == '_' ||
  649. data[link_end] == '/' ||
  650. data[link_end] == '-')) {
  651. link_end++;
  652. }
  653. }
  654. /* make the link */
  655. link.s += data.slice(0, link_end);
  656. rewind_p.p = 0;
  657. return link_end;
  658. }
  659. function sd_autolink__username(rewind_p, link, data_, offset, size) {
  660. var data = data_.slice(offset);
  661. var link_end;
  662. if (size < 6)
  663. return 0;
  664. /* make sure this / is part of /u/ */
  665. if (data.indexOf('/u/') != 0)
  666. return 0;
  667. /* the first letter of a username must... well, be valid, we don't care otherwise */
  668. link_end = "/u/".length;
  669. if (!isalnum(data[link_end]) && data[link_end] != '_' && data[link_end] != '-')
  670. return 0;
  671. link_end += 1;
  672. /* consume valid characters ([A-Za-z0-9_-/]) until we run out */
  673. while (link_end < size && (isalnum(data[link_end]) ||
  674. data[link_end] == '_' ||
  675. data[link_end] == '/' ||
  676. data[link_end] == '-'))
  677. link_end++;
  678. /* make the link */
  679. link.s += data.slice(0, link_end);
  680. rewind_p.p = 0;
  681. return link_end;
  682. }
  683. function sd_autolink__email(rewind_p, link, data_, offset, size, flags) {
  684. var data = data_.slice(offset);
  685. var link_end, rewind;
  686. var nb = 0, np = 0;
  687. for (rewind = 0; rewind < offset; ++rewind) {
  688. var c = data_[offset - rewind - 1];
  689. if (c == '\0')
  690. break;
  691. if (isalnum(c))
  692. continue;
  693. if (".+-_".indexOf(c) != -1)
  694. continue;
  695. break;
  696. }
  697. if (rewind == 0)
  698. return 0;
  699. for (link_end = 0; link_end < size; ++link_end) {
  700. var c = data_[offset + link_end];
  701. if (isalnum(c))
  702. continue;
  703. if (c == '@')
  704. nb++;
  705. else if (c == '.' && link_end < size - 1)
  706. np++;
  707. else if (c != '-' && c != '_')
  708. break;
  709. }
  710. if (link_end < 2 || nb != 1 || np == 0)
  711. return 0;
  712. //TODO
  713. link_end = autolink_delim(data_, link_end, offset, size);
  714. if (link_end == 0)
  715. return 0;
  716. // link.s += data_.slice(offset - rewind, link_end + rewind
  717. link.s += data_.substr(offset - rewind, link_end + rewind);
  718. rewind_p.p = rewind;
  719. return link_end;
  720. }
  721. function sd_autolink__www(rewind_p, link, data_, offset, size, flags) {
  722. var data = data_.slice(offset);
  723. var link_end;
  724. if (offset > 0 && !ispunct(data_[offset - 1]) && !isspace(data_[offset - 1]))
  725. return 0;
  726. // if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
  727. if (size < 4 || (data.slice(0, 4) != 'www.'))
  728. return 0;
  729. link_end = check_domain(data, 0);
  730. if (link_end == 0)
  731. return 0;
  732. while (link_end < size && !isspace(data[link_end]))
  733. link_end++;
  734. link_end = autolink_delim(data_, link_end, offset, size);
  735. if (link_end == 0)
  736. return 0;
  737. link.s += data.slice(0, link_end);
  738. rewind_p.p = 0;
  739. return link_end;
  740. }
  741. /**
  742. Initialize a Callbacks object.
  743. @constructor
  744. @param {Object.<string, ?function>} callbacks A set of callbacks to use as the methods on this object.
  745. */
  746. function Callbacks(callbacks) {
  747. if (callbacks) {
  748. for (var name in callbacks) {
  749. if (name in this)
  750. this[name] = callbacks[name];
  751. }
  752. }
  753. }
  754. Callbacks.prototype = {
  755. /**
  756. Renders a code block.
  757. Syntax highlighting specific to lanugage may be performed here.
  758. @method
  759. @param {Buffer} out The output string buffer to append to.
  760. @param {Buffer} text The input text.
  761. @param {Buffer} language The name of the code langage.
  762. @param {?Object} context A renderer specific context object.
  763. */
  764. blockcode: null,
  765. /**
  766. Renders a blockquote.
  767. @method
  768. @param {Buffer} out The output string buffer to append to.
  769. @param {Buffer} text The input text.
  770. @param {?Object} context A renderer specific context object.
  771. */
  772. blockquote: null,
  773. /**
  774. Renders a block of HTML code.
  775. @method
  776. @param {Buffer} out The output string buffer to append to.
  777. @param {Buffer} text The input text.
  778. @param {?Object} context A renderer specific context object.
  779. */
  780. blockhtml: null,
  781. /**
  782. Renders a header.
  783. @method
  784. @param {Buffer} out The output string buffer to append to.
  785. @param {Buffer} text The input text.
  786. @param {Number} level The header level.
  787. @param {?Object} context A renderer specific context object.
  788. */
  789. header: null,
  790. /**
  791. Renders a horizontal rule.
  792. @method
  793. @param {Buffer} out The output string buffer to append to.
  794. @param {?Object} context A renderer specific context object.
  795. */
  796. hrule: null,
  797. /**
  798. Renders a list.
  799. <p>
  800. This method handles the list wrapper, which in terms of HTML would be &lt;ol&gt; or &lt;ul&gt;.
  801. This method is not responsible for handling list elements, all such processing should
  802. already have occured on text pased to the method . All that it is intended
  803. to do is to wrap the text parameter in anything needed.
  804. </p>
  805. @example
  806. out.s += "&lt;ul&gt;" + text.s + "&lt;/ul&gt;"
  807. @method
  808. @param {Buffer} out The output string buffer to append to.
  809. @param {Buffer} text The input that goes inside the list.
  810. @param {Number} flags A bitfield holding a portion of the render state. The only bit that this should be concerned with is MKD_LIST_ORDERED
  811. @param {?Object} context A renderer specific context object.
  812. */
  813. list: null,
  814. /**
  815. Renders a list.
  816. <p>
  817. Wraps the text in a list element.
  818. </p>
  819. @example
  820. out.s += "&lt;li&gt;" + text.s + "&lt;/li&gt;"
  821. @method
  822. @param {Buffer} out The output string buffer to append to.
  823. @param {Buffer} text The contents of the list element.
  824. @param {Number} flags A bitfield holding a portion of the render state. The only bit that this should be concerned with is MKD_LI_BLOCK.
  825. @param {?Object} context A renderer specific context object.
  826. */
  827. listitem: null,
  828. /**
  829. Renders a paragraph.
  830. @example
  831. out.s += "&lt;p&gt;" + text.s + "&lt;/p&gt;";
  832. @method
  833. @param {Buffer} out The output string buffer to append to.
  834. @param {Buffer} text The input text.
  835. @param {?Object} context A renderer specific context object.
  836. */
  837. paragraph: null,
  838. /**
  839. Renders a table.
  840. @example
  841. out.s += "<table><thead>";
  842. out.s += header.s;
  843. out.s += "</thead><tbody>";
  844. out.s += body.s;
  845. out.s += "</tbody></table>";
  846. @method
  847. @param {Buffer} out The output string buffer to append to.
  848. @param {Buffer} head The table header.
  849. @param {Buffer} body The table body.
  850. @param {?Object} context A renderer specific context object.
  851. */
  852. table: null,
  853. /**
  854. Renders a table row.
  855. @example
  856. out.s += "&lt;tr&gt;" + text.s + "&lt;/tr&gt;";
  857. @method
  858. @param {Buffer} out The output string buffer to append to.
  859. @param {Buffer} text The input text.
  860. @param {?Object} context A renderer specific context object.
  861. */
  862. table_row: null,
  863. /**
  864. Renders a table cell.
  865. @example
  866. out.s += "&lt;td&gt;" + text.s + "&lt;/td&gt;";
  867. @method
  868. @param {Buffer} out The output string buffer to append to.
  869. @param {Buffer} text The input text.
  870. @param {Number} flags A bit filed indicating a portion of the output state. Relevant bits are: MKD_TABLE_HEADER, MKD_TABLE_ALIGN_CENTER. MKD_TABLE_ALIGN_L, and MKD_TABLE_ALIGN_R.
  871. @param {?Object} context A renderer specific context object.
  872. */
  873. table_cell: null,
  874. /**
  875. Renders a link that was autodetected.
  876. @example
  877. out.s += "&lt;a href=\""+ text.s + "\"&gt;" + text.s + "&lt;/a&gt;";
  878. @method
  879. @param {Buffer} out The output string buffer to append to.
  880. @param {Buffer} text The address being linked to.
  881. @param {Number} type Equal to MKDA_NORMAL or MKDA_EMAIL
  882. @param {?Object} context A renderer specific context object.
  883. @returns {Boolean} Whether or not the tag was rendered.
  884. */
  885. autolink: null,
  886. /**
  887. Renders inline code.
  888. @method
  889. @param {Buffer} out The output string buffer to append to.
  890. @param {Buffer} text The text being wrapped.
  891. @param {?Object} context A renderer specific context object.
  892. @returns {Boolean} Whether or not the tag was rendered.
  893. */
  894. codespan: null,
  895. /**
  896. Renders text with double emphasis. Default is equivalent to the HTML &lt;strong&gt; tag.
  897. @method
  898. @param {Buffer} out The output string buffer to append to.
  899. @param {Buffer} text The text being wrapped.
  900. @param {?Object} context A renderer specific context object.
  901. @returns {Boolean} Whether or not the tag was rendered.
  902. */
  903. double_emphasis: null,
  904. /**
  905. Renders text with single emphasis. Default is equivalent to the HTML &lt;em&gt; tag.
  906. @method
  907. @param {Buffer} out The output string buffer to append to.
  908. @param {Buffer} text The text being wrapped.
  909. @param {?Object} context A renderer specific context object.
  910. @returns {Boolean} Whether or not the tag was rendered.
  911. */
  912. emphasis: null,
  913. /**
  914. Renders an image.
  915. @example
  916. out.s = "&lt;img src=\"" + link.s + "\" title=\"" + title.s + "\" alt=\"" + alt.s + "\"/&gt;";"
  917. @method
  918. @param {Buffer} out The output string buffer to append to.
  919. @param {Buffer} link The address of the image.
  920. @param {Buffer} title Title text for the image
  921. @param {Buffer} alt Alt text for the image
  922. @param {?Object} context A renderer specific context object.
  923. @returns {Boolean} Whether or not the tag was rendered.
  924. */
  925. image: null,
  926. /**
  927. Renders line break.
  928. @example
  929. out.s += "&lt;br/&gt;";
  930. @method
  931. @param {Buffer} out The output string buffer to append to.
  932. @param {?Object} context A renderer specific context object.
  933. @returns {Boolean} Whether or not the tag was rendered.
  934. */
  935. linebreak: null,
  936. /**
  937. Renders a link.
  938. @example
  939. out.s = "&lt;a href=\"" + link.s + "\" title=\"" + title.s + "\"&gt;" + content.s + "&lt;/a&gt;";
  940. @method
  941. @param {Buffer} out The output string buffer to append to.
  942. @param {Buffer} link The link address.
  943. @param {Buffer} title Title text for the link.
  944. @param {Buffer} content Link text.
  945. @param {?Object} context A renderer specific context object.
  946. @returns {Boolean} Whether or not the tag was rendered.
  947. */
  948. link: null,
  949. /**
  950. Copies and potentially escapes some HTML.
  951. @method
  952. @param {Buffer} out The output string buffer to append to.
  953. @param {Buffer} text The input text.
  954. @param {?Object} context A renderer specific context object.
  955. @returns {Boolean} Whether or not the tag was rendered.
  956. */
  957. raw_html_tag: null,
  958. /**
  959. Renders text with triple emphasis. Default is equivalent to both the &lt;em&gt; and &lt;strong&gt; HTML tags.
  960. @method
  961. @param {Buffer} out The output string buffer to append to.
  962. @param {Buffer} text The text being wrapped.
  963. @param {?Object} context A renderer specific context object.
  964. @returns {Boolean} Whether or not the tag was rendered.
  965. */
  966. triple_emphasis: null,
  967. /**
  968. Renders text crossd out.
  969. @method
  970. @param {Buffer} out The output string buffer to append to.
  971. @param {Buffer} text The text being wrapped.
  972. @param {?Object} context A renderer specific context object.
  973. @returns {Boolean} Whether or not the tag was rendered.
  974. */
  975. strikethrough: null,
  976. /**
  977. Renders text as superscript.
  978. @method
  979. @param {Buffer} out The output string buffer to append to.
  980. @param {Buffer} text The text being wrapped.
  981. @param {?Object} context A renderer specific context object.
  982. @returns {Boolean} Whether or not the tag was rendered.
  983. */
  984. superscript: null,
  985. /**
  986. Escapes an HTML entity.
  987. @method
  988. @param {Buffer} out The output string buffer to append to.
  989. @param {Buffer} text The text being wrapped.
  990. @param {?Object} context A renderer specific context object.
  991. */
  992. entity: null,
  993. /**
  994. Renders plain text.
  995. @method
  996. @param {Buffer} out The output string buffer to append to.
  997. @param {Buffer} text The text being rendered.
  998. @param {?Object} context A renderer specific context object.
  999. */
  1000. normal_text: null,
  1001. /**
  1002. Creates opening boilerplate for a table of contents.
  1003. @method
  1004. @param {Buffer} out The output string buffer to append to.
  1005. @param {?Object} context A renderer specific context object.
  1006. */
  1007. doc_header: null,
  1008. /**
  1009. Creates closing boilerplate for a table of contents.
  1010. @method
  1011. @param {Buffer} out The output string buffer to append to.
  1012. @param {?Object} context A renderer specific context object.
  1013. */
  1014. doc_footer: null
  1015. };
  1016. /**
  1017. A renderer object
  1018. @constructor
  1019. @param {Callbacks} callbacks The callbacks object to use for the renderer.
  1020. @param {?Callbacks} context Renderer specific context information.
  1021. */
  1022. function Renderer(callbacks, context) {
  1023. this.callbacks = callbacks;
  1024. this.context = context;
  1025. }
  1026. /**
  1027. Instantiates a custom Renderer object.
  1028. @param {Callbacks} callbacks The callbacks object to use for the renderer.
  1029. @param {?Callbacks} context Renderer specific context information.
  1030. @returns {Renderer}
  1031. */
  1032. function createCustomRenderer(callbacks, context) {
  1033. return new Renderer(callbacks, context);
  1034. }
  1035. exports.createCustomRenderer = createCustomRenderer;
  1036. function defaultRenderState() {
  1037. return {
  1038. nofollow: /*0*/ 1,
  1039. target: null,
  1040. tocData: {
  1041. headerCount: 0,
  1042. currentLevel: 0,
  1043. levelOffset: 0
  1044. },
  1045. toc_id_prefix: null,
  1046. html_element_whitelist: DEFAULT_HTML_ELEMENT_WHITELIST,
  1047. html_attr_whitelist: DEFAULT_HTML_ATTR_WHITELIST,
  1048. flags: 0,
  1049. //(flags != undefined?flags:HTML_SKIP_HTML | HTML_SKIP_IMAGES | HTML_SAFELINK | HTML_ESCAPE | HTML_USE_XHTML),
  1050. /* extra callbacks */
  1051. // void (*link_attributes)(struct buf *ob, const struct buf *url, void *self);
  1052. link_attributes: function link_attributes(out, url, options) {
  1053. if (options.nofollow)
  1054. out.s += ' rel="nofollow"';
  1055. if (options.target != null) {
  1056. out.s += ' target="' + options.target + '"';
  1057. }
  1058. }
  1059. };
  1060. }
  1061. exports.defaultRenderState = defaultRenderState;
  1062. /**
  1063. Produces a renderer object that will match Reddit's output.
  1064. @param {?Number=} flags A bitfield containing flags specific to the reddit HTML renderer. Passing undefined, null, or null value will produce reddit exact output.
  1065. @returns {Renderer} A renderer object that will match Reddit's output.
  1066. */
  1067. function getRedditRenderer(flags) {
  1068. if (flags === void 0) { flags = null; }
  1069. var state = defaultRenderState();
  1070. if (flags == null) {
  1071. state.flags = DEFAULT_BODY_FLAGS;
  1072. }
  1073. else {
  1074. state.flags = flags;
  1075. }
  1076. var renderer = new Renderer(getRedditCallbacks(), state);
  1077. if (renderer.context.flags & HTML_SKIP_IMAGES)
  1078. renderer.callbacks.image = null;
  1079. if (renderer.context.flags & HTML_SKIP_LINKS) {
  1080. renderer.callbacks.link = null;
  1081. renderer.callbacks.autolink = null;
  1082. }
  1083. if (renderer.context.flags & HTML_SKIP_HTML || renderer.context.flags & HTML_ESCAPE)
  1084. renderer.callbacks.blockhtml = null;
  1085. return renderer;
  1086. }
  1087. exports.getRedditRenderer = getRedditRenderer;
  1088. /**
  1089. Produces a renderer object that will match Reddit's for a table of contents.
  1090. @returns {Renderer} A renderer object that will match Reddit's output.
  1091. */
  1092. function getTocRenderer() {
  1093. var state = defaultRenderState();
  1094. state.flags = HTML_TOC | HTML_SKIP_HTML;
  1095. var renderer = new Renderer(getTocCallbacks(), state);
  1096. return renderer;
  1097. }
  1098. exports.getTocRenderer = getTocRenderer;
  1099. /**
  1100. Create a Callbacks object with the given callback table.
  1101. @param {Object.<string, function>} callbacks A table of callbacks to place int a callbacks object.
  1102. @returns {Callbacks} A callbacks object holding the provided callbacks.
  1103. */
  1104. function createCustomCallbacks(callbacks) {
  1105. return new Callbacks(callbacks);
  1106. }
  1107. exports.createCustomCallbacks = createCustomCallbacks;
  1108. /**
  1109. Produce a callbacks object that matches Reddit's output.
  1110. @returns {Callbacks} A callbacks object that matches Reddit's output.
  1111. */
  1112. function getRedditCallbacks() {
  1113. return new Callbacks({
  1114. blockcode: cb_blockcode,
  1115. blockquote: cb_blockquote,
  1116. blockhtml: cb_blockhtml,
  1117. header: cb_header,
  1118. hrule: cb_hrule,
  1119. list: cb_list,
  1120. listitem: cb_listitem,
  1121. paragraph: cb_paragraph,
  1122. table: cb_table,
  1123. table_row: cb_table_row,
  1124. table_cell: cb_table_cell,
  1125. autolink: cb_autolink,
  1126. codespan: cb_codespan,
  1127. double_emphasis: cb_double_emphasis,
  1128. emphasis: cb_emphasis,
  1129. image: cb_image,
  1130. linebreak: cb_linebreak,
  1131. link: cb_link,
  1132. raw_html_tag: cb_raw_html_tag,
  1133. triple_emphasis: cb_triple_emphasis,
  1134. strikethrough: cb_strikethrough,
  1135. superscript: cb_superscript,
  1136. entity: null,
  1137. normal_text: cb_normal_text,
  1138. doc_header: null,
  1139. doc_footer: cb_reset_toc
  1140. });
  1141. }
  1142. exports.getRedditCallbacks = getRedditCallbacks;
  1143. /**
  1144. Produce a callbacks object for rendering a table of contents.
  1145. @returns {Callbacks} A callbacks object for rendering a table of contents.
  1146. */
  1147. function getTocCallbacks() {
  1148. return new Callbacks({
  1149. blockcode: null,
  1150. blockquote: null,
  1151. blockhtml: null,
  1152. header: cb_toc_header,
  1153. hrule: null,
  1154. list: null,
  1155. listitem: null,
  1156. paragraph: null,
  1157. table: null,
  1158. table_row: null,
  1159. table_cell: null,
  1160. autolink: null,
  1161. codespan: cb_codespan,
  1162. double_emphasis: cb_double_emphasis,
  1163. emphasis: cb_emphasis,
  1164. image: null,
  1165. linebreak: null,
  1166. link: cb_toc_link,
  1167. raw_html_tag: null,
  1168. triple_emphasis: cb_triple_emphasis,
  1169. strikethrough: cb_strikethrough,
  1170. superscript: cb_superscript,
  1171. entity: null,
  1172. normal_text: null,
  1173. doc_header: null,
  1174. doc_footer: cb_toc_finalize
  1175. });
  1176. }
  1177. exports.getTocCallbacks = getTocCallbacks;
  1178. /* block level callbacks - NULL skips the block */
  1179. // void (*blockcode)(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque);
  1180. function cb_blockcode(out, text, lang, options) {
  1181. if (out.s.length)
  1182. out.s += '\n';
  1183. if (lang && lang.s.length) {
  1184. var i, cls;
  1185. out.s += '<pre><code class="';
  1186. for (i = 0, cls = 0; i < lang.s.length; ++i, ++cls) {
  1187. while (i < lang.s.length && isspace(lang.s[i]))
  1188. i++;
  1189. if (i < lang.s.length) {
  1190. var org = i;
  1191. while (i < lang.s.length && !isspace(lang.s[i]))
  1192. i++;
  1193. if (lang.s[org] == '.')
  1194. org++;
  1195. if (cls)
  1196. out.s += ' ';
  1197. escape_html(out, lang.s.slice(org, i), false);
  1198. }
  1199. }
  1200. out.s += '">';
  1201. }
  1202. else
  1203. out.s += '<pre><code>';
  1204. if (text)
  1205. escape_html(out, text.s, false);
  1206. out.s += '</code></pre>\n';
  1207. }
  1208. // void (*blockquote)(struct buf *ob, const struct buf *text, void *opaque);
  1209. function cb_blockquote(out, text, options) {
  1210. if (out.s.length)
  1211. out.s += '\n';
  1212. out.s += '<blockquote>\n';
  1213. if (text)
  1214. out.s += text.s;
  1215. out.s += '</blockquote>\n';
  1216. }
  1217. // void (*blockhtml)(struct buf *ob,const struct buf *text, void *opaque);
  1218. function cb_blockhtml(out, text, options) {
  1219. var org, sz;
  1220. if (!text)
  1221. return;
  1222. sz = text.s.length;
  1223. while (sz > 0 && text.s[sz - 1] == '\n')
  1224. sz--;
  1225. org = 0;
  1226. while (org < sz && text.s[org] == '\n')
  1227. org++;
  1228. if (org >= sz)
  1229. return;
  1230. if (out.s.length)
  1231. out.s += '\n';
  1232. out.s += text.s.slice(org, sz);
  1233. out.s += '\n';
  1234. }
  1235. // header(Buffer out, Buffer text, int level, void *opaque);
  1236. function cb_header(out, text, level, options) {
  1237. if (out.s.length)
  1238. out.s += '\n';
  1239. if (options.flags & HTML_TOC) {
  1240. out.s += '<h' + (+level) + ' id="';
  1241. if (options.toc_id_prefix)
  1242. out.s += options.toc_id_prefix;
  1243. out.s += 'toc_' + (options.tocData.headerCount++) + '">';
  1244. }
  1245. else {
  1246. out.s += '<h' + (+level) + '>';
  1247. }
  1248. if (text)
  1249. out.s += text.s;
  1250. out.s += '</h' + (+level) + '>\n';
  1251. }
  1252. // void (*hrule)(struct buf *ob, void *opaque);
  1253. function cb_hrule(out, options) {
  1254. if (out.s.length)
  1255. out.s += '\n';
  1256. out.s += (options.flags & HTML_USE_XHTML) ? '<hr/>\n' : '<hr>\n';
  1257. }
  1258. // void (*list)(struct buf *ob, const struct buf *text, int flags, void *opaque);
  1259. function cb_list(out, text, flags, options) {
  1260. if (out.s.length)
  1261. out.s += '\n';
  1262. out.s += (flags & MKD_LIST_ORDERED ? '<ol>\n' : '<ul>\n');
  1263. if (text)
  1264. out.s += text.s;
  1265. out.s += (flags & MKD_LIST_ORDERED ? '</ol>\n' : '</ul>\n');
  1266. }
  1267. // void (*listitem)(struct buf *ob, const struct buf *text, int flags, void *opaque);
  1268. function cb_listitem(out, text, flags, options) {
  1269. out.s += '<li>';
  1270. if (text) {
  1271. var size = text.s.length;
  1272. while (size && text.s[size - 1] == '\n')
  1273. size--;
  1274. out.s += text.s.slice(0, size);
  1275. }
  1276. out.s += '</li>\n';
  1277. }
  1278. // void (*paragraph)(struct buf *ob, const struct buf *text, void *opaque);
  1279. function cb_paragraph(out, text, options) {
  1280. var i = 0;
  1281. if (out.s.length)
  1282. out.s += '\n';
  1283. if (!text || !text.s.length)
  1284. return;
  1285. while (i < text.s.length && isspace(text.s[i]))
  1286. i++;
  1287. if (i == text.s.length)
  1288. return;
  1289. out.s += '<p>';
  1290. if (options.flags & HTML_HARD_WRAP) {
  1291. var org;
  1292. while (i < text.s.length) {
  1293. org = i;
  1294. while (i < text.s.length && text.data[i] != '\n')
  1295. i++;
  1296. if (i > org)
  1297. out.s += text.s.slice(org, i);
  1298. /*
  1299. * do not insert a line break if this newline
  1300. * is the last character on the paragraph
  1301. */
  1302. if (i >= text.s.length - 1)
  1303. break;
  1304. cb_linebreak(out, options);
  1305. i++;
  1306. }
  1307. }
  1308. else {
  1309. out.s += text.s.slice(i);
  1310. }
  1311. out.s += '</p>\n';
  1312. }
  1313. // void (*table)(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque);
  1314. function cb_table(out, header, body, options) {
  1315. if (out.s.length)
  1316. out.s += '\n';
  1317. out.s += '<table><thead>\n';
  1318. if (header)
  1319. out.s += header.s;
  1320. out.s += '</thead><tbody>\n';
  1321. if (body)
  1322. out.s += body.s;
  1323. out.s += '</tbody></table>\n';
  1324. }
  1325. // void (*table_row)(struct buf *ob, const struct buf *text, void *opaque);
  1326. function cb_table_row(out, text, options) {
  1327. out.s += '<tr>\n';
  1328. if (text)
  1329. out.s += text.s;
  1330. out.s += '</tr>\n';
  1331. }
  1332. // void (*table_cell)(struct buf *ob, const struct buf *text, int flags, void *opaque);
  1333. function cb_table_cell(out, text, flags, options, col_span) {
  1334. if (flags & MKD_TABLE_HEADER) {
  1335. out.s += '<th';
  1336. }
  1337. else {
  1338. out.s += '<td';
  1339. }
  1340. if (col_span > 1) {
  1341. out.s += " colspan=\"" + col_span + "\" ";
  1342. }
  1343. switch (flags & MKD_TABLE_ALIGNMASK) {
  1344. case MKD_TABLE_ALIGN_CENTER:
  1345. out.s += ' align="center">';
  1346. break;
  1347. case MKD_TABLE_ALIGN_L:
  1348. out.s += ' align="left">';
  1349. break;
  1350. case MKD_TABLE_ALIGN_R:
  1351. out.s += ' align="right">';
  1352. break;
  1353. default:
  1354. out.s += '>';
  1355. }
  1356. if (text)
  1357. out.s += text.s;
  1358. if (flags & MKD_TABLE_HEADER) {
  1359. out.s += '</th>\n';
  1360. }
  1361. else {
  1362. out.s += '</td>\n';
  1363. }
  1364. }
  1365. /* span level callbacks - NULL or return 0 prints the span verbatim */
  1366. // int (*autolink)(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque);
  1367. function cb_autolink(out, link, type, options) {
  1368. var offset = 0;
  1369. if (!link || !link.s.length)
  1370. return 0;
  1371. if ((options.flags & HTML_SAFELINK) != 0 &&
  1372. !sd_autolink_issafe(link.s) && type != MKDA_EMAIL)
  1373. return 0;
  1374. out.s += '<a href="';
  1375. if (type == MKDA_EMAIL)
  1376. out.s += 'mailto:';
  1377. escape_href(out, link.s.slice(offset));
  1378. if (options.link_attributes) {
  1379. out.s += '"';
  1380. options.link_attributes(out, link, options);
  1381. out.s += '>';
  1382. }
  1383. else {
  1384. out.s += '">';
  1385. }
  1386. /*
  1387. * Pretty printing: if we get an email address as
  1388. * an actual URI, e.g. `mailto:foo@bar.com`, we don't
  1389. * want to print the `mailto:` prefix
  1390. */
  1391. if (link.s.indexOf('mailto:') == 0) {
  1392. escape_html(out, link.s.slice(7), false);
  1393. }
  1394. else {
  1395. escape_html(out, link.s, false);
  1396. }
  1397. out.s += '</a>';
  1398. return 1;
  1399. }
  1400. // int (*codespan)(struct buf *ob, const struct buf *text, void *opaque);
  1401. function cb_codespan(out, text, options) {
  1402. out.s += '<code>';
  1403. if (text)
  1404. escape_html(out, text.s, false);
  1405. out.s += '</code>';
  1406. return 1;
  1407. }
  1408. // int (*double_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
  1409. function cb_double_emphasis(out, text, options) {
  1410. if (!text || !text.s.length)
  1411. return 0;
  1412. out.s += '<strong>' + text.s + '</strong>';
  1413. return 1;
  1414. }
  1415. // int (*emphasis)(struct buf *ob, const struct buf *text, void *opaque);
  1416. function cb_emphasis(out, text, options) {
  1417. if (!text || !text.s.length)
  1418. return 0;
  1419. out.s += '<em>' + text.s + '</em>';
  1420. return 1;
  1421. }
  1422. // int (*image)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque);
  1423. function cb_image(out, link, title, alt, options) {
  1424. if (!link || !link.s.length)
  1425. return 0;
  1426. out.s += '<img src="';
  1427. escape_href(out, link.s);
  1428. out.s += '" alt="';
  1429. if (alt && alt.s.length)
  1430. escape_html(out, alt.s, false);
  1431. if (title && title.s.length) {
  1432. out.s += '" title="';
  1433. escape_html(out, title.s, false);
  1434. }
  1435. out.s += (options.flags & HTML_USE_XHTML ? '"/>' : '">');
  1436. return 1;
  1437. }
  1438. // int (*linebreak)(struct buf *ob, void *opaque);
  1439. function cb_linebreak(out, options) {
  1440. out.s += (options.flags & HTML_USE_XHTML ? '<br/>\n' : '<br>\n');
  1441. return 1;
  1442. }
  1443. // int (*link)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque);
  1444. function cb_link(out, link, title, content, options) {
  1445. if (link != null && (options.flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link.s))
  1446. return 0;
  1447. out.s += '<a href="';
  1448. if (link && link.s.length)
  1449. escape_href(out, link.s);
  1450. if (title && title.s.length) {
  1451. out.s += '" title="';
  1452. escape_html(out, title.s, false);
  1453. }
  1454. if (options.link_attributes) {
  1455. out.s += '"';
  1456. options.link_attributes(out, link, options);
  1457. out.s += '>';
  1458. }
  1459. else {
  1460. out.s += '">';
  1461. }
  1462. if (content && content.s.length)
  1463. out.s += content.s;
  1464. out.s += '</a>';
  1465. return 1;
  1466. }
  1467. // rndr_html_tag(struct buf *ob, const struct buf *text, void *opaque, char* tagname, char** whitelist, int tagtype)
  1468. //NOT A CALLBACK!
  1469. function rndr_html_tag(out, text, options, tagname, whitelist, tagtype) {
  1470. var i, x, z, in_str = 0, seen_equals = 0, done = 0, done_attr = 0, reset = 0;
  1471. var attr, value;
  1472. var c;
  1473. out.s += '<';
  1474. if (tagtype == HTML_TAG_CLOSE) {
  1475. out.s += "/" + tagname + ">";
  1476. return;
  1477. }
  1478. out.s += tagname;
  1479. var i = 1 + tagname.length;
  1480. attr = new Buffer;
  1481. value = new Buffer;
  1482. for (; i < text.s.length && !done; i++) {
  1483. c = text.s[i];
  1484. done = 0;
  1485. reset = 0;
  1486. done_attr = 0;
  1487. switch (c) {
  1488. case '>':
  1489. done = 1;
  1490. break;
  1491. case '\'':
  1492. case '"':
  1493. if (!seen_equals) {
  1494. reset = 1;
  1495. }
  1496. else if (!in_str) {
  1497. in_str = c;
  1498. }
  1499. else if (in_str == c) {
  1500. in_str = 0;
  1501. done_attr = 1;
  1502. }
  1503. else {
  1504. value.s += c;
  1505. }
  1506. break;
  1507. case ' ':
  1508. if (in_str) {
  1509. value.s += ' ';
  1510. }
  1511. else {
  1512. reset = 1;
  1513. }
  1514. break;
  1515. case '=':
  1516. if (seen_equals) {
  1517. reset = 1;
  1518. break;
  1519. }
  1520. seen_equals = 1;
  1521. break;
  1522. default:
  1523. if (seen_equals && in_str || !seen_equals) {
  1524. if (seen_equals) {
  1525. value.s += c;
  1526. }
  1527. else {
  1528. attr.s += c;
  1529. }
  1530. }
  1531. break;
  1532. }
  1533. if (done_attr) {
  1534. var valid = 0;
  1535. for (z = 0; whitelist[z]; z++) {
  1536. if (whitelist[z].length != attr.s.length) {
  1537. continue;
  1538. }
  1539. for (x = 0; x < attr.s.length; x++) {
  1540. if (whitelist[z][x].toLowerCase() != attr.s[x].toLowerCase()) {
  1541. break;
  1542. }
  1543. }
  1544. if (x == attr.s.length) {
  1545. valid = 1;
  1546. break;
  1547. }
  1548. }
  1549. if (valid && value.s.length && attr.s.length) {
  1550. // console.log("VALD", value.s.length, attr.s.length);
  1551. out.s += ' ';
  1552. escape_html(out, attr.s, false);
  1553. out.s += '="';
  1554. escape_html(out, value.s, false);
  1555. out.s += '"';
  1556. }
  1557. reset = 1;
  1558. }
  1559. if (reset) {
  1560. seen_equals = 0;
  1561. in_str = 0;
  1562. attr = new Buffer;
  1563. value = new Buffer;
  1564. }
  1565. }
  1566. out.s += '>';
  1567. }
  1568. // int (*raw_html_tag)(struct buf *ob, const struct buf *tag, void *opaque);
  1569. function cb_raw_html_tag(out, text, options) {
  1570. var whitelist = options.html_element_whitelist;
  1571. /* Items on the whitelist ignore all other flags and just output */
  1572. if (((options.flags & HTML_ALLOW_ELEMENT_WHITELIST) != 0) && whitelist) {
  1573. for (var i = 0; whitelist[i]; i++) {
  1574. var tagtype = sdhtml_is_tag(text.s, whitelist[i]);
  1575. if (tagtype != HTML_TAG_NONE) {
  1576. rndr_html_tag(out, text, options, whitelist[i], options.html_attr_whitelist, tagtype);
  1577. return 1;
  1578. }
  1579. }
  1580. }
  1581. /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
  1582. * It doens't see if there are any valid tags, just escape all of them. */
  1583. if ((options.flags & HTML_ESCAPE) != 0) {
  1584. escape_html(out, text.s, false);
  1585. return 1;
  1586. }
  1587. if ((options.flags & HTML_SKIP_HTML) != 0)
  1588. return 1;
  1589. if ((options.flags & HTML_SKIP_STYLE) != 0 &&
  1590. sdhtml_is_tag(text.s, "style"))
  1591. return 1;
  1592. if ((options.flags & HTML_SKIP_LINKS) != 0 &&
  1593. sdhtml_is_tag(text.s, "a"))
  1594. return 1;
  1595. if ((options.flags & HTML_SKIP_IMAGES) != 0 &&
  1596. sdhtml_is_tag(text.s, "img"))
  1597. return 1;
  1598. out.s += text.s;
  1599. return 1;
  1600. }
  1601. // int (*triple_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
  1602. function cb_triple_emphasis(out, text, options) {
  1603. if (!text || !text.s.length)
  1604. return 0;
  1605. out.s += '<strong><em>' + text.s + '</em></strong>';
  1606. return 1;
  1607. }
  1608. // int (*strikethrough)(struct buf *ob, const struct buf *text, void *opaque);
  1609. function cb_strikethrough(out, text, options) {
  1610. if (!text || !text.s.length)
  1611. return 0;
  1612. out.s += '<del>' + text.s + '</del>';
  1613. return 1;
  1614. }
  1615. // int (*superscript)(struct buf *ob, const struct buf *text, void *opaque);
  1616. function cb_superscript(out, text, options) {
  1617. if (!text || !text.s.length)
  1618. return 0;
  1619. out.s += '<sup>' + text.s + '</sup>';
  1620. return 1;
  1621. }
  1622. /* low level callbacks - NULL copies input directly into the output */
  1623. //do not use
  1624. // void (*entity)(struct buf *ob, const struct buf *entity, void *opaque);
  1625. // function cb_entity(out, entity, options) {}
  1626. // void (*normal_text)(struct buf *ob, const struct buf *text, void *opaque);
  1627. function cb_normal_text(out, text, options) {
  1628. if (text)
  1629. escape_html(out, text.s, false);
  1630. }
  1631. // toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
  1632. function cb_toc_header(out, text, level, options) {
  1633. /* set the level offset if this is the first header
  1634. * we're parsing for the document */
  1635. if (options.tocData.currentLevel == 0) {
  1636. out.s += '<div class="toc">\n';
  1637. options.tocData.levelOffset = level - 1;
  1638. }
  1639. level -= options.tocData.levelOffset;
  1640. if (level > options.tocData.currentLevel) {
  1641. while (level > options.tocData.currentLevel) {
  1642. out.s += '<ul>\n<li>\n';
  1643. options.tocData.currentLevel++;
  1644. }
  1645. }
  1646. else if (level < options.tocData.currentLevel) {
  1647. out.s += '</li>\n';
  1648. while (level < options.tocData.currentLevel) {
  1649. out.s += '</ul>\n</li>\n';
  1650. options.tocData.currentLevel--;
  1651. }
  1652. out.s += '<li>\n';
  1653. }
  1654. else {
  1655. out.s += '</li>\n<li>\n';
  1656. }
  1657. out.s += '<a href="#';
  1658. if (options.toc_id_prefix)
  1659. out.s += options.toc_id_prefix;
  1660. out.s += 'toc_' + (options.tocData.headerCount++) + '">';
  1661. if (text)
  1662. escape_html(out, text.s, false);
  1663. out.s += '</a>\n';
  1664. }
  1665. //toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
  1666. function cb_toc_link(out, link, title, content, options) {
  1667. if (content && content.s)
  1668. out.s += content.s;
  1669. return 1;
  1670. }
  1671. // reset_toc(struct buf *ob, void *opaque)
  1672. function cb_reset_toc(out, options) {
  1673. options.tocData = {
  1674. headerCount: 0,
  1675. currentLevel: 0,
  1676. levelOffset: 0
  1677. };
  1678. }
  1679. //toc_finalize(struct buf *ob, void *opaque)
  1680. function cb_toc_finalize(out, options) {
  1681. var hasToc = false;
  1682. while (options.tocData.currentLevel > 0) {
  1683. out.s += '</li>\n</ul>\n';
  1684. options.tocData.currentLevel--;
  1685. hasToc = true;
  1686. }
  1687. if (hasToc) {
  1688. out.s += '</div>\n';
  1689. }
  1690. cb_reset_toc(out, options);
  1691. }
  1692. /* header and footer */
  1693. // doc_header(Buffer out}, context);
  1694. // doc_header: null,
  1695. // doc_footer(Buffer out, context);
  1696. // doc_footer: null
  1697. /* char_emphasis β€’ single and double emphasis parsing */
  1698. //Buffer, md, str, int
  1699. function char_emphasis(out, md, data_, offset) {
  1700. var data = data_.slice(offset);
  1701. var size = data.length;
  1702. var c = data[0];
  1703. var ret;
  1704. if (size > 2 && data[1] != c) {
  1705. /* whitespace cannot follow an opening emphasis;
  1706. * strikethrough only takes two characters '~~' */
  1707. if (c == '~' || _isspace(data[1]) || (ret = parse_emph1(out, md, data, c)) == 0)
  1708. return 0;
  1709. return ret + 1;
  1710. }
  1711. if (data.length > 3 && data[1] == c && data[2] != c) {
  1712. if (_isspace(data[2]) || (ret = parse_emph2(out, md, data, c)) == 0)
  1713. return 0;
  1714. return ret + 2;
  1715. }
  1716. if (data.length > 4 && data[1] == c && data[2] == c && data[3] != c) {
  1717. if (c == '~' || _isspace(data[3]) || (ret = parse_emph3(out, md, data, c)) == 0)
  1718. return 0;
  1719. return ret + 3;
  1720. }
  1721. return 0;
  1722. }
  1723. /* char_codespan - '`' parsing a code span (assuming codespan != 0) */
  1724. function char_codespan(out, md, data_, offset) {
  1725. var data = data_.slice(offset);
  1726. var end, nb = 0, i, f_begin, f_end;
  1727. /* counting the number of backticks in the delimiter */
  1728. while (nb < data.length && data[nb] == '`')
  1729. nb++;
  1730. /* finding the next delimiter */
  1731. i = 0;
  1732. for (end = nb; end < data.length && i < nb; end++) {
  1733. if (data[end] == '`')
  1734. i++;
  1735. else
  1736. i = 0;
  1737. }
  1738. if (i < nb && end >= data.length)
  1739. return 0; /* no matching delimiter */
  1740. /* trimming outside whitespaces */
  1741. f_begin = nb;
  1742. while (f_begin < end && data[f_begin] == ' ')
  1743. f_begin++;
  1744. f_end = end - nb;
  1745. while (f_end > nb && data[f_end - 1] == ' ')
  1746. f_end--;
  1747. /* real code span */
  1748. if (f_begin < f_end) {
  1749. var work = new Buffer(data.slice(f_begin, f_end));
  1750. if (!md.callbacks.codespan(out, work, md.context))
  1751. end = 0;
  1752. }
  1753. else {
  1754. if (!md.callbacks.codespan(out, null, md.context))
  1755. end = 0;
  1756. }
  1757. return end;
  1758. }
  1759. /* char_linebreak - '\n' preceded by two spaces (assuming linebreak != 0) */
  1760. function char_linebreak(out, md, data_, offset) {
  1761. var data = data_.slice(offset);
  1762. if (offset < 2 || data_[offset - 1] != ' ' || data_[offset - 2] != ' ')
  1763. return 0;
  1764. /* removing the last space from ob and rendering */
  1765. var len = out.s.length;
  1766. while (len && out.s[len - 1] == ' ')
  1767. len--;
  1768. out.s = out.s.slice(0, len);
  1769. return md.callbacks.linebreak(out, md.context) ? 1 : 0;
  1770. }
  1771. /* char_link - '[': parsing a link or an image */
  1772. function char_link(out, md, data_, offset) {
  1773. var data = data_.slice(offset);
  1774. var is_img = (offset && data_[offset - 1] == '!'), level;
  1775. var i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;
  1776. //4 bufs
  1777. var content = null;
  1778. var link = null;
  1779. var title = null;
  1780. var u_link = null;
  1781. var org_work_size = md.spanStack.length;
  1782. var text_has_nl = 0, ret = 0;
  1783. var in_title = 0, qtype = 0;
  1784. function cleanup() {
  1785. md.spanStack.length = org_work_size;
  1786. return ret ? i : 0;
  1787. }
  1788. /* checking whether the correct renderer exists */
  1789. if ((is_img && !md.callbacks.image) || (!is_img && !md.callbacks.link))
  1790. return cleanup();
  1791. /* looking for the matching closing bracket */
  1792. for (level = 1; i < data.length; i++) {
  1793. if (data[i] == '\n')
  1794. text_has_nl = 1;
  1795. else if (data[i - 1] == '\\')
  1796. continue;
  1797. else if (data[i] == '[')
  1798. level++;
  1799. else if (data[i] == ']') {
  1800. level--;
  1801. if (level <= 0)
  1802. break;
  1803. }
  1804. }
  1805. if (i >= data.length)
  1806. return cleanup();
  1807. txt_e = i;
  1808. i++;
  1809. /* skip any amount of whitespace or newline */
  1810. /* (this is much more laxist than original markdown syntax) */
  1811. while (i < data.length && _isspace(data[i]))
  1812. i++;
  1813. /* inline style link */
  1814. if (i < data.length && data[i] == '(') {
  1815. /* skipping initial whitespace */
  1816. i++;
  1817. while (i < data.length && _isspace(data[i]))
  1818. i++;
  1819. link_b = i;
  1820. /* looking for link end: ' " ) */
  1821. while (i < data.length) {
  1822. if (data[i] == '\\')
  1823. i += 2;
  1824. else if (data[i] == ')')
  1825. break;
  1826. else if (i >= 1 && _isspace(data[i - 1]) && (data[i] == '\'' || data[i] == '"'))
  1827. break;
  1828. else
  1829. i++;
  1830. }
  1831. if (i >= data.length)
  1832. return cleanup();
  1833. link_e = i;
  1834. /* looking for title end if present */
  1835. if (data[i] == '\'' || data[i] == '"') {
  1836. qtype = data[i];
  1837. in_title = 1;
  1838. i++;
  1839. title_b = i;
  1840. while (i < data.length) {
  1841. if (data[i] == '\\')
  1842. i += 2;
  1843. else if (data[i] == qtype) {
  1844. in_title = 0;
  1845. i++;
  1846. }
  1847. else if ((data[i] == ')') && !in_title)
  1848. break;
  1849. else
  1850. i++;
  1851. }
  1852. if (i >= data.length)
  1853. return cleanup();
  1854. /* skipping whitespaces after title */
  1855. title_e = i - 1;
  1856. while (title_e > title_b && _isspace(data[title_e]))
  1857. title_e--;
  1858. /* checking for closing quote presence */
  1859. if (data[title_e] != '\'' && data[title_e] != '"') {
  1860. title_b = title_e = 0;
  1861. link_e = i;
  1862. }
  1863. }
  1864. /* remove whitespace at the end of the link */
  1865. while (link_e > link_b && _isspace(data[link_e - 1]))
  1866. link_e--;
  1867. /* remove optional angle brackets around the link */
  1868. if (data[link_b] == '<')
  1869. link_b++;
  1870. if (data[link_e - 1] == '>')
  1871. link_e--;
  1872. /* building escaped link and title */
  1873. if (link_e > link_b) {
  1874. link = new Buffer();
  1875. md.spanStack.push(link);
  1876. link.s += data.slice(link_b, link_e);
  1877. }
  1878. if (title_e > title_b) {
  1879. title = new Buffer();
  1880. md.spanStack.push(title);
  1881. title.s += data.slice(title_b, title_e);
  1882. }
  1883. i++;
  1884. }
  1885. else if (i < data.length && data[i] == '[') {
  1886. var id = new Buffer();
  1887. var lr = null;
  1888. /* looking for the id */
  1889. i++;
  1890. link_b = i;
  1891. while (i < data.length && data[i] != ']')
  1892. i++;
  1893. if (i >= data.length)
  1894. return cleanup();
  1895. link_e = i;
  1896. /* finding the link_ref */
  1897. if (link_b == link_e) {
  1898. if (text_has_nl) {
  1899. var b = new Buffer();
  1900. md.spanStack.push(b);
  1901. var j;
  1902. for (j = 1; j < txt_e; j++) {
  1903. if (data[j] != '\n')
  1904. b.s += data[j];
  1905. else if (data[j - 1] != ' ')
  1906. b.s += ' ';
  1907. }
  1908. id.s = b.s;
  1909. }
  1910. else {
  1911. id.s = data.slice(1);
  1912. }
  1913. }
  1914. else {
  1915. id.s = data.slice(link_b, link_e);
  1916. }
  1917. //TODO
  1918. lr = md.refs[id.s];
  1919. if (!lr)
  1920. return cleanup();
  1921. /* keeping link and title from link_ref */
  1922. link = lr.link;
  1923. title = lr.title;
  1924. i++;
  1925. }
  1926. else {
  1927. var id = new Buffer();
  1928. var lr = null;
  1929. /* crafting the id */
  1930. if (text_has_nl) {
  1931. var b = new Buffer();
  1932. md.spanStack.push(b);
  1933. var j;
  1934. for (j = 1; j < txt_e; j++) {
  1935. if (data[j] != '\n')
  1936. b.s += data[j];
  1937. else if (data[j - 1] != ' ')
  1938. b.s += ' ';
  1939. }
  1940. id.s = b.s;
  1941. }
  1942. else {
  1943. id.s = data.slice(1, txt_e);
  1944. }
  1945. /* finding the link_ref */
  1946. lr = md.refs[id.s];
  1947. if (!lr)
  1948. return cleanup();
  1949. /* keeping link and title from link_ref */
  1950. link = lr.link;
  1951. title = lr.title;
  1952. /* rewinding the whitespace */
  1953. i = txt_e + 1;
  1954. }
  1955. /* building content: img alt is escaped, link content is parsed */
  1956. if (txt_e > 1) {
  1957. content = new Buffer();
  1958. md.spanStack.push(content);
  1959. if (is_img) {
  1960. content.s += data.slice(1, txt_e);
  1961. }
  1962. else {
  1963. /* disable autolinking when parsing inline the
  1964. * content of a link */
  1965. md.inLinkBody = 1;
  1966. parse_inline(content, md, data.slice(1, txt_e));
  1967. md.inLinkBody = 0;
  1968. }
  1969. }
  1970. if (link) {
  1971. u_link = new Buffer();
  1972. md.spanStack.push(u_link);
  1973. unscape_text(u_link, link);
  1974. }
  1975. else {
  1976. return cleanup();
  1977. }
  1978. /* calling the relevant rendering function */
  1979. if (is_img) {
  1980. if (out.s.length && out.s[out.s.length - 1] == '!')
  1981. out.s = out.s.slice(0, -1);
  1982. ret = md.callbacks.image(out, u_link, title, content, md.context);
  1983. }
  1984. else {
  1985. ret = md.callbacks.link(out, u_link, title, content, md.context);
  1986. }
  1987. /* cleanup */
  1988. // cleanup:
  1989. // rndr->work_bufs[BUFFER_SPAN].size = (int)org_work_size;
  1990. // return ret ? i : 0;
  1991. return cleanup();
  1992. }
  1993. /* char_langle_tag - '<' when tags or autolinks are allowed */
  1994. function char_langle_tag(out, md, data_, offset) {
  1995. var data = data_.slice(offset);
  1996. var altype = { p: MKDA_NOT_AUTOLINK };
  1997. var end = tag_length(data, altype);
  1998. var work = new Buffer(data.slice(0, end));
  1999. var ret = 0;
  2000. if (end > 2) {
  2001. if (md.callbacks.autolink && altype.p != MKDA_NOT_AUTOLINK) {
  2002. var u_link = new Buffer();
  2003. md.spanStack.push(u_link);
  2004. work.s = data.substr(1, end - 2);
  2005. unscape_text(u_link, work);
  2006. ret = md.callbacks.autolink(out, u_link, altype.p, md.context);
  2007. md.spanStack.pop();
  2008. }
  2009. else if (md.callbacks.raw_html_tag)
  2010. ret = md.callbacks.raw_html_tag(out, work, md.context);
  2011. }
  2012. if (!ret)
  2013. return 0;
  2014. else
  2015. return end;
  2016. }
  2017. /* char_escape - '\\' backslash escape */
  2018. function char_escape(out, md, data_, offset) {
  2019. var data = data_.slice(offset);
  2020. var escape_chars = "\\`*_{}[]()#+-.!:|&<>/^~";
  2021. var work = new Buffer();
  2022. if (data.length > 1) {
  2023. if (escape_chars.indexOf(data[1]) == -1)
  2024. return 0;
  2025. if (md.callbacks.normal_text) {
  2026. work.s = data[1];
  2027. md.callbacks.normal_text(out, work, md.context);
  2028. }
  2029. else
  2030. out.s += data[1];
  2031. }
  2032. else if (data.length == 1) {
  2033. out.s += data[0];
  2034. }
  2035. return 2;
  2036. }
  2037. /* char_entity - '&' escaped when it doesn't belong to an entity */
  2038. //-/* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
  2039. function char_entity(out, md, data_, offset) {
  2040. var data = data_.slice(offset);
  2041. var end = 1, content_start, content_end;
  2042. var numeric = false, hex = false, entity_base, entity_val;
  2043. var work = new Buffer();
  2044. if (end < data.length && data[end] === '#') {
  2045. numeric = true;
  2046. end++;
  2047. }
  2048. if (end < data.length && numeric && data[end].toLowerCase() === 'x') {
  2049. hex = true;
  2050. end++;
  2051. }
  2052. content_start = end;
  2053. while (end < data.length) {
  2054. var c = data[end];
  2055. if (hex) {
  2056. if (!isxdigit(c))
  2057. break;
  2058. }
  2059. else if (numeric) {
  2060. if (!isdigit(c))
  2061. break;
  2062. }
  2063. else if (!isalnum(c)) {
  2064. break;
  2065. }
  2066. end++;
  2067. }
  2068. if (end > content_start && end < data.length && data[end] === ';') {
  2069. /* well formed entity */
  2070. end++;
  2071. }
  2072. else {
  2073. return 0; /* not an entity */
  2074. }
  2075. /* way too long to be a valid numeric entity */
  2076. if (numeric && content_end - content_start > MAX_NUM_ENTITY_LEN) {
  2077. return 0;
  2078. }
  2079. /* Validate the entity's contents */
  2080. if (numeric) {
  2081. if (hex)
  2082. entity_base = 16;
  2083. else
  2084. entity_base = 10;
  2085. entity_val = parseInt(data.slice(content_start), entity_base);
  2086. if (!is_valid_numeric_entity(entity_val))
  2087. return 0;
  2088. }
  2089. else {
  2090. if (VALID_ENTITIES.indexOf(data.slice(0, end)) === -1)
  2091. return 0;
  2092. }
  2093. if (md.callbacks.entity) {
  2094. work.s = data.slice(0, end);
  2095. md.callbacks.entity(out, work, md.context);
  2096. }
  2097. else
  2098. out.s += data.slice(0, end);
  2099. return end;
  2100. }
  2101. function char_autolink_url(out, md, data_, offset) {
  2102. var data = data_.slice(offset);
  2103. var link = null;
  2104. var link_len, rewind = { p: null };
  2105. if (!md.callbacks.autolink || md.inLinkBody)
  2106. return 0;
  2107. link = new Buffer();
  2108. md.spanStack.push(link);
  2109. if ((link_len = sd_autolink__url(rewind, link, data_, offset, data.length, 0)) > 0) {
  2110. if (rewind.p > 0)
  2111. out.truncate(out.s.length - rewind.p);
  2112. md.callbacks.autolink(out, link, MKDA_NORMAL, md.context);
  2113. }
  2114. md.spanStack.pop();
  2115. return link_len;
  2116. }
  2117. function char_autolink_email(out, md, data_, offset) {
  2118. var data = data_.slice(offset);
  2119. var link = null;
  2120. var link_len, rewind = { p: null };
  2121. if (!md.callbacks.autolink || md.inLinkBody)
  2122. return 0;
  2123. link = new Buffer();
  2124. md.spanStack.push(link);
  2125. if ((link_len = sd_autolink__email(rewind, link, data_, offset, data.length, 0)) > 0) {
  2126. if (rewind.p > 0)
  2127. out.truncate(out.s.length - rewind.p);
  2128. md.callbacks.autolink(out, link, MKDA_EMAIL, md.context);
  2129. }
  2130. md.spanStack.pop();
  2131. return link_len;
  2132. }
  2133. function char_autolink_www(out, md, data_, offset) {
  2134. var data = data_.slice(offset);
  2135. var link = null, link_url = null, link_text = null;
  2136. var link_len, rewind = { p: null };
  2137. if (!md.callbacks.link || md.inLinkBody)
  2138. return 0;
  2139. link = new Buffer();
  2140. md.spanStack.push(link);
  2141. if ((link_len = sd_autolink__www(rewind, link, data_, offset, data.length, 0)) > 0) {
  2142. link_url = new Buffer();
  2143. md.spanStack.push(link_url);
  2144. link_url.s += 'http://';
  2145. link_url.s += link.s;
  2146. if (rewind.p > 0)
  2147. out.truncate(out.s.length - rewind.p);
  2148. if (md.callbacks.normal_text) {
  2149. link_text = new Buffer();
  2150. md.spanStack.push(link_text);
  2151. md.callbacks.normal_text(link_text, link, md.context);
  2152. md.callbacks.link(out, link_url, null, link_text, md.context);
  2153. md.spanStack.pop();
  2154. }
  2155. else {
  2156. md.callbacks.link(out, link_url, null, link, md.context);
  2157. }
  2158. md.spanStack.pop();
  2159. }
  2160. md.spanStack.pop();
  2161. return link_len;
  2162. }
  2163. function char_autolink_subreddit_or_username(out, md, data_, offset) {
  2164. var data = data_.slice(offset);
  2165. var link = null;
  2166. var link_len, rewind = { p: null };
  2167. if (!md.callbacks.autolink || md.inLinkBody)
  2168. return 0;
  2169. link = new Buffer();
  2170. md.spanStack.push(link);
  2171. if ((link_len = sd_autolink__subreddit(rewind, link, data_, offset, data.length)) > 0) {
  2172. //don't slice because the rewind pointer will always be 0
  2173. if (rewind.p > 0)
  2174. out.truncate(out.s.length - rewind.p);
  2175. md.callbacks.autolink(out, link, MKDA_NORMAL, md.context);
  2176. }
  2177. else if ((link_len = sd_autolink__username(rewind, link, data_, offset, data.length)) > 0) {
  2178. //don't slice because the rewind pointer will always be 0
  2179. if (rewind.p > 0)
  2180. out.truncate(out.s.length - rewind.p);
  2181. md.callbacks.autolink(out, link, MKDA_NORMAL, md.context);
  2182. }
  2183. md.spanStack.pop();
  2184. return link_len;
  2185. }
  2186. function char_superscript(out, md, data_, offset) {
  2187. var data = data_.slice(offset);
  2188. var size = data.length;
  2189. var sup_start, sup_len;
  2190. var sup = null;
  2191. if (!md.callbacks.superscript)
  2192. return 0;
  2193. if (size < 2)
  2194. return 0;
  2195. if (data[1] == '(') {
  2196. sup_start = sup_len = 2;
  2197. while (sup_len < size && data[sup_len] != ')' && data[sup_len - 1] != '\\')
  2198. sup_len++;
  2199. if (sup_len == size)
  2200. return 0;
  2201. }
  2202. else {
  2203. sup_start = sup_len = 1;
  2204. while (sup_len < size && !_isspace(data[sup_len]))
  2205. sup_len++;
  2206. }
  2207. if (sup_len - sup_start == 0)
  2208. return (sup_start == 2) ? 3 : 0;
  2209. sup = new Buffer();
  2210. md.spanStack.push(sup);
  2211. parse_inline(sup, md, data.slice(sup_start, sup_len));
  2212. md.callbacks.superscript(out, sup, md.context);
  2213. md.spanStack.pop();
  2214. return (sup_start == 2) ? sup_len + 1 : sup_len;
  2215. }
  2216. var markdown_char_ptrs = [
  2217. null,
  2218. char_emphasis,
  2219. char_codespan,
  2220. char_linebreak,
  2221. char_link,
  2222. char_langle_tag,
  2223. char_escape,
  2224. char_entity,
  2225. char_autolink_url,
  2226. char_autolink_email,
  2227. char_autolink_www,
  2228. char_autolink_subreddit_or_username,
  2229. char_superscript
  2230. ];
  2231. var MKD_LIST_ORDERED = 1;
  2232. var MKD_LI_BLOCK = 2; /* <li> containing block data */
  2233. var MKD_LI_END = 8; /* internal list flag */
  2234. var enumCounter = 0;
  2235. var MD_CHAR_NONE = enumCounter++;
  2236. var MD_CHAR_EMPHASIS = enumCounter++;
  2237. var MD_CHAR_CODESPAN = enumCounter++;
  2238. var MD_CHAR_LINEBREAK = enumCounter++;
  2239. var MD_CHAR_LINK = enumCounter++;
  2240. var MD_CHAR_LANGLE = enumCounter++;
  2241. var MD_CHAR_ESCAPE = enumCounter++;
  2242. var MD_CHAR_ENTITITY = enumCounter++;
  2243. var MD_CHAR_AUTOLINK_URL = enumCounter++;
  2244. var MD_CHAR_AUTOLINK_EMAIL = enumCounter++;
  2245. var MD_CHAR_AUTOLINK_WWW = enumCounter++;
  2246. var MD_CHAR_AUTOLINK_SUBREDDIT_OR_USERNAME = enumCounter++;
  2247. var MD_CHAR_SUPERSCRIPT = enumCounter++;
  2248. var SD_AUTOLINK_SHORT_DOMAINS = (1 << 0);
  2249. enumCounter = 0;
  2250. var MKDA_NOT_AUTOLINK = enumCounter++; /* used internally when it is not an autolink*/
  2251. var MKDA_NORMAL = enumCounter++; /* normal http/http/ftp/mailto/etc link */
  2252. var MKDA_EMAIL = enumCounter++; /* e-mail link without explit mailto: */
  2253. var MKDEXT_NO_INTRA_EMPHASIS = (1 << 0);
  2254. var MKDEXT_TABLES = (1 << 1);
  2255. var MKDEXT_FENCED_CODE = (1 << 2);
  2256. var MKDEXT_AUTOLINK = (1 << 3);
  2257. var MKDEXT_STRIKETHROUGH = (1 << 4);
  2258. // var MKDEXT_LAX_HTML_BLOCKS = (1 << 5);
  2259. var MKDEXT_SPACE_HEADERS = (1 << 6);
  2260. var MKDEXT_SUPERSCRIPT = (1 << 7);
  2261. var MKDEXT_LAX_SPACING = (1 << 8);
  2262. var MKDEXT_NO_EMAIL_AUTOLINK = (1 << 9);
  2263. var HTML_SKIP_HTML = (1 << 0);
  2264. var HTML_SKIP_STYLE = (1 << 1);
  2265. var HTML_SKIP_IMAGES = (1 << 2);
  2266. var HTML_SKIP_LINKS = (1 << 3);
  2267. var HTML_EXPAND_TABS = (1 << 4);
  2268. var HTML_SAFELINK = (1 << 5);
  2269. var HTML_TOC = (1 << 6);
  2270. var HTML_HARD_WRAP = (1 << 7);
  2271. var HTML_USE_XHTML = (1 << 8);
  2272. var HTML_ESCAPE = (1 << 9);
  2273. var HTML_ALLOW_ELEMENT_WHITELIST = (1 << 10);
  2274. var MKD_TABLE_ALIGN_L = 1;
  2275. var MKD_TABLE_ALIGN_R = 2;
  2276. var MKD_TABLE_ALIGN_CENTER = 3;
  2277. var MKD_TABLE_ALIGNMASK = 3;
  2278. var MKD_TABLE_HEADER = 4;
  2279. var HTML_TAG_NONE = 0;
  2280. var HTML_TAG_OPEN = 1;
  2281. var HTML_TAG_CLOSE = 2;
  2282. /**
  2283. * A string buffer wrapper because JavaScript doesn't have mutable strings.
  2284. * @constructor
  2285. * @param {string=} str Optional string to initialize the Buffer with.
  2286. */
  2287. var Buffer = function (str) {
  2288. this.s = str || "";
  2289. };
  2290. /**
  2291. */
  2292. Buffer.prototype.truncate = function (size) {
  2293. if (this.s.length < size)
  2294. throw new RangeError("Buffer smaller than desired size");
  2295. if (size < 0)
  2296. throw new RangeError("Size argument is negative");
  2297. this.s = this.s.slice(0, size);
  2298. };
  2299. /**
  2300. * A Markdown parser object.
  2301. * @constructor
  2302. */
  2303. function Markdown() {
  2304. //Becase javascript strings are immutable they must be wrapped with Buffer()
  2305. this.spanStack = [];
  2306. this.blockStack = [];
  2307. this.extensions = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_SUPERSCRIPT | MKDEXT_AUTOLINK | MKDEXT_STRIKETHROUGH | MKDEXT_TABLES | MKDEXT_TABLES;
  2308. var renderer = getRedditRenderer();
  2309. this.context = renderer.context;
  2310. this.callbacks = renderer.callbacks;
  2311. this.inLinkBody = 0;
  2312. this.activeChars = {};
  2313. this.refs = {};
  2314. this.nestingLimit = 16;
  2315. this.maxTableCols = 64;
  2316. }
  2317. ;
  2318. /* is_empty - returns the line length when it is empty, 0 otherwise */
  2319. function is_empty(data) {
  2320. var i;
  2321. for (i = 0; i < data.length && data[i] != '\n'; i++)
  2322. if (data[i] != ' ')
  2323. return 0;
  2324. return i + 1;
  2325. }
  2326. /* is_hrule - returns whether a line is a horizontal rule */
  2327. function is_hrule(data) {
  2328. var i = 0, n = 0;
  2329. var c;
  2330. /* skipping initial spaces */
  2331. if (data.length < 3)
  2332. return 0;
  2333. if (data[0] == ' ') {
  2334. i++;
  2335. if (data[1] == ' ') {
  2336. i++;
  2337. if (data[2] == ' ') {
  2338. i++;
  2339. }
  2340. }
  2341. }
  2342. /* looking at the hrule uint8_t */
  2343. if (i + 2 >= data.length
  2344. || (data[i] != '*' && data[i] != '-' && data[i] != '_'))
  2345. return 0;
  2346. c = data[i];
  2347. /* the whole line must be the char or whitespace */
  2348. while (i < data.length && data[i] != '\n') {
  2349. if (data[i] == c)
  2350. n++;
  2351. else if (data[i] != ' ')
  2352. return 0;
  2353. i++;
  2354. }
  2355. return n >= 3;
  2356. }
  2357. /* check if a line begins with a code fence; return the
  2358. * width if it is */
  2359. function prefix_codefence(data) {
  2360. var i = 0, n = 0;
  2361. var c;
  2362. /* skipping initial spaces */
  2363. if (data.length < 3)
  2364. return 0;
  2365. if (data[0] == ' ') {
  2366. i++;
  2367. if (data[1] == ' ') {
  2368. i++;
  2369. if (data[2] == ' ') {
  2370. i++;
  2371. }
  2372. }
  2373. }
  2374. /* looking at the hrule uint8_t */
  2375. if (i + 2 >= data.length || !(data[i] == '~' || data[i] == '`'))
  2376. return 0;
  2377. c = data[i];
  2378. /* the whole line must be the uint8_t or whitespace */
  2379. while (i < data.length && data[i] == c) {
  2380. n++;
  2381. i++;
  2382. }
  2383. if (n < 3)
  2384. return 0;
  2385. return i;
  2386. }
  2387. /* check if a line is a code fence; return its size if it */
  2388. function is_codefence(data, syntax) {
  2389. var i = 0, syn_len = 0;
  2390. i = prefix_codefence(data);
  2391. if (i == 0)
  2392. return 0;
  2393. while (i < data.length && data[i] == ' ')
  2394. i++;
  2395. var syn_start;
  2396. //syn_start = data + i;
  2397. syn_start = i;
  2398. if (i < data.length && data[i] == '{') {
  2399. i++;
  2400. syn_start++;
  2401. while (i < data.length && data[i] != '}' && data[i] != '\n') {
  2402. syn_len++;
  2403. i++;
  2404. }
  2405. if (i == data.length || data[i] != '}')
  2406. return 0;
  2407. /* strip all whitespace at the beginning and the end
  2408. * of the {} block */
  2409. /*remember not to remove the +0, it helps me keep syncronised with snudown*/
  2410. while (syn_len > 0 && _isspace(data[syn_start + 0])) {
  2411. syn_start++;
  2412. syn_len--;
  2413. }
  2414. // while (syn_len > 0 && _isspace(syn_start[syn_len - 1]))
  2415. while (syn_len > 0 && _isspace(data[syn_start + syn_len - 1]))
  2416. syn_len--;
  2417. i++;
  2418. }
  2419. else {
  2420. while (i < data.length && !_isspace(data[i])) {
  2421. syn_len++;
  2422. i++;
  2423. }
  2424. }
  2425. if (syntax)
  2426. syntax.s = data.substr(syn_start, syn_len);
  2427. // syntax->size = syn;
  2428. while (i < data.length && data[i] != '\n') {
  2429. if (!_isspace(data[i]))
  2430. return 0;
  2431. i++;
  2432. }
  2433. return i + 1;
  2434. }
  2435. /* find_emph_char - looks for the next emph uint8_t, skipping other constructs */
  2436. function find_emph_char(data, c) {
  2437. var i = 1;
  2438. while (i < data.length) {
  2439. while (i < data.length && data[i] != c && data[i] != '`' && data[i] != '[')
  2440. i++;
  2441. if (i == data.length)
  2442. return 0;
  2443. if (data[i] == c)
  2444. return i;
  2445. /* not counting escaped chars */
  2446. if (i && data[i - 1] == '\\') {
  2447. i++;
  2448. continue;
  2449. }
  2450. if (data[i] == '`') {
  2451. var span_nb = 0, bt;
  2452. var tmp_i = 0;
  2453. /* counting the number of opening backticks */
  2454. while (i < data.length && data[i] == '`') {
  2455. i++;
  2456. span_nb++;
  2457. }
  2458. if (i >= data.length)
  2459. return 0;
  2460. /* finding the matching closing sequence */
  2461. bt = 0;
  2462. while (i < data.length && bt < span_nb) {
  2463. if (!tmp_i && data[i] == c)
  2464. tmp_i = i;
  2465. if (data[i] == '`')
  2466. bt++;
  2467. else
  2468. bt = 0;
  2469. i++;
  2470. }
  2471. if (i >= data.length)
  2472. return tmp_i;
  2473. }
  2474. else if (data[i] == '[') {
  2475. var tmp_i = 0;
  2476. var cc;
  2477. i++;
  2478. while (i < data.length && data[i] != ']') {
  2479. if (!tmp_i && data[i] == c)
  2480. tmp_i = i;
  2481. i++;
  2482. }
  2483. i++;
  2484. while (i < data.length && (data[i] == ' ' || data[i] == '\n'))
  2485. i++;
  2486. if (i >= data.length)
  2487. return tmp_i;
  2488. switch (data[i]) {
  2489. case '[':
  2490. cc = ']';
  2491. break;
  2492. case '(':
  2493. cc = ')';
  2494. break;
  2495. default:
  2496. if (tmp_i)
  2497. return tmp_i;
  2498. else
  2499. continue;
  2500. }
  2501. i++;
  2502. while (i < data.length && data[i] != cc) {
  2503. if (!tmp_i && data[i] == c)
  2504. tmp_i = i;
  2505. i++;
  2506. }
  2507. if (i >= data.length)
  2508. return tmp_i;
  2509. i++;
  2510. }
  2511. }
  2512. return 0;
  2513. }
  2514. /* parse_emph1 - parsing single emphase */
  2515. /* closed by a symbol not preceded by whitespace and not followed by symbol */
  2516. function parse_emph1(out, md, data_, c) {
  2517. var data = data_.slice(1);
  2518. var i = 0, len;
  2519. var r;
  2520. if (!md.callbacks.emphasis)
  2521. return 0;
  2522. /* skipping one symbol if coming from emph3 */
  2523. if (data.length > 1 && data[0] == c && data[1] == c)
  2524. i = 1;
  2525. while (i < data.length) {
  2526. len = find_emph_char(data.slice(i), c);
  2527. if (!len)
  2528. return 0;
  2529. i += len;
  2530. if (i >= data.length)
  2531. return 0;
  2532. if (data[i] == c && !_isspace(data[i - 1])) {
  2533. if ((md.extensions & MKDEXT_NO_INTRA_EMPHASIS) && (c == '_')) {
  2534. if (!(i + 1 == data.length || _isspace(data[i + 1]) || ispunct(data[i + 1])))
  2535. continue;
  2536. }
  2537. var work = new Buffer();
  2538. md.spanStack.push(work);
  2539. parse_inline(work, md, data.slice(0, i));
  2540. r = md.callbacks.emphasis(out, work, md.context);
  2541. md.spanStack.pop();
  2542. return r ? i + 1 : 0;
  2543. }
  2544. }
  2545. return 0;
  2546. }
  2547. /* parse_emph2 - parsing single emphase */
  2548. function parse_emph2(out, md, data_, c) {
  2549. var data = data_.slice(2);
  2550. var i = 0, len;
  2551. var r;
  2552. var render_method = (c == '~') ? md.callbacks.strikethrough : md.callbacks.double_emphasis;
  2553. if (!render_method)
  2554. return 0;
  2555. while (i < data.length) {
  2556. len = find_emph_char(data.slice(i), c);
  2557. if (!len)
  2558. return 0;
  2559. i += len;
  2560. if (i + 1 < data.length && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) {
  2561. var work = new Buffer();
  2562. md.spanStack.push(work);
  2563. parse_inline(work, md, data.slice(0, i));
  2564. r = render_method(out, work, md.context);
  2565. md.spanStack.pop();
  2566. return r ? i + 2 : 0;
  2567. }
  2568. i++;
  2569. }
  2570. return 0;
  2571. }
  2572. /* parse_emph3 β€’ parsing single emphase */
  2573. /* finds the first closing tag, and delegates to the other emph */
  2574. function parse_emph3(out, md, data_, c) {
  2575. var data = data_.slice(3);
  2576. var i = 0, len;
  2577. var r;
  2578. while (i < data.length) {
  2579. len = find_emph_char(data.slice(i), c);
  2580. if (!len)
  2581. return 0;
  2582. i += len;
  2583. /* skip whitespace preceded symbols */
  2584. if (data[i] != c || _isspace(data[i - 1]))
  2585. continue;
  2586. if (i + 2 < data.length && data[i + 1] == c && data[i + 2] == c && md.callbacks.triple_emphasis) {
  2587. /* triple symbol found */
  2588. var work = new Buffer();
  2589. md.spanStack.push(work);
  2590. parse_inline(work, md, data.slice(0, i));
  2591. r = md.callbacks.triple_emphasis(out, work, md.context);
  2592. md.spanStack.pop();
  2593. return r ? i + 3 : 0;
  2594. }
  2595. else if (i + 1 < data.length && data[i + 1] == c) {
  2596. /* double symbol found, handing over to emph1 */
  2597. len = parse_emph1(out, md, data_, c);
  2598. if (!len)
  2599. return 0;
  2600. else
  2601. return len - 2;
  2602. }
  2603. else {
  2604. /* single symbol found, handing over to emph2 */
  2605. len = parse_emph2(out, md, data_, c);
  2606. if (!len)
  2607. return 0;
  2608. else
  2609. return len - 1;
  2610. }
  2611. }
  2612. return 0;
  2613. }
  2614. function is_atxheader(md, data) {
  2615. if (data[0] != '#')
  2616. return false;
  2617. if (md.extensions & MKDEXT_SPACE_HEADERS) {
  2618. var level = 0;
  2619. while (level < data.length && level < 6 && data[level] == '#')
  2620. level++;
  2621. if (level < data.length && data[level] != ' ')
  2622. return false;
  2623. }
  2624. return true;
  2625. }
  2626. /* is_headerline . returns whether the line is a setext-style hdr underline */
  2627. function is_headerline(data) {
  2628. var i = 0;
  2629. var size = data.length;
  2630. /* test of level 1 header */
  2631. if (data[i] == '=') {
  2632. for (i = 1; i < size && data[i] == '='; i++) { }
  2633. while (i < size && data[i] == ' ')
  2634. i++;
  2635. return (i >= size || data[i] == '\n') ? 1 : 0;
  2636. }
  2637. /* test of level 2 header */
  2638. if (data[i] == '-') {
  2639. for (i = 1; i < size && data[i] == '-'; i++) { }
  2640. while (i < size && data[i] == ' ')
  2641. i++;
  2642. return (i >= size || data[i] == '\n') ? 2 : 0;
  2643. }
  2644. return 0;
  2645. }
  2646. function is_next_headerline(data) {
  2647. var size = data.length;
  2648. var i = 0;
  2649. while (i < size && data[i] != '\n')
  2650. i++;
  2651. if (++i >= size)
  2652. return 0;
  2653. return is_headerline(data.slice(i));
  2654. }
  2655. /* prefix_quote - returns blockquote prefix length */
  2656. function prefix_quote(data) {
  2657. var i = 0;
  2658. var size = data.length;
  2659. if (i < size && data[i] == ' ')
  2660. i++;
  2661. if (i < size && data[i] == ' ')
  2662. i++;
  2663. if (i < size && data[i] == ' ')
  2664. i++;
  2665. if (i < size && data[i] == '>') {
  2666. if (i + 1 < size && data[i + 1] == ' ')
  2667. return i + 2;
  2668. return i + 1;
  2669. }
  2670. return 0;
  2671. }
  2672. /* prefix_code β€’ returns prefix length for block code*/
  2673. function prefix_code(data) {
  2674. if (data.length > 3 && data[0] == ' ' && data[1] == ' '
  2675. && data[2] == ' ' && data[3] == ' ')
  2676. return 4;
  2677. return 0;
  2678. }
  2679. /* prefix_oli - returns ordered list item prefix */
  2680. function prefix_oli(data) {
  2681. var size = data.length;
  2682. var i = 0;
  2683. if (i < size && data[i] == ' ')
  2684. i++;
  2685. if (i < size && data[i] == ' ')
  2686. i++;
  2687. if (i < size && data[i] == ' ')
  2688. i++;
  2689. if (i >= size || data[i] < '0' || data[i] > '9')
  2690. return 0;
  2691. while (i < size && data[i] >= '0' && data[i] <= '9')
  2692. i++;
  2693. if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ')
  2694. return 0;
  2695. if (is_next_headerline(data.slice(i)))
  2696. return 0;
  2697. return i + 2;
  2698. }
  2699. /* prefix_uli - returns ordered list item prefix */
  2700. function prefix_uli(data) {
  2701. var size = data.length;
  2702. var i = 0;
  2703. if (i < size && data[i] == ' ')
  2704. i++;
  2705. if (i < size && data[i] == ' ')
  2706. i++;
  2707. if (i < size && data[i] == ' ')
  2708. i++;
  2709. if (i + 1 >= size ||
  2710. (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
  2711. data[i + 1] != ' ')
  2712. return 0;
  2713. if (is_next_headerline(data.slice(i)))
  2714. return 0;
  2715. return i + 2;
  2716. }
  2717. /* is_mail_autolink - looks for the address part of a mail autolink and '>' */
  2718. /* this is less strict than the original markdown e-mail address matching */
  2719. function is_mail_autolink(data) {
  2720. var i = 0, nb = 0;
  2721. /* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */
  2722. for (i = 0; i < data.length; ++i) {
  2723. if (isalnum(data[i]))
  2724. continue;
  2725. switch (data[i]) {
  2726. case '@':
  2727. nb++;
  2728. case '-':
  2729. case '.':
  2730. case '_':
  2731. break;
  2732. case '>':
  2733. return (nb == 1) ? i + 1 : 0;
  2734. default:
  2735. return 0;
  2736. }
  2737. }
  2738. return 0;
  2739. }
  2740. /* tag_length - returns the length of the given tag, or 0 is it's not valid */
  2741. function tag_length(data, autolink) {
  2742. var i, j;
  2743. /* a valid tag can't be shorter than 3 chars */
  2744. if (data.length < 3)
  2745. return 0;
  2746. /* begins with a '<' optionally followed by '/', followed by letter or number */
  2747. if (data[0] != '<')
  2748. return 0;
  2749. i = (data[1] == '/') ? 2 : 1;
  2750. if (!isalnum(data[i]))
  2751. return 0;
  2752. /* scheme test */
  2753. autolink.p = MKDA_NOT_AUTOLINK;
  2754. /* try to find the beginning of an URI */
  2755. while (i < data.length && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-'))
  2756. i++;
  2757. if (i > 1 && data[i] == '@') {
  2758. if ((j = is_mail_autolink(data.slice(i))) != 0) {
  2759. autolink.p = MKDA_EMAIL;
  2760. return i + j;
  2761. }
  2762. }
  2763. if (i > 2 && data[i] == ':') {
  2764. autolink.p = MKDA_NORMAL;
  2765. i++;
  2766. }
  2767. /* completing autolink test: no whitespace or ' or " */
  2768. if (i >= data.length)
  2769. autolink.p = MKDA_NOT_AUTOLINK;
  2770. else if (autolink.p) {
  2771. j = i;
  2772. while (i < data.length) {
  2773. if (data[i] == '\\')
  2774. i += 2;
  2775. else if (data[i] == '>' || data[i] == '\'' ||
  2776. data[i] == '"' || data[i] == ' ' || data[i] == '\n')
  2777. break;
  2778. else
  2779. i++;
  2780. }
  2781. if (i >= data.length)
  2782. return 0;
  2783. if (i > j && data[i] == '>')
  2784. return i + 1;
  2785. /* one of the forbidden chars has been found */
  2786. autolink.p = MKDA_NOT_AUTOLINK;
  2787. }
  2788. /* looking for sometinhg looking like a tag end */
  2789. while (i < data.length && data[i] != '>')
  2790. i++;
  2791. if (i >= data.length)
  2792. return 0;
  2793. return i + 1;
  2794. }
  2795. // parse_inline - parses inline markdown elements
  2796. //Buffer, md, String
  2797. function parse_inline(out, md, data) {
  2798. var i = 0, end = 0;
  2799. var action = 0;
  2800. var work = new Buffer();
  2801. if (md.spanStack.length + md.blockStack.length > md.nestingLimit)
  2802. return;
  2803. while (i < data.length) {
  2804. /* copying inactive chars into the output */
  2805. while (end < data.length && !(action = md.activeChars[data[end]])) {
  2806. end++;
  2807. }
  2808. if (md.callbacks.normal_text) {
  2809. work.s = data.slice(i, end);
  2810. md.callbacks.normal_text(out, work, md.context);
  2811. }
  2812. else
  2813. out.s += data.slice(i, end);
  2814. if (end >= data.length)
  2815. break;
  2816. i = end;
  2817. end = markdown_char_ptrs[action](out, md, data, i);
  2818. if (!end)
  2819. end = i + 1;
  2820. else {
  2821. i += end;
  2822. end = i;
  2823. }
  2824. }
  2825. }
  2826. /* parse_atxheader - parsing of atx-style headers */
  2827. function parse_atxheader(out, md, data) {
  2828. var level = 0;
  2829. var i, end, skip;
  2830. while (level < data.length && level < 6 && data[level] == '#')
  2831. level++;
  2832. for (i = level; i < data.length && data[i] == ' '; i++) { }
  2833. for (end = i; end < data.length && data[end] != '\n'; end++) { }
  2834. skip = end;
  2835. while (end && data[end - 1] == '#')
  2836. end--;
  2837. while (end && data[end - 1] == ' ')
  2838. end--;
  2839. if (end > i) {
  2840. var work = new Buffer();
  2841. md.spanStack.push(work);
  2842. parse_inline(work, md, data.slice(i, end));
  2843. if (md.callbacks.header)
  2844. md.callbacks.header(out, work, level, md.context);
  2845. md.spanStack.pop();
  2846. }
  2847. return skip;
  2848. }
  2849. /* htmlblock_end - checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
  2850. /* returns the length on match, 0 otherwise */
  2851. // htmlblock_end(const char *tag, size_t tag_len, struct sd_markdown *rndr, uint8_t *data, size_t size)
  2852. function htmlblock_end(tag, md, data) {
  2853. var i, w;
  2854. /* checking if tag is a match */
  2855. //tag should already be lowercase
  2856. if (tag.length + 3 >= data.length ||
  2857. data.slice(2).toLowerCase() != tag ||
  2858. data[tag.length + 2] != '>')
  2859. return 0;
  2860. /* checking white lines */
  2861. i = tag.length + 3;
  2862. w = 0;
  2863. if (i < data.length && (w = is_empty(data.slice(i))) == 0)
  2864. return 0; /* non-blank after tag */
  2865. i += w;
  2866. w = 0;
  2867. if (i < data.length)
  2868. w = is_empty(data.slice(i));
  2869. return i + w;
  2870. }
  2871. /* parse_htmlblock - parsing of inline HTML block */
  2872. //TODO
  2873. function parse_htmlblock(out, md, data, do_render) {
  2874. var i, j = 0;
  2875. var curtag = null;
  2876. var found;
  2877. var work = new Buffer(data);
  2878. /* identification of the opening tag */
  2879. if (data.length < 2 || data[0] != '<')
  2880. return 0;
  2881. i = 1;
  2882. while (i < data.length && data[i] != '>' && data[i] != ' ')
  2883. i++;
  2884. if (i < data.length)
  2885. curtag = find_block_tag(data.slice(1));
  2886. /* handling of special cases */
  2887. if (!curtag) {
  2888. /* HTML comment, laxist form */
  2889. if (data.length > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') {
  2890. i = 5;
  2891. while (i < data.length && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>'))
  2892. i++;
  2893. i++;
  2894. if (i < data.length)
  2895. j = is_empty(data.slice(i));
  2896. if (j) {
  2897. //TODO: HANDLE WORK!!!
  2898. // work.size = i + j;
  2899. work.s = data.slice(0, i + j);
  2900. if (do_render && md.callbacks.blockhtml)
  2901. md.callbacks.blockhtml(out, work, md.context);
  2902. return work.s.length;
  2903. }
  2904. }
  2905. /* HR, which is the only self-closing block tag considered */
  2906. if (data.length > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) {
  2907. i = 3;
  2908. while (i < data.length && data[i] != '>')
  2909. i++;
  2910. if (i + 1 < data.length) {
  2911. i++;
  2912. j = is_empty(data.slice(i));
  2913. if (j) {
  2914. work.s = data.slice(0, i + j);
  2915. if (do_render && md.callbacks.blockhtml)
  2916. md.callbacks.blockhtml(out, work, md.context);
  2917. return work.s.length;
  2918. }
  2919. }
  2920. }
  2921. /* no special case recognised */
  2922. return 0;
  2923. }
  2924. /* looking for an unindented matching closing tag */
  2925. /* followed by a blank line */
  2926. i = 1;
  2927. found = 0;
  2928. /* if not found, trying a second pass looking for indented match */
  2929. /* but not if tag is "ins" or "del" (following original Markdown.pl) */
  2930. if (curtag != 'ins' && curtag != 'del') {
  2931. var tag_size = curtag.length;
  2932. i = 1;
  2933. while (i < data.length) {
  2934. i++;
  2935. while (i < data.length && !(data[i - 1] == '<' && data[i] == '/'))
  2936. i++;
  2937. if (i + 2 + tag_size >= data.length)
  2938. break;
  2939. // j = htmlblock_end(tag, md, data + i - 1, size - i + 1);
  2940. //TODO
  2941. j = htmlblock_end(curtag, md, data.slice(i - 1));
  2942. if (j) {
  2943. i += j - 1;
  2944. found = 1;
  2945. break;
  2946. }
  2947. }
  2948. }
  2949. if (!found)
  2950. return 0;
  2951. /* the end of the block has been found */
  2952. //TODO:
  2953. work.s = work.s.slice(0, i);
  2954. if (do_render && md.callbacks.blockhtml)
  2955. md.callbacks.blockhtml(out, work, md.context);
  2956. return i;
  2957. }
  2958. /* parse_blockquote - handles parsing of a blockquote fragment */
  2959. function parse_blockquote(out, md, data) {
  2960. var size = data.length;
  2961. var beg, end = 0, pre, work_size = 0;
  2962. // uint8_t *work_data = 0;
  2963. var work_data = "";
  2964. var work_data_cursor = 0;
  2965. var out_ = new Buffer();
  2966. md.blockStack.push(out_);
  2967. beg = 0;
  2968. while (beg < size) {
  2969. for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) { }
  2970. pre = prefix_quote(data.slice(beg, end));
  2971. if (pre)
  2972. beg += pre; /* skipping prefix */
  2973. else if (is_empty(data.slice(beg, end)) &&
  2974. (end >= size || (prefix_quote(data.slice(end)) == 0 &&
  2975. !is_empty(data.slice(end)))))
  2976. break;
  2977. if (beg < end) {
  2978. /* bufput(work, data + beg, end - beg); */
  2979. //TODO:!!! FIX THIS!!!
  2980. // if (!work_data) work_data = data.slice(beg, end);
  2981. // work_data = data + beg;
  2982. work_data += data.slice(beg, end);
  2983. /*
  2984. if (!work_data) work_data_cursor = beg;
  2985. else if (beg != work_data_cursor + work_size)
  2986. work_data += data.slice(beg, end);
  2987. */
  2988. // memmove(work_data + work_size, data + beg, end - beg);
  2989. work_size += end - beg;
  2990. }
  2991. beg = end;
  2992. }
  2993. parse_block(out_, md, work_data);
  2994. if (md.callbacks.blockquote)
  2995. md.callbacks.blockquote(out, out_, md.context);
  2996. md.blockStack.pop();
  2997. return end;
  2998. }
  2999. /* parse_paragraph - handles parsing of a regular paragraph */
  3000. function parse_paragraph(out, md, data) {
  3001. var i = 0, end = 0;
  3002. var level = 0;
  3003. var size = data.length;
  3004. var work = new Buffer(data);
  3005. while (i < size) {
  3006. for (end = i + 1; end < size && data[end - 1] != '\n'; end++) { }
  3007. if (prefix_quote(data.slice(i, end)) != 0) {
  3008. end = i;
  3009. break;
  3010. }
  3011. var tempdata = data.slice(i);
  3012. if (is_empty(tempdata) || (level = is_headerline(tempdata)) != 0)
  3013. break;
  3014. if (is_empty(tempdata))
  3015. break;
  3016. if ((level = is_headerline(tempdata)) != 0)
  3017. break;
  3018. if (is_atxheader(md, tempdata)
  3019. || is_hrule(tempdata)
  3020. || prefix_quote(tempdata)) {
  3021. end = i;
  3022. break;
  3023. }
  3024. /*
  3025. * Early termination of a paragraph with the same logic
  3026. * as markdown 1.0.0. If this logic is applied, the
  3027. * Markdown 1.0.3 test suite wont pass cleanly.
  3028. *
  3029. * :: If the first character in a new line is not a letter
  3030. * lets check to see if there's some kind of block starting here
  3031. */
  3032. if ((md.extensions & MKDEXT_LAX_SPACING) && !isalnum(data[i])) {
  3033. if (prefix_oli(tempdata)
  3034. || prefix_uli(tempdata)) {
  3035. end = i;
  3036. break;
  3037. }
  3038. /* see if an html block starts here */
  3039. if (data[i] == '<' && md.callbacks.blockhtml
  3040. && parse_htmlblock(out, md, tempdata, 0)) {
  3041. end = i;
  3042. break;
  3043. }
  3044. /* see if a code fence starts here */
  3045. if ((md.extensions && MKDEXT_FENCED_CODE) != 0
  3046. && is_codefence(tempdata, null) != 0) {
  3047. end = i;
  3048. break;
  3049. }
  3050. }
  3051. i = end;
  3052. }
  3053. var work_size = i;
  3054. while (work_size && data[work_size - 1] == '\n')
  3055. work_size--;
  3056. work.s = work.s.slice(0, work_size);
  3057. if (!level) {
  3058. var tmp = new Buffer();
  3059. md.blockStack.push(tmp);
  3060. parse_inline(tmp, md, work.s);
  3061. if (md.callbacks.paragraph)
  3062. md.callbacks.paragraph(out, tmp, md.context);
  3063. md.blockStack.pop();
  3064. }
  3065. else {
  3066. var header_work = null;
  3067. if (work.size) {
  3068. var beg;
  3069. i = work.s.length;
  3070. // var work_size = work.s.length - 1;
  3071. // work.size -= 1;
  3072. while (work_size && data[work_size] != '\n')
  3073. work_size -= 1;
  3074. beg = work_size + 1;
  3075. while (work_size && data[work_size - 1] == '\n')
  3076. work_size -= 1;
  3077. work.s = work.s.slice(0, work_size);
  3078. if (work_size > 0) {
  3079. var tmp = new Buffer();
  3080. md.blockStack.push(tmp);
  3081. parse_inline(tmp, md, work.s);
  3082. if (md.callbacks.paragraph)
  3083. md.callbacks.paragraph(out, tmp, md.context);
  3084. md.blockStack.pop();
  3085. work.s = work.s.slice(beg, i);
  3086. }
  3087. else
  3088. work.s = work.s.slice(0, i);
  3089. }
  3090. header_work = new Buffer();
  3091. md.spanStack.push(header_work);
  3092. parse_inline(header_work, md, work.s);
  3093. if (md.callbacks.header)
  3094. md.callbacks.header(out, header_work, level, md.context);
  3095. md.spanStack.pop();
  3096. }
  3097. return end;
  3098. }
  3099. /* parse_fencedcode - handles parsing of a block-level code fragment */
  3100. function parse_fencedcode(out, md, data) {
  3101. var beg, end;
  3102. var work = null;
  3103. var lang = new Buffer();
  3104. beg = is_codefence(data, lang);
  3105. if (beg == 0)
  3106. return 0;
  3107. work = new Buffer();
  3108. md.blockStack.push(work);
  3109. while (beg < data.length) {
  3110. var fence_end;
  3111. var fence_trail = new Buffer();
  3112. fence_end = is_codefence(data.slice(beg), fence_trail);
  3113. if (fence_end != 0 && fence_trail.s.length == 0) {
  3114. beg += fence_end;
  3115. break;
  3116. }
  3117. for (end = beg + 1; end < data.length && data[end - 1] != '\n'; end++) { }
  3118. if (beg < end) {
  3119. /* verbatim copy to the working buffer,
  3120. escaping entities */
  3121. var tempData = data.slice(beg, end);
  3122. if (is_empty(tempData))
  3123. work.s += '\n';
  3124. else
  3125. work.s += tempData;
  3126. }
  3127. beg = end;
  3128. }
  3129. if (work.s.length && work.s[work.s.length - 1] != '\n')
  3130. work.s += '\n';
  3131. if (md.callbacks.blockcode)
  3132. md.callbacks.blockcode(out, work, lang.s.length ? lang : null, md.context);
  3133. md.blockStack.pop();
  3134. return beg;
  3135. }
  3136. function parse_blockcode(out, md, data) {
  3137. var size = data.length;
  3138. var beg, end, pre;
  3139. var work = null;
  3140. md.blockStack.push(work = new Buffer());
  3141. beg = 0;
  3142. while (beg < size) {
  3143. for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) { }
  3144. ;
  3145. pre = prefix_code(data.slice(beg, end));
  3146. if (pre)
  3147. beg += pre; /* skipping prefix */
  3148. else if (!is_empty(data.slice(beg, end)))
  3149. /* non-empty non-prefixed line breaks the pre */
  3150. break;
  3151. if (beg < end) {
  3152. /* verbatim copy to the working buffer,
  3153. escaping entities */
  3154. if (is_empty(data.slice(beg, end)))
  3155. work.s += '\n';
  3156. else
  3157. work.s += data.slice(beg, end);
  3158. }
  3159. beg = end;
  3160. }
  3161. var work_size = work.s.length;
  3162. while (work_size && work.s[work_size - 1] == '\n')
  3163. work_size -= 1;
  3164. work.s = work.s.slice(0, work_size);
  3165. work.s += '\n';
  3166. if (md.callbacks.blockcode)
  3167. md.callbacks.blockcode(out, work, null, md.context);
  3168. md.blockStack.pop();
  3169. return beg;
  3170. }
  3171. /* parse_listitem - parsing of a single list item */
  3172. /* assuming initial prefix is already removed */
  3173. //FLAGS is pointer
  3174. function parse_listitem(out, md, data, flags) {
  3175. var size = data.length;
  3176. var work = null, inter = null;
  3177. var beg = 0, end, pre, sublist = 0, orgpre = 0, i;
  3178. var in_empty = 0, has_inside_empty = 0;
  3179. var in_fence = 0;
  3180. /* keeping track of the first indentation prefix */
  3181. while (orgpre < 3 && orgpre < size && data[orgpre] == ' ')
  3182. orgpre++;
  3183. //TODO
  3184. beg = prefix_uli(data);
  3185. if (!beg)
  3186. beg = prefix_oli(data);
  3187. if (!beg)
  3188. return 0;
  3189. /* skipping to the beginning of the following line */
  3190. end = beg;
  3191. while (end < size && data[end - 1] != '\n')
  3192. end++;
  3193. /* getting working buffers */
  3194. md.spanStack.push(work = new Buffer());
  3195. md.spanStack.push(inter = new Buffer());
  3196. /* putting the first line into the working buffer */
  3197. work.s += data.slice(beg, end);
  3198. beg = end;
  3199. /* process the following lines */
  3200. while (beg < size) {
  3201. var has_next_uli, has_next_oli;
  3202. end++;
  3203. while (end < size && data[end - 1] != '\n')
  3204. end++;
  3205. /* process an empty line */
  3206. if (is_empty(data.slice(beg, end))) {
  3207. in_empty = 1;
  3208. beg = end;
  3209. continue;
  3210. }
  3211. /* calculating the indentation */
  3212. i = 0;
  3213. while (i < 4 && beg + i < end && data[beg + i] == ' ')
  3214. i++;
  3215. pre = i;
  3216. //TODO: Cache this slice?
  3217. if (md.flags & MKDEXT_FENCED_CODE) {
  3218. if (is_codefence(data.slice(beg + i, end), null) != 0) {
  3219. in_fence = !in_fence;
  3220. }
  3221. }
  3222. /* only check for new list items if we are **not** in a fenced code block */
  3223. if (!in_fence) {
  3224. has_next_uli = prefix_uli(data.slice(beg + i, end));
  3225. has_next_oli = prefix_oli(data.slice(beg + i, end));
  3226. }
  3227. /* checking for ul/ol switch */
  3228. if (in_empty && (((flags.p & MKD_LIST_ORDERED) && has_next_uli) ||
  3229. (!(flags.p & MKD_LIST_ORDERED) && has_next_oli))) {
  3230. flags.p |= MKD_LI_END;
  3231. break; /* the following item must have same list type */
  3232. }
  3233. /* checking for a new item */
  3234. if ((has_next_uli && !is_hrule(data.slice(beg + i, end))) || has_next_oli) {
  3235. if (in_empty)
  3236. has_inside_empty = 1;
  3237. if (pre == orgpre)
  3238. break; /* the same indentation */
  3239. if (!sublist)
  3240. sublist = work.s.length;
  3241. }
  3242. else if (in_empty && pre == 0) {
  3243. flags.p |= MKD_LI_END;
  3244. break;
  3245. }
  3246. else if (in_empty) {
  3247. work.s += '\n';
  3248. has_inside_empty = 1;
  3249. }
  3250. in_empty = 0;
  3251. /* adding the line without prefix into the working buffer */
  3252. work.s += data.slice(beg + i, end);
  3253. beg = end;
  3254. }
  3255. /* render of li contents */
  3256. if (has_inside_empty)
  3257. flags.p |= MKD_LI_BLOCK;
  3258. if (flags.p & MKD_LI_BLOCK) {
  3259. /* intermediate render of block li */
  3260. if (sublist && sublist < work.s.length) {
  3261. parse_block(inter, md, work.s.slice(0, sublist));
  3262. parse_block(inter, md, work.s.slice(sublist));
  3263. }
  3264. else
  3265. parse_block(inter, md, work.s);
  3266. }
  3267. else {
  3268. //TODO:
  3269. /* intermediate render of inline li */
  3270. if (sublist && sublist < work.s.length) {
  3271. parse_inline(inter, md, work.s.slice(0, sublist));
  3272. parse_block(inter, md, work.s.slice(sublist));
  3273. }
  3274. else
  3275. parse_inline(inter, md, work.s);
  3276. }
  3277. /* render of li itself */
  3278. if (md.callbacks.listitem)
  3279. md.callbacks.listitem(out, inter, flags.p, md.context);
  3280. md.spanStack.pop();
  3281. md.spanStack.pop();
  3282. return beg;
  3283. }
  3284. /* parse_list - parsing ordered or unordered list block */
  3285. function parse_list(out, md, data, flags) {
  3286. var size = data.length;
  3287. var i = 0, j;
  3288. var work = null;
  3289. md.blockStack.push(work = new Buffer());
  3290. while (i < size) {
  3291. var flag_p = { p: flags };
  3292. j = parse_listitem(work, md, data.slice(i), flag_p);
  3293. flags = flag_p.p;
  3294. i += j;
  3295. if (!j || (flags & MKD_LI_END))
  3296. break;
  3297. }
  3298. if (md.callbacks.list)
  3299. md.callbacks.list(out, work, flags, md.context);
  3300. md.blockStack.pop();
  3301. return i;
  3302. }
  3303. function parse_table_row(out, md, data, columns, header_flag) {
  3304. var i = 0, col, cols_left;
  3305. var row_work = null;
  3306. if (!md.callbacks.table_cell || !md.callbacks.table_row)
  3307. return;
  3308. md.spanStack.push(row_work = new Buffer());
  3309. if (i < data.length && data[i] == '|')
  3310. i++;
  3311. for (col = 0; col < columns.length && i < data.length; ++col) {
  3312. var cell_start, cell_end;
  3313. var cell_work;
  3314. md.spanStack.push(cell_work = new Buffer());
  3315. while (i < data.length && _isspace(data[i]))
  3316. i++;
  3317. cell_start = i;
  3318. while (i < data.length && data[i] != '|')
  3319. i++;
  3320. cell_end = i - 1;
  3321. while (cell_end > cell_start && _isspace(data[cell_end]))
  3322. cell_end--;
  3323. // parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start);
  3324. parse_inline(cell_work, md, data.slice(cell_start, 1 + cell_end));
  3325. md.callbacks.table_cell(row_work, cell_work, columns[col] | header_flag, md.context, 0);
  3326. md.spanStack.pop();
  3327. i++;
  3328. }
  3329. cols_left = columns.length - col;
  3330. // for (; col < columns.length; ++col) {
  3331. if (cols_left > 0) {
  3332. var empty_cell = null;
  3333. md.callbacks.table_cell(row_work, empty_cell, columns[col] | header_flag, md.context, cols_left);
  3334. }
  3335. md.callbacks.table_row(out, row_work, md.context);
  3336. md.spanStack.pop();
  3337. }
  3338. function parse_table_header(out, md, data, columns) {
  3339. var i = 0, col, header_end, under_end;
  3340. var pipes = 0;
  3341. while (i < data.length && data[i] != '\n')
  3342. if (data[i++] == '|')
  3343. pipes++;
  3344. if (i == data.length || pipes == 0)
  3345. return 0;
  3346. header_end = i;
  3347. while (header_end > 0 && _isspace(data[header_end - 1]))
  3348. header_end--;
  3349. if (data[0] == '|')
  3350. pipes--;
  3351. if (header_end && data[header_end - 1] == '|')
  3352. pipes--;
  3353. if (pipes + 1 > md.maxTableCols)
  3354. return 0;
  3355. // columns.p = pipes + 1;
  3356. // column_data.p = new Array(columns.p);
  3357. columns.p = new Array(pipes + 1);
  3358. for (var k = 0; k < columns.p.length; k++)
  3359. columns.p[k] = 0;
  3360. /* Parse the header underline */
  3361. i++;
  3362. if (i < data.length && data[i] == '|')
  3363. i++;
  3364. under_end = i;
  3365. while (under_end < data.length && data[under_end] != '\n')
  3366. under_end++;
  3367. for (col = 0; col < columns.p.length && i < under_end; ++col) {
  3368. var dashes = 0;
  3369. while (i < under_end && data[i] == ' ')
  3370. i++;
  3371. if (data[i] == ':') {
  3372. i++;
  3373. columns.p[col] |= MKD_TABLE_ALIGN_L;
  3374. dashes++;
  3375. }
  3376. while (i < under_end && data[i] == '-') {
  3377. i++;
  3378. dashes++;
  3379. }
  3380. if (i < under_end && data[i] == ':') {
  3381. i++;
  3382. columns.p[col] |= MKD_TABLE_ALIGN_R;
  3383. dashes++;
  3384. }
  3385. while (i < under_end && data[i] == ' ')
  3386. i++;
  3387. if (i < under_end && data[i] != '|')
  3388. break;
  3389. if (dashes < 1)
  3390. break;
  3391. i++;
  3392. }
  3393. if (col < columns.p.length)
  3394. return 0;
  3395. parse_table_row(out, md, data.slice(0, header_end), columns.p, MKD_TABLE_HEADER);
  3396. return under_end + 1;
  3397. }
  3398. function parse_table(out, md, data) {
  3399. var i;
  3400. var header_work, body_work;
  3401. var columns = { p: null };
  3402. md.spanStack.push(header_work = new Buffer());
  3403. md.blockStack.push(body_work = new Buffer());
  3404. i = parse_table_header(header_work, md, data, columns);
  3405. if (i > 0) {
  3406. while (i < data.length) {
  3407. var row_start;
  3408. var pipes = 0;
  3409. row_start = i;
  3410. while (i < data.length && data[i] != '\n')
  3411. if (data[i++] == '|')
  3412. pipes++;
  3413. if (pipes == 0 || i == data.length) {
  3414. i = row_start;
  3415. break;
  3416. }
  3417. parse_table_row(body_work, md, data.slice(row_start, i), columns.p, 0);
  3418. i++;
  3419. }
  3420. if (md.callbacks.table)
  3421. md.callbacks.table(out, header_work, body_work, md.context);
  3422. }
  3423. md.spanStack.pop();
  3424. md.blockStack.pop();
  3425. return i;
  3426. }
  3427. function parse_block(out, md, data) {
  3428. var beg = 0, end, i;
  3429. var textData;
  3430. if (md.spanStack.length +
  3431. md.blockStack.length > md.nestingLimit)
  3432. return;
  3433. while (beg < data.length) {
  3434. textData = data.slice(beg);
  3435. end = data.length - beg;
  3436. if (is_atxheader(md, textData))
  3437. beg += parse_atxheader(out, md, textData);
  3438. else if (data[beg] == '<' && md.callbacks.blockhtml &&
  3439. (i = parse_htmlblock(out, md, textData, 1)) != 0)
  3440. beg += i;
  3441. else if ((i = is_empty(textData)) != 0)
  3442. beg += i;
  3443. else if (is_hrule(textData)) {
  3444. if (md.callbacks.hrule)
  3445. md.callbacks.hrule(out, md.context);
  3446. while (beg < data.length && data[beg] != '\n')
  3447. beg++;
  3448. beg++;
  3449. }
  3450. else if ((md.extensions & MKDEXT_FENCED_CODE) != 0 &&
  3451. (i = parse_fencedcode(out, md, textData)) != 0)
  3452. beg += i;
  3453. else if ((md.extensions & MKDEXT_TABLES) != 0 &&
  3454. (i = parse_table(out, md, textData)) != 0)
  3455. beg += i;
  3456. else if (prefix_quote(textData))
  3457. beg += parse_blockquote(out, md, textData);
  3458. else if (prefix_code(textData))
  3459. beg += parse_blockcode(out, md, textData);
  3460. else if (prefix_uli(textData))
  3461. beg += parse_list(out, md, textData, 0);
  3462. else if (prefix_oli(textData))
  3463. beg += parse_list(out, md, textData, MKD_LIST_ORDERED);
  3464. else {
  3465. beg += parse_paragraph(out, md, textData);
  3466. }
  3467. }
  3468. }
  3469. function is_ref(data, beg, end, md) {
  3470. /* int n; */
  3471. var i = 0;
  3472. var idOffset, idEnd;
  3473. var linkOffset, linkEnd;
  3474. var titleOffset, titleEnd;
  3475. var lineEnd;
  3476. /* up to 3 optional leading spaces */
  3477. if (beg + 3 >= end)
  3478. return 0;
  3479. if (data[beg] == ' ') {
  3480. i = 1;
  3481. if (data[beg + 1] == ' ') {
  3482. i = 2;
  3483. if (data[beg + 2] == ' ') {
  3484. i = 3;
  3485. if (data[beg + 3] == ' ')
  3486. return 0;
  3487. }
  3488. }
  3489. }
  3490. i += beg;
  3491. /* id part: anything but a newline between brackets */
  3492. if (data[i] != '[')
  3493. return 0;
  3494. i++;
  3495. idOffset = i;
  3496. while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']')
  3497. i++;
  3498. if (i >= end || data[i] != ']')
  3499. return 0;
  3500. idEnd = i;
  3501. /* spacer: colon (space | tab)* newline? (space | tab)* */
  3502. i++;
  3503. if (i >= end || data[i] != ':')
  3504. return 0;
  3505. i++;
  3506. while (i < end && data[i] == ' ')
  3507. i++;
  3508. if (i < end && (data[i] == '\n' || data[i] == '\r')) {
  3509. i++;
  3510. if (i < end && data[i] == '\r' && data[i - 1] == '\n')
  3511. i++;
  3512. }
  3513. while (i < end && data[i] == ' ')
  3514. i++;
  3515. if (i >= end)
  3516. return 0;
  3517. /* link: whitespace-free sequence, optionally between angle brackets */
  3518. if (data[i] == '<')
  3519. i++;
  3520. linkOffset = i;
  3521. while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r')
  3522. i++;
  3523. if (data[i - 1] == '>')
  3524. linkEnd = i - 1;
  3525. else
  3526. linkEnd = i;
  3527. /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
  3528. while (i < end && data[i] == ' ')
  3529. i++;
  3530. if (i < end && data[i] != '\n' && data[i] != '\r'
  3531. && data[i] != '\'' && data[i] != '"' && data[i] != '(')
  3532. return 0;
  3533. lineEnd = 0;
  3534. /* computing end-of-line */
  3535. if (i >= end || data[i] == '\r' || data[i] == '\n')
  3536. lineEnd = i;
  3537. if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
  3538. lineEnd = i + 1;
  3539. /* optional (space|tab)* spacer after a newline */
  3540. if (lineEnd) {
  3541. i = lineEnd + 1;
  3542. while (i < end && data[i] == ' ')
  3543. i++;
  3544. }
  3545. /* optional title: any non-newline sequence enclosed in '"()
  3546. alone on its line */
  3547. titleOffset = titleEnd = 0;
  3548. if (i + 1 < end && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) {
  3549. i++;
  3550. titleOffset = i;
  3551. /* looking for EOL */
  3552. while (i < end && data[i] != '\n' && data[i] != '\r')
  3553. i++;
  3554. if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
  3555. titleEnd = i + 1;
  3556. else
  3557. titleEnd = i;
  3558. /* stepping back */
  3559. i -= 1;
  3560. while (i > titleOffset && data[i] == ' ')
  3561. i -= 1;
  3562. if (i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) {
  3563. lineEnd = titleEnd;
  3564. titleEnd = i;
  3565. }
  3566. }
  3567. if (!lineEnd || linkEnd == linkOffset)
  3568. return 0; /* garbage after the link empty link */
  3569. var id = data.slice(idOffset, idEnd);
  3570. var link = data.slice(linkOffset, linkEnd);
  3571. var title = null;
  3572. if (titleEnd > titleOffset)
  3573. title = data.slice(titleOffset, titleEnd);
  3574. md.refs[id] = {
  3575. id: id,
  3576. link: new Buffer(link),
  3577. title: new Buffer(title)
  3578. };
  3579. return lineEnd;
  3580. }
  3581. function expand_tabs(out, line) {
  3582. var i = 0, tab = 0;
  3583. while (i < line.length) {
  3584. var org = i;
  3585. while (i < line.length && line[i] != '\t') {
  3586. i++;
  3587. tab++;
  3588. }
  3589. if (i > org)
  3590. out.s += line.slice(org, i);
  3591. if (i >= line.length)
  3592. break;
  3593. do {
  3594. out.s += ' ';
  3595. tab++;
  3596. } while (tab % 4);
  3597. i++;
  3598. }
  3599. }
  3600. /**
  3601. Render markdown code to HTML.
  3602. @param {string} source Markdown code.
  3603. @returns {string} HTML code.
  3604. */
  3605. function render(source) {
  3606. var text = new Buffer();
  3607. var beg = 0, end;
  3608. this.refs = {};
  3609. while (beg < source.length) {
  3610. if (end = is_ref(source, beg, source.length, this))
  3611. beg = end;
  3612. else {
  3613. end = beg;
  3614. while (end < source.length && source[end] != '\n' && source[end] != '\r')
  3615. end++;
  3616. /* adding the line body if present */
  3617. if (end > beg)
  3618. expand_tabs(text, source.slice(beg, end));
  3619. while (end < source.length && (source[end] == '\n' || source[end] == '\r')) {
  3620. /* add one \n per newline */
  3621. if (source[end] == '\n' || (end + 1 < source.length && source[end + 1] != '\n'))
  3622. text.s += '\n';
  3623. end++;
  3624. }
  3625. beg = end;
  3626. }
  3627. }
  3628. var out = new Buffer();
  3629. /* second pass: actual rendering */
  3630. if (this.callbacks.doc_header)
  3631. this.callbacks.doc_header(out, this.context);
  3632. if (text.s.length) {
  3633. /* adding a final newline if not already present */
  3634. if (text.s[text.s.length - 1] != '\n' && text.s[text.s.length - 1] != '\r')
  3635. text.s += '\n';
  3636. parse_block(out, this, text.s);
  3637. }
  3638. if (this.callbacks.doc_footer)
  3639. this.callbacks.doc_footer(out, this.context);
  3640. return out.s;
  3641. }
  3642. Markdown.prototype['render'] = render;
  3643. /**
  3644. Create a parser object using the given configuration parameters.
  3645. To get a Reddit equivelent configuration, pass no arguments.
  3646. @param {?Renderer=} renderer A renderer object.
  3647. @param {?Number=} extensions A series of OR'd extension flags. (Extension flags start with MKDEXT_)
  3648. @param {?Number=} nestingLimit The maximum depth to which inline elements can be nested.
  3649. @return {Markdown} A configured markdown object.
  3650. */
  3651. exports.getParser = function getParser(renderer, extensions, nestingLimit, columnLimit) {
  3652. var md = new Markdown();
  3653. if (renderer)
  3654. md.callbacks = renderer.callbacks;
  3655. if (nestingLimit)
  3656. md.nestingLimit = nestingLimit;
  3657. if (nestingLimit)
  3658. md.maxTableCols = columnLimit;
  3659. if (renderer)
  3660. md.context = renderer.context;
  3661. if (extensions != undefined && extensions != null)
  3662. md.extensions = extensions;
  3663. // if (!(nestingLimit > 0 && columnLimit > 0 && callbacks)) throw new Error;
  3664. var cb = md.callbacks;
  3665. if (cb.emphasis || cb.double_emphasis || cb.triple_emphasis) {
  3666. md.activeChars['*'] = MD_CHAR_EMPHASIS;
  3667. md.activeChars['_'] = MD_CHAR_EMPHASIS;
  3668. if (md.extensions & MKDEXT_STRIKETHROUGH)
  3669. md.activeChars['~'] = MD_CHAR_EMPHASIS;
  3670. }
  3671. if (cb.codespan)
  3672. md.activeChars['`'] = MD_CHAR_CODESPAN;
  3673. if (cb.linebreak)
  3674. md.activeChars['\n'] = MD_CHAR_LINEBREAK;
  3675. if (cb.image || cb.link)
  3676. md.activeChars['['] = MD_CHAR_LINK;
  3677. md.activeChars['<'] = MD_CHAR_LANGLE;
  3678. md.activeChars['\\'] = MD_CHAR_ESCAPE;
  3679. md.activeChars['&'] = MD_CHAR_ENTITITY;
  3680. if (md.extensions & MKDEXT_AUTOLINK) {
  3681. if (!(md.extensions & MKDEXT_NO_EMAIL_AUTOLINK)) {
  3682. md.activeChars['@'] = MD_CHAR_AUTOLINK_EMAIL;
  3683. }
  3684. md.activeChars[':'] = MD_CHAR_AUTOLINK_URL;
  3685. md.activeChars['w'] = MD_CHAR_AUTOLINK_WWW;
  3686. md.activeChars['/'] = MD_CHAR_AUTOLINK_SUBREDDIT_OR_USERNAME;
  3687. }
  3688. if (md.extensions & MKDEXT_SUPERSCRIPT)
  3689. md.activeChars['^'] = MD_CHAR_SUPERSCRIPT;
  3690. return md;
  3691. };
  3692. var DEFAULT_BODY_FLAGS = HTML_SKIP_HTML | HTML_SKIP_IMAGES | HTML_SAFELINK | HTML_ESCAPE | HTML_USE_XHTML;
  3693. var DEFAULT_WIKI_FLAGS = HTML_SKIP_HTML | HTML_SAFELINK | HTML_ALLOW_ELEMENT_WHITELIST | HTML_ESCAPE | HTML_USE_XHTML;
  3694. var DEFAULT_HTML_ATTR_WHITELIST = ['colspan', 'rowspan', 'cellspacing', 'cellpadding', 'scope'];
  3695. var DEFAULT_HTML_ELEMENT_WHITELIST = ['tr', 'th', 'td', 'table', 'tbody', 'thead', 'tfoot', 'caption'];
  3696. exports.DEFAULT_HTML_ELEMENT_WHITELIST = DEFAULT_HTML_ELEMENT_WHITELIST;
  3697. exports.DEFAULT_HTML_ATTR_WHITELIST = DEFAULT_HTML_ATTR_WHITELIST;
  3698. exports.DEFAULT_BODY_FLAGS = DEFAULT_BODY_FLAGS;
  3699. exports.DEFAULT_WIKI_FLAGS = DEFAULT_WIKI_FLAGS;
  3700. exports.HTML_SKIP_HTML = HTML_SKIP_HTML;
  3701. exports.HTML_SKIP_STYLE = HTML_SKIP_STYLE;
  3702. exports.HTML_SKIP_IMAGES = HTML_SKIP_IMAGES;
  3703. exports.HTML_SKIP_LINKS = HTML_SKIP_LINKS;
  3704. exports.HTML_EXPAND_TABS = HTML_EXPAND_TABS;
  3705. exports.HTML_SAFELINK = HTML_SAFELINK;
  3706. exports.HTML_TOC = HTML_TOC;
  3707. exports.HTML_HARD_WRAP = HTML_HARD_WRAP;
  3708. exports.HTML_USE_XHTML = HTML_USE_XHTML;
  3709. exports.HTML_ESCAPE = HTML_ESCAPE;
  3710. exports.HTML_ALLOW_ELEMENT_WHITELIST = HTML_ALLOW_ELEMENT_WHITELIST;
  3711. exports.MKDEXT_NO_INTRA_EMPHASIS = MKDEXT_NO_INTRA_EMPHASIS;
  3712. exports.MKDEXT_TABLES = MKDEXT_TABLES;
  3713. exports.MKDEXT_FENCED_CODE = MKDEXT_FENCED_CODE;
  3714. exports.MKDEXT_AUTOLINK = MKDEXT_AUTOLINK;
  3715. exports.MKDEXT_STRIKETHROUGH = MKDEXT_STRIKETHROUGH;
  3716. exports.MKDEXT_SPACE_HEADERS = MKDEXT_SPACE_HEADERS;
  3717. exports.MKDEXT_SUPERSCRIPT = MKDEXT_SUPERSCRIPT;
  3718. exports.MKDEXT_LAX_SPACING = MKDEXT_LAX_SPACING;
  3719. exports.MKDEXT_NO_EMAIL_AUTOLINK = MKDEXT_NO_EMAIL_AUTOLINK;
  3720. exports['SD_AUTOLINK_SHORT_DOMAINS'] = SD_AUTOLINK_SHORT_DOMAINS;
  3721. exports.MKDA_NOT_AUTOLINK = MKDA_NOT_AUTOLINK;
  3722. exports.MKDA_NORMAL = MKDA_NORMAL;
  3723. exports.MKDA_EMAIL = MKDA_EMAIL;
  3724. })(SnuOwnd);
  3725. ///////////////
  3726. // Cookie.ts //
  3727. ///////////////
  3728. // Uses the js-cookie library (lib/cookies.ts) for specialised cookie operations and intialization
  3729. var Cookie;
  3730. (function (Cookie) {
  3731. // INITIALIZATION
  3732. var cookieName = "LCE_" + THREAD;
  3733. var cookieVersion = '10';
  3734. // Try to load existing cookie save data, or create a cookie with default data
  3735. Cookie.saveDefaultOptions = true;
  3736. var save_default = {
  3737. version: cookieVersion,
  3738. options: {},
  3739. stats: {},
  3740. collapsed: [false, false, false, true]
  3741. };
  3742. Cookie.save = Cookies.getJSON(cookieName);
  3743. // In versions prior to 1.5.3, the extension used the cookie 'live-counting-extension'
  3744. // instead of 'LCE_{THREAD}'.
  3745. // To provide support for clients who had last used the extension at that point in time,
  3746. // we shall copy the contents of the cookie 'live-counting-extension' to 'LCE_{THREAD}'.
  3747. var oldCookie = Cookies.get('live-counting-extension');
  3748. if (oldCookie !== undefined && oldCookie !== null) {
  3749. if (Cookie.save === undefined || Cookie.save === null) {
  3750. Cookies.set(cookieName, oldCookie, { expires: 9000, path: '' });
  3751. Cookie.save = Cookies.getJSON(cookieName);
  3752. }
  3753. Cookies.remove('live-counting-extension', { path: '' });
  3754. }
  3755. // Create new cookie as it does not exist
  3756. if (Cookie.save === undefined || Cookie.save === null) {
  3757. Cookie.saveDefaultOptions = true;
  3758. Cookie.save = save_default;
  3759. update();
  3760. }
  3761. else if (Cookie.save.version != cookieVersion) {
  3762. Cookie.saveDefaultOptions = true;
  3763. Cookie.save.version = cookieVersion;
  3764. // If the current save is missing a few keys, add these keys, set to the default
  3765. for (var k in save_default) {
  3766. if (!Cookie.save.hasOwnProperty(k))
  3767. Cookie.save[k] = save_default[k];
  3768. }
  3769. update();
  3770. }
  3771. // METHODS
  3772. // Set the cookie value to `save`
  3773. function update() {
  3774. Cookies.set(cookieName, Cookie.save, { expires: 9000, path: '' });
  3775. }
  3776. Cookie.update = update;
  3777. })(Cookie || (Cookie = {}));
  3778. /////////////////
  3779. // Elements.ts //
  3780. /////////////////
  3781. var Elements;
  3782. (function (Elements) {
  3783. // PROPERTIES
  3784. // Important elements
  3785. Elements.$head = $('head');
  3786. Elements.$body = $('body');
  3787. Elements.$content = $('div.content');
  3788. Elements.$updates = $('.liveupdate-listing');
  3789. Elements.$options = $('#liveupdate-options');
  3790. Elements.$sidebar = $('aside.sidebar');
  3791. Elements.$form = $('#new-update-form');
  3792. Elements.$textarea = Elements.$form.find('textarea');
  3793. Elements.$submitBtn = Elements.$form.find('.save-button button');
  3794. Elements.$submitError = Elements.$form.find('.save-button .error');
  3795. // INITIALIZATION
  3796. Elements.$body.attr('id', 'lc-body');
  3797. // Prevent the larger $options from displacing the sidebar
  3798. // with different behaviour depending on whether or not textbox exists
  3799. if (Elements.$form.length) {
  3800. Elements.$form.after('<div style="clear:both;"></div>');
  3801. }
  3802. else {
  3803. Elements.$options.after('<div style="clear:both;"></div>');
  3804. }
  3805. // Make the submitError display default to none (important in RemoveSubmissionLag)
  3806. Elements.$submitError.css('display', 'inline');
  3807. })(Elements || (Elements = {}));
  3808. ;
  3809. ///////////////
  3810. // Styles.ts //
  3811. ///////////////
  3812. var Styles;
  3813. (function (Styles) {
  3814. // STYLESHEET
  3815. var $css = $("<style>\n\n\t/* General styles */\n\t#lc-body, #lc-body .liveupdate-listing {\n\t\tmin-width: 0px;\n\t}\n\n\t/* Prevent the button row from always showing up when screen is small */\n\t#lc-body li.liveupdate ul.buttonrow {\n\t\tdisplay: none !important;\n\t}\n\n\t#lc-body li.liveupdate:hover ul.buttonrow {\n\t\tdisplay: block !important;\n\t}\n\n\t/* Disable the transition entrance fade effect when an update is sent */\n\t#lc-body li.liveupdate * {\n\t\ttransition: none;\n\t}\n\n\t</style>");
  3816. // INITIALIZATION
  3817. $('head').append($css);
  3818. // METHODS
  3819. // Add code to stylesheet
  3820. function add(code) {
  3821. $css.append(code);
  3822. }
  3823. Styles.add = add;
  3824. })(Styles || (Styles = {}));
  3825. ////////////////
  3826. // Options.ts //
  3827. ////////////////
  3828. var Options;
  3829. (function (Options) {
  3830. // INITIALIZATION
  3831. // Initialize new content in the options box
  3832. // TODO: move the $all to another core file since it will be used for stats as well
  3833. var $all_heading = $("\n\t\t<h1 style=\"font-size:16px;\">\n\t\t\t<a href=\"https://github.com/co3carbonate/live-counting-extension/blob/master/README.md#readme\" target=\"_blank\">Live Counting Extension " + VERSION + "</a> \n\t\t</h1>\n\t");
  3834. var $options_heading = $("<h2>Options </h2>");
  3835. var $options_basic_heading = $("<h2>Basic </h2>");
  3836. var $options_advanced_heading = $("<h2>Advanced </h2>");
  3837. var $all_toggle = $("<span class=\"toggle-trigger\" style=\"font-size:15px;\">[-]</span>");
  3838. var $options_toggle = $("<span class=\"toggle-trigger\">[-]</span>");
  3839. var $options_basic_toggle = $("<span class=\"toggle-trigger\">[-]</span>");
  3840. var $options_advanced_toggle = $("<span class=\"toggle-trigger\">[-]</span>");
  3841. var $all = $("<div id='live-counting-extension'></div>");
  3842. var $options = $("<div></div>");
  3843. var $options_basic = $("<div></div>");
  3844. var $options_advanced = $("<div></div>");
  3845. $all_heading.append($all_toggle);
  3846. $all.append($options_heading, $options);
  3847. $options_heading.append($options_toggle);
  3848. $options_basic_heading.append($options_basic_toggle);
  3849. $options_advanced_heading.append($options_advanced_toggle);
  3850. $options.append($options_basic_heading, $options_basic, $options_advanced_heading, $options_advanced);
  3851. Elements.$options.append($all_heading, $all);
  3852. var all_innerWidth = $all.innerWidth();
  3853. var all_offsetLeft = $all.offset().left;
  3854. // Handling toggle buttons ([-] and [+])
  3855. function toggle($trigger, $change, index) {
  3856. // bind click event listeners to the trigger
  3857. $trigger.on('click', function () {
  3858. if ($change.css('display') == 'none') {
  3859. // show
  3860. $trigger.html('[-]');
  3861. $change.slideDown(500);
  3862. Cookie.save.collapsed[index] = false;
  3863. }
  3864. else {
  3865. // hide
  3866. $trigger.html('[+]');
  3867. $change.slideUp(500);
  3868. Cookie.save.collapsed[index] = true;
  3869. }
  3870. Cookie.update();
  3871. });
  3872. // immediately trigger the click event if current cookie save is true
  3873. if (Cookie.save.collapsed[index])
  3874. $trigger.trigger('click');
  3875. }
  3876. // call
  3877. toggle($all_toggle, $all, 0);
  3878. toggle($options_toggle, $options, 1);
  3879. toggle($options_basic_toggle, $options_basic, 2);
  3880. toggle($options_advanced_toggle, $options_advanced, 3);
  3881. // Styles
  3882. Styles.add("\n\n\t/* Subheadings */\n\t#live-counting-extension h2 {\n\t\tcolor: #4F4F4F;\n\t\tfont-size: 14px;\n\t\tfont-weight: bold;\n\t\tmargin: 8px 0px;\n\t}\n\n\t/* Toggle triggers */\n\t/* Don't specify #live-counting-extension for this, because the first trigger is actually outside the #live-counting-extension element */\n\t\n\t.toggle-trigger {\n\t\tcursor: pointer;\n\t\tcolor: #656565;\n\t\tfont-weight: normal;\n\t}\n\n\n\t/* Labels */\n\t#live-counting-extension label {\n\t\tdisplay: block;\n\t\tmargin-bottom: 10px;\n\t\tline-height: 160%;\n\t}\n\t\n\t");
  3883. // METHODS
  3884. // Add a checkbox option
  3885. // Returns the newly created checkbox
  3886. function addCheckbox(properties) {
  3887. // Handling properties
  3888. if (!properties.hasOwnProperty('section'))
  3889. properties.section = 'Basic';
  3890. if (!properties.hasOwnProperty('onchange'))
  3891. properties.onchange = null;
  3892. if (!properties.hasOwnProperty('default'))
  3893. properties["default"] = false;
  3894. if (!properties.hasOwnProperty('help'))
  3895. properties.help = '';
  3896. var label = properties["label"], section = properties["section"], onchange = properties["onchange"], defaultChecked = properties["default"], help = properties["help"];
  3897. // Default value handling (cookie)
  3898. var checked = defaultChecked;
  3899. if (Cookie.saveDefaultOptions && !Cookie.save.options.hasOwnProperty(label)) {
  3900. Cookie.save.options[label] = checked;
  3901. Cookie.update();
  3902. }
  3903. else
  3904. checked = Cookie.save.options[label];
  3905. // Create label and checkbox
  3906. var $elem = $("<input type=\"checkbox\"" + (checked ? ' checked="true"' : '') + "/>");
  3907. // Add option
  3908. var $options_section = $options_basic;
  3909. if (section == 'Basic')
  3910. $options_section = $options_basic;
  3911. else if (section == 'Advanced')
  3912. $options_section = $options_advanced;
  3913. $options_section.append($("<label>" + label + "</label>")
  3914. .attr('title', help)
  3915. .prepend($elem));
  3916. // Handle onchange
  3917. $elem.on('change', function () {
  3918. Cookie.save.options[label] = $elem.prop('checked');
  3919. Cookie.update();
  3920. if (onchange != null)
  3921. onchange.call($elem);
  3922. });
  3923. // Trigger change event if the value != default
  3924. if (defaultChecked != checked)
  3925. $elem.trigger('change');
  3926. return $elem;
  3927. }
  3928. Options.addCheckbox = addCheckbox;
  3929. // Add select option
  3930. // Returns the newly created select
  3931. function addSelect(properties) {
  3932. // Handling properties
  3933. if (!properties.hasOwnProperty('section'))
  3934. properties.section = 'Basic';
  3935. if (!properties.hasOwnProperty('onchange'))
  3936. properties.onchange = null;
  3937. if (!properties.hasOwnProperty('default'))
  3938. properties["default"] = 0;
  3939. if (!properties.hasOwnProperty('help'))
  3940. properties.help = '';
  3941. var label = properties["label"], options = properties["options"], section = properties["section"], onchange = properties["onchange"], selectedIndex = properties["default"], help = properties["help"];
  3942. // Default value handling (cookie)
  3943. var defaultVal = options[selectedIndex];
  3944. var selectedVal = defaultVal;
  3945. if (Cookie.saveDefaultOptions && !Cookie.save.options.hasOwnProperty(label)) {
  3946. Cookie.save.options[label] = selectedVal;
  3947. Cookie.update();
  3948. }
  3949. else
  3950. selectedVal = Cookie.save.options[label];
  3951. // Create label and select
  3952. var $options_section;
  3953. var $elem = $("<select></select>");
  3954. if (section == 'Basic')
  3955. $options_section = $options_basic;
  3956. else if (section == 'Advanced')
  3957. $options_section = $options_advanced;
  3958. $options_section.append($("<label>" + label + ": </label>")
  3959. .attr('title', help)
  3960. .append($elem));
  3961. // Configure the max-width of the select to ensure that it doesn't end up getting wrapped
  3962. // onto the next line
  3963. $elem.css('max-width', all_innerWidth - ($elem.offset().left - all_offsetLeft) + 'px');
  3964. // Set options of select
  3965. var elem_contents = '';
  3966. for (var i = 0; i < options.length; i++) {
  3967. elem_contents +=
  3968. "<option value=\"" + options[i] + "\"" + ((options[i] == selectedVal) ? ' selected="true"' : '') + ">" + options[i] + "</option>";
  3969. }
  3970. $elem.html(elem_contents);
  3971. // Handle onchange
  3972. $elem.on('change', function () {
  3973. Cookie.save.options[label] = $elem.val();
  3974. Cookie.update();
  3975. if (onchange != null)
  3976. onchange.call($elem);
  3977. });
  3978. // Trigger change event if the value != default
  3979. if (defaultVal != selectedVal)
  3980. $elem.trigger('change');
  3981. return $elem;
  3982. }
  3983. Options.addSelect = addSelect;
  3984. // WINDOW SIZE
  3985. // If sidebar turns into '[+] more about this live thread',
  3986. // (or window width < 700px according to CSS),
  3987. // move options to inside .sidebar
  3988. // New section in the sidebar for options
  3989. var $section = $("<section>\n\t\t<h2>options</h2>\n\t\t<div class='md'></div>\n\t</section>");
  3990. var $section_md = $section.children('.md');
  3991. $section.css('display', 'none').css('margin-top', '20px');
  3992. Elements.$sidebar.children('.sidebar-expand').after($section);
  3993. // Window resized
  3994. $(window).on('load resize', function () {
  3995. if (window.innerWidth <= 700) {
  3996. // add the options to '[+] more about this live thread'
  3997. if ($section.css('display') == 'none') {
  3998. $section.css('display', '');
  3999. $all.detach().appendTo($section_md);
  4000. }
  4001. }
  4002. else {
  4003. // remove the options from '[+] more about this live thread'
  4004. if ($section.css('display') != 'none') {
  4005. $section.css('display', 'none');
  4006. $all.detach().insertAfter($all_heading);
  4007. }
  4008. }
  4009. });
  4010. })(Options || (Options = {}));
  4011. ;
  4012. ///////////////
  4013. // Update.ts //
  4014. ///////////////
  4015. var Update;
  4016. (function (Update) {
  4017. // UTILITY
  4018. // Get information about an update node
  4019. function getUpdateInfo($node) {
  4020. var data = {
  4021. elem: $node,
  4022. author: $node.find('.body > .author').text(),
  4023. body_elem: $node.find('.body > .md'),
  4024. author_elem: $node.find('.body > .author')
  4025. };
  4026. if (data.author)
  4027. data.author = data.author.trim().replace('/u/', '');
  4028. return data;
  4029. }
  4030. // METHODS
  4031. // Bind functions to execute in the following events:
  4032. // - loadedNew(): When a new update is sent
  4033. // - loadedOld(): When an old update is loaded
  4034. // - striked(): When an update is striked
  4035. // - TODO: deleted(): When an update is deleted
  4036. // loaded from top (new updates sent)
  4037. var funcLoadedTop = [];
  4038. function loadedNew(func) {
  4039. funcLoadedTop.push(func);
  4040. }
  4041. Update.loadedNew = loadedNew;
  4042. // loaded from bottom (scrolled down to load old updates)
  4043. var funcLoadedBottom = [];
  4044. function loadedOld(func) {
  4045. funcLoadedBottom.push(func);
  4046. }
  4047. Update.loadedOld = loadedOld;
  4048. // striked
  4049. var funcStriked = [];
  4050. function striked(func) {
  4051. funcStriked.push(func);
  4052. }
  4053. Update.striked = striked;
  4054. // EVENTS
  4055. // Setup MutationObserver on Elements.$updates
  4056. var observer = new MutationObserver(function (mutations) {
  4057. // Loop through MutationRecords and call the functions in various arrays based on .type
  4058. // (Honestly the MutationRecord[] usually only contains one, but whatever)
  4059. for (var _i = 0, mutations_1 = mutations; _i < mutations_1.length; _i++) {
  4060. var mutation = mutations_1[_i];
  4061. // Addition / removal of child elements
  4062. // Executes loadedNew(), loadedOld(), deleted() functions accordingly
  4063. if (mutation.type == 'childList') {
  4064. // Setup variables for new updates or deleted updates
  4065. var $addedNodes = $(mutation.addedNodes).filter('.liveupdate');
  4066. var $removedNodes = $(mutation.removedNodes).filter('.liveupdate');
  4067. // Loop through new updates (if any)
  4068. $addedNodes.each(function (index, element) {
  4069. var $node = $(element);
  4070. if ($node.hasClass('preview'))
  4071. return; // ignore preview messages (RemoveSubmissionLag.ts)
  4072. // Get data about the new update
  4073. var data = getUpdateInfo($node);
  4074. // Check if the update was loaded from top or bottom
  4075. // Execute loadedNew() or loadedOld() functions accordingly
  4076. if ($node.index() == 0) {
  4077. // Loaded from top
  4078. // Execute loadedNew() functions
  4079. for (var _i = 0, funcLoadedTop_1 = funcLoadedTop; _i < funcLoadedTop_1.length; _i++) {
  4080. var func = funcLoadedTop_1[_i];
  4081. func(data);
  4082. }
  4083. }
  4084. else {
  4085. // Loaded from bottom
  4086. // Execute loadedOld() functions
  4087. for (var _a = 0, funcLoadedBottom_1 = funcLoadedBottom; _a < funcLoadedBottom_1.length; _a++) {
  4088. var func = funcLoadedBottom_1[_a];
  4089. func(data);
  4090. }
  4091. }
  4092. });
  4093. }
  4094. else if (mutation.type == 'attributes') {
  4095. // Setup
  4096. var $node = $(mutation.target);
  4097. if (!(mutation.oldValue && $node.attr('class')))
  4098. return;
  4099. var oldClasses = mutation.oldValue.split(' ');
  4100. var newClasses = $node.attr('class').split(' ');
  4101. // Must be a .liveupdate element
  4102. if (!$node.hasClass('liveupdate'))
  4103. return;
  4104. // Get data about the update
  4105. var data = getUpdateInfo($node);
  4106. // Check if the update had only now been stricken
  4107. if (oldClasses.indexOf('stricken') == -1
  4108. && newClasses.indexOf('stricken') > -1) {
  4109. // Execute striked() functions
  4110. for (var _a = 0, funcStriked_1 = funcStriked; _a < funcStriked_1.length; _a++) {
  4111. var func = funcStriked_1[_a];
  4112. func(data);
  4113. }
  4114. }
  4115. }
  4116. }
  4117. });
  4118. observer.observe(Elements.$updates.get(0), {
  4119. // observe for insertion / removal of children updates
  4120. childList: true,
  4121. // observe for change in the 'class' attribute value
  4122. attributes: true,
  4123. attributeOldValue: true,
  4124. attributeFilter: ['class'],
  4125. // observe for these changes (particularly attributes changes) in descendants
  4126. subtree: true
  4127. });
  4128. })(Update || (Update = {}));
  4129. //////////////////
  4130. // CtrlEnter.ts //
  4131. //////////////////
  4132. var CtrlEnter;
  4133. (function (CtrlEnter) {
  4134. // INITIALIZATION
  4135. var enabled = true;
  4136. var $textarea = $('#new-update-form textarea');
  4137. var $submitBtn = $('#new-update-form .save-button button');
  4138. // RES already has a ctrl-enter feature since v4.7.8
  4139. // Skip remaining actions if using a version higher than that
  4140. var $resVersion = $('#RESConsoleVersion');
  4141. if ($resVersion.length > 0 && +($resVersion.text().replace(/\D/g, '')) >= 478)
  4142. enabled = false;
  4143. // Bind keydown event to the textarea
  4144. if (enabled) {
  4145. $textarea.on('keydown', function (e) {
  4146. if (e.keyCode == 13 && (e.ctrlKey || e.metaKey)) {
  4147. e.preventDefault();
  4148. $submitBtn.trigger('click');
  4149. }
  4150. });
  4151. }
  4152. })(CtrlEnter || (CtrlEnter = {}));
  4153. /////////////////////////
  4154. // ColoredUsernames.ts //
  4155. /////////////////////////
  4156. var ColoredUsernames;
  4157. (function (ColoredUsernames) {
  4158. // INITIALIZATION
  4159. // Specified colors for known users
  4160. var userColors = {
  4161. 'SolidGoldMagikarp': '#008080',
  4162. 'rschaosid': '#008080',
  4163. 'live_mentions': 'Black',
  4164. 'joinlivecounting': 'Black',
  4165. 'livecounting_sidebar': 'Black',
  4166. 'piyushsharma301': '#FF0F19',
  4167. 'Tranquilsunrise': 'Orange',
  4168. 'dominodan123': 'Blue',
  4169. 'smarvin6689':'#060647',
  4170. 'rideride':'#069420',
  4171. 'nomaur2':'#8A2BE2',
  4172. 'VitaminB16': '#1AFFA7',
  4173. 'co3_carbonate': 'Grey',
  4174. 'artbn': '#e66b00',
  4175. 'amazingpikachu_38': '#FFFF00',
  4176. 'qwertylool': "YellowGreen",
  4177. 'TOP_20': '#ff00bf',
  4178. 'parker_cube': '#FF69B4',
  4179. 'QuestoGuy': 'Purple',
  4180. 'Smartstocks': '#840d0d',
  4181. 'gordonpt8': '#00FF00',
  4182. 'Mooraell': '#DAA520',
  4183. 'randomusername123458': '#00CC99',
  4184. 'davidjl123': '#6495ED',
  4185. 'abplows':'#2B0090',
  4186. 'Iamspeedy36': '#00BFFF',
  4187. 'Phoenixness': '#ff0000',
  4188. 'jillis6': '#ffd700',
  4189. 'Kris18': '#0000ff',
  4190. 'xHOCKEYx12': 'Lime',
  4191. '_ntrpy': '#FF6600',
  4192. 'o99o99': '#2BBDFF',
  4193. 'afaintsmellofcurry': '#6799A0',
  4194. 'KingCaspianX': '#191970',
  4195. 'MewDP': '#FFFF33',
  4196. 'DaveTheDave_': '#00BFFF',
  4197. 'Luigi86101': '#006400',
  4198. 'thetiredlemur': '#464942',
  4199. 'DemonBurritoCat':'#890003',
  4200. 'TheGlobeIsRound': '#0080ff',
  4201. 'CarbonSpectre': '#339933'
  4202. };
  4203. // Possible colors for other users
  4204. var colors = ['Blue', 'Coral', 'DodgerBlue', 'SpringGreen', 'YellowGreen', 'Green', 'OrangeRed', 'Red', 'GoldenRod', 'CadetBlue', 'SeaGreen', 'Chocolate', 'BlueViolet', 'Firebrick'];
  4205. for (var i = colors.length - 1; i > 0; i--) {
  4206. // use Durstenfeld shuffle algorithm on colors array
  4207. var j = Math.floor(Math.random() * (i + 1));
  4208. var temp = colors[i];
  4209. colors[i] = colors[j];
  4210. colors[j] = temp;
  4211. }
  4212. function aprfoo(str){
  4213. var i = 0;
  4214. var l = str.length;
  4215. var start = 0;
  4216. var end = l - 1;
  4217. t = str.replace("0", "7").replace("1","7").replace("2","7").replace("3","7").replace("4","7").replace("5","7").replace("6","7").replace("8","7").replace("9","7");
  4218. return t;
  4219. }
  4220.  
  4221. function chu_inc1(){
  4222. master_s = ["7️⃣", "πŸ’", "πŸ’Ž","πŸ‰","πŸ‡","🍊","🍌"];
  4223. index = Math.floor(Math.random()*7)
  4224. return master_s[index];
  4225. }
  4226.  
  4227. function chu_inc(){
  4228. master_s = "πŸΆπŸŒΈπŸŒΊπŸŒΌπŸΉπŸ•ŠπŸ°πŸŽπŸŒˆπŸ“˜πŸŽ΅πŸ…πŸ†9βƒ£πŸ”†πŸ˜ŽπŸ¬β€ ⚽ 😍 βœ…πŸŽ–πŸ‡πŸπŸ‰πŸ˜‡πŸ’‹πŸ™ŠπŸ“πŸ₯‡πŸ’”πŸ¦‹β­ ❀ πŸ’―πŸ’€πŸŒŸπŸ“’πŸŽ€πŸ’Ύβ¬… πŸ’™πŸ’šπŸ’œβ„’ ️";
  4229. index = Math.floor(Math.random()*44)+1
  4230. chu = master_s.substring(index*2,index*2+2);
  4231. return master_s.substring(index*2,index*2+2);
  4232. }
  4233. // index of next color to assign from colors array
  4234. var currentColor = 0;
  4235. // Options
  4236. var enabled = true;
  4237. Options.addCheckbox({
  4238. label: 'COLORED USERNAMES',
  4239. "default": true,
  4240. help: 'Makes the username in each message gain a unique color.\n\nCertain users who have specified their preferred username colors to /u/co3_carbonaate get that fixed color all the time. Otherwise, your color will be random and appear differently for everyone using the extension.',
  4241. onchange: function () {
  4242. enabled = this.prop('checked');
  4243. }
  4244. });
  4245. // EVENTS
  4246. // New update loaded
  4247. Update.loadedNew(function (data) {
  4248. if (!enabled)
  4249. return;
  4250. // Special usernames (temp rewards for top in 100k HoC, or other contributions)
  4251. // Bot-maker privileges (/u/co3_carbonate, /u/rschaosid, /u/piyushsharma301,/u/rideride)
  4252. if (data.author == 'co3_carbonate' || data.author == 'rschaosid' || data.author == 'piyushsharma301' || data.author == 'rideride') {
  4253. data.author_elem.css('font-weight', 'bold');
  4254. }
  4255. // /u/gordonpt8 username special (Bold and Changed text)
  4256. if (data.author == 'smarvin6689') {
  4257. data.author_elem.css({
  4258. 'letter-spacing': '2px',
  4259. 'font-weight': 'bold',
  4260. 'background-color': 'Black',
  4261. });
  4262. data.author_elem.html('πŸš”<span style="color:#FF0000">s</span><span style="color:#FFFFFF">m</span><span style="color:#0000FF">a</span><span style="color:#FF0000">r</span><span style="color:#FFFFFF">v</span><span style="color:#0000FF">i</span><span style="color:#FF0000">n</span><span style="color:#FFFFFF">6</span><span style="color:#0000FF">6</span><span style="color:#FF0000">8</span><span style="color:#FFFFFF">9</span>πŸš”');
  4263. return;
  4264. }
  4265.  
  4266.  
  4267. // Set username colour
  4268. if (!userColors.hasOwnProperty(data.author)) {
  4269. userColors[data.author] = colors[currentColor];
  4270. currentColor++;
  4271. if (currentColor == colors.length) {
  4272. currentColor = 0;
  4273. }
  4274. }
  4275. data.author_elem.css('color', userColors[data.author]);
  4276.  
  4277. });
  4278. })(ColoredUsernames || (ColoredUsernames = {}));
  4279. //////////////////////////
  4280. // ClearPastMessages.ts //
  4281. //////////////////////////
  4282. var ClearPastMessages;
  4283. (function (ClearPastMessages) {
  4284. // INITIALIZATION
  4285. var maxMessages = 50;
  4286. // Options
  4287. var enabled = true;
  4288. var $checkbox = Options.addCheckbox({
  4289. label: 'CLEAR PAST MESSAGES (REDUCES LAG)',
  4290. "default": true,
  4291. help: 'Frequently clears past messages from the page, which drastically negates lag and reduces the need to refresh constantly.',
  4292. onchange: function () {
  4293. enabled = this.prop('checked');
  4294. }
  4295. });
  4296. // EVENTS
  4297. // New update loaded
  4298. Update.loadedNew(function (data) {
  4299. if (!enabled)
  4300. return;
  4301. var $screenMessages = Elements.$updates.children('.liveupdate');
  4302. if ($screenMessages.length > maxMessages) {
  4303. $screenMessages.slice(maxMessages).remove();
  4304. }
  4305. });
  4306. // Old update loaded (scrolled to bottom)
  4307. Update.loadedOld(function (data) {
  4308. // disable
  4309. if (!enabled)
  4310. return;
  4311. $checkbox.prop('checked', false).trigger('change');
  4312. });
  4313. })(ClearPastMessages || (ClearPastMessages = {}));
  4314. ////////////////////
  4315. // DisplayMode.ts //
  4316. ////////////////////
  4317. var DisplayMode;
  4318. (function (DisplayMode) {
  4319. // INITIALIZATION
  4320. Elements.$body.attr('data-DisplayMode', 'Normal');
  4321. // "Return to Normal Display" button
  4322. var $returnBtn = $('<input type="button" value="Return to Normal Display"/>');
  4323. $returnBtn.on('click', function () {
  4324. $select.children('option[value="Normal"]').prop('selected', true).trigger('change');
  4325. });
  4326. $returnBtn.css({
  4327. marginBottom: '20px',
  4328. display: 'none',
  4329. margin: '0 auto'
  4330. });
  4331. Elements.$content.prepend($returnBtn);
  4332. // Options
  4333. var $select = Options.addSelect({
  4334. label: 'DISPLAY MODE',
  4335. options: ['Normal', 'Minimal'],
  4336. help: 'Changes the display interface of the page to your preference.',
  4337. onchange: function () {
  4338. var display = this.val();
  4339. $returnBtn.css('display', (display == 'Normal' ? 'none' : 'block'));
  4340. Elements.$body.attr('data-DisplayMode', display);
  4341. }
  4342. });
  4343. // Styles
  4344. Styles.add("\n\n\t/* Display Minimal */\n\t#lc-body[data-DisplayMode='Minimal'] #header,\n\t#lc-body[data-DisplayMode='Minimal'] #liveupdate-statusbar,\n\t#lc-body[data-DisplayMode='Minimal'] .markdownEditor-wrapper,\n\t#lc-body[data-DisplayMode='Minimal'] #new-update-form .bottom-area,\n\t#lc-body[data-DisplayMode='Minimal'] li.liveupdate time.live-timestamp,\n\t#lc-body[data-DisplayMode='Minimal'] #liveupdate-options, \n\t#lc-body[data-DisplayMode='Minimal'] aside.sidebar {\n\t\tdisplay: none;\n\t}\n\n\t#lc-body[data-DisplayMode='Minimal'] #liveupdate-header,\n\t#lc-body[data-DisplayMode='Minimal'] #new-update-form {\n\t\tmargin-left: 0px;\n\t}\n\n\t#lc-body[data-DisplayMode='Minimal'] li.liveupdate ul.buttonrow {\n\t\tmargin: 0 0 2em 0px !important;\n\t}\n\n\t#lc-body[data-DisplayMode='Minimal'] div.content {\n\t\tmax-width: " + Math.max(450, $('#new-update-form textarea').outerWidth()) + "px;\n\t}\n\n\t");
  4345. })(DisplayMode || (DisplayMode = {}));
  4346. ////////////////////////////
  4347. // RemoveSubmissionLag.ts //
  4348. ////////////////////////////
  4349. var RemoveSubmissionLag;
  4350. (function (RemoveSubmissionLag) {
  4351. // INITIALIZATION
  4352. var lastInput = '';
  4353. var enabled = true;
  4354. var ghostEnabled = true;
  4355. var previews = [];
  4356. // Options
  4357. Options.addSelect({
  4358. label: 'REMOVE SUBMISSION LAG',
  4359. options: ['Enabled', 'Enabled without Ghost Messages', 'Disabled'],
  4360. "default": 0,
  4361. help: 'Upon submitting a message, the textbox is immediately cleared to allow you to enter new contents without waiting for your previous submission to be processed.\n\nThe ghost messages are to prevent messages from being permanently lost if they had failed to deliver. You can enable the feature without ghost messages if you find them too distracting.',
  4362. onchange: function () {
  4363. var display = this.val();
  4364. enabled = display == 'Enabled' || display == 'Enabled without Ghost Messages';
  4365. ghostEnabled = display == 'Enabled';
  4366. }
  4367. });
  4368. // Styles
  4369. Styles.add("\n\n\t.liveupdate-listing li.liveupdate.preview {\n\t\topacity: 0.75;\n\t}\n\t.liveupdate-listing li.liveupdate.preview .live-timestamp {\n\t\tvisibility: hidden;\n\t}\n\n\t");
  4370. // EVENTS
  4371. // When message is submitted
  4372. Elements.$submitBtn.on('click', function (e) {
  4373. if (!enabled)
  4374. return;
  4375. setTimeout(function () {
  4376. var val = Elements.$textarea.val();
  4377. if (val.length == 0)
  4378. return;
  4379. // Add preview element, a "ghost" message containing the contents of the new message
  4380. // until it has been delivered.
  4381. // Prevents permanent loss of messages if delivery fails
  4382. if (ghostEnabled) {
  4383. var html = SnuOwnd.getParser().render(val);
  4384. var $buttonRow = $("\n\t\t\t\t\t<ul class=\"buttonrow\">\n\t\t\t\t\t\t<li><button>retry</button></li>\n\t\t\t\t\t\t<li><button>cancel</button></li>\n\t\t\t\t\t</ul>\n\t\t\t\t");
  4385. var $elem_1 = $("\n\t\t\t\t\t<li class=\"liveupdate preview\">\n\t\t\t\t\t\t<a href=\"#\"><time class=\"live-timestamp\"></time></a>\n\t\t\t\t\t\t<div class=\"body\">\n\t\t\t\t\t\t\t<div class=\"md\">\n\t\t\t\t\t\t\t\t" + html + "\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</li>\n\t\t\t\t").append($buttonRow);
  4386. previews.push({
  4387. html: html.trim().replace(/(\r\n|\n|\r)/gm, ""),
  4388. elem: $elem_1
  4389. });
  4390. Elements.$updates.prepend($elem_1);
  4391. // Setup event listeners for the buttons of the preview message
  4392. var $buttons = $buttonRow.find('button');
  4393. // "Retry" button
  4394. $buttons.eq(0).on('click', function () {
  4395. Elements.$textarea.val(val).focus();
  4396. });
  4397. // "Cancel" button
  4398. $buttons.eq(1).on('click', function () {
  4399. for (var i = 0; i < previews.length; i++) {
  4400. if ($elem_1 == previews[i].elem) {
  4401. $elem_1.remove();
  4402. previews.splice(i, 1);
  4403. break;
  4404. }
  4405. }
  4406. });
  4407. }
  4408. // Clear textbox
  4409. Elements.$textarea.val('');
  4410. // This is a way to work around the issue where Reddit automatically clears the textbox
  4411. // when the update had been successfully delivered, although we just cleared it here.
  4412. // Call backupInput() whenever the textarea content is changed
  4413. Elements.$textarea.on('keydown keyup input', backupInput);
  4414. }, 0);
  4415. });
  4416. // In backupInput(), keep track of the last backed up textarea content, by storing in lastInput
  4417. function backupInput() {
  4418. lastInput = Elements.$textarea.val();
  4419. }
  4420. // Use MutationObserver on the 'error message' to detect when Reddit had cleared the textbox.
  4421. // When the error message's style changes from 'display: inline;' to 'display: none;', it
  4422. // is clear that Reddit had cleared the textbox.
  4423. // At this point, use the last backed-up input
  4424. var observer = new MutationObserver(function (mutations) {
  4425. if (!enabled)
  4426. return;
  4427. if (mutations.length != 1)
  4428. return;
  4429. // Exit if we think that Reddit had not cleared the textbox
  4430. var mutation = mutations[0];
  4431. if (!(mutation.oldValue == 'display: inline;' &&
  4432. Elements.$submitError.attr('style') == 'display: none;'))
  4433. return;
  4434. // Use the last backed-up input
  4435. Elements.$textarea.off('keydown keyup input', backupInput);
  4436. Elements.$textarea.val(lastInput);
  4437. lastInput = '';
  4438. });
  4439. observer.observe(Elements.$submitError.get(0), {
  4440. // observe for change in 'style' attribute value
  4441. attributes: true,
  4442. attributeOldValue: true,
  4443. attributeFilter: ['style']
  4444. });
  4445. // When new message is loaded and is by this user, check if we can delete the corresponding
  4446. // preview message.
  4447. // If it is by another user, push all the preview messages to the front (in the right order),
  4448. // so that they seem to always be on top.
  4449. // (Only if preview messages are enabled)
  4450. Update.loadedNew(function (data) {
  4451. if (!enabled || !ghostEnabled)
  4452. return;
  4453. var l = previews.length; // number of preview messages
  4454. if (data.author != USER) {
  4455. // Message not from this user
  4456. // Attempt to bring all the preview messages to the front and exit
  4457. for (var i = 0; i < l; i++) {
  4458. Elements.$updates.prepend(previews[i].elem);
  4459. }
  4460. return;
  4461. }
  4462. // Get the contents of the user's message (trimmed and without linebreaks),
  4463. // loop through the preview messages (trimmed and without linebreaks),
  4464. // and if the contents are the same, delete the preview message
  4465. var body = data.body_elem.html().replace(/(\r\n|\n|\r)/gm, "").trim();
  4466. //console.log(body);
  4467. var to_delete = -1;
  4468. for (var i = 0; i < l; i++) {
  4469. //console.log(previews[i].html);
  4470. if (previews[i].html == body) {
  4471. to_delete = i;
  4472. break;
  4473. }
  4474. }
  4475. if (to_delete != -1)
  4476. previews.splice(to_delete, 1)[0].elem.remove();
  4477. });
  4478. })(RemoveSubmissionLag || (RemoveSubmissionLag = {}));
  4479. /////////////////////////////
  4480. // DisableUsernameLinks.ts //
  4481. /////////////////////////////
  4482. var DisableUsernameLinks;
  4483. (function (DisableUsernameLinks) {
  4484. // INITIALIZATION
  4485. Elements.$body.attr('data-DisableUsernameLinks', 'false');
  4486. // Options
  4487. Options.addCheckbox({
  4488. label: 'DISABLE USERNAME LINKS',
  4489. section: 'Advanced',
  4490. help: 'Disables the redirection to a user\'s profile upon clicking on his/her username. This is convenient to prevent yourself from accidentally going to one\'s profile page when trying to strike or delete a message.',
  4491. onchange: function () {
  4492. Elements.$body.attr('data-DisableUsernameLinks', this.prop('checked').toString());
  4493. }
  4494. });
  4495. // Styles
  4496. Styles.add("\n\n\t#lc-body[data-DisableUsernameLinks='true'] li.liveupdate > .body > .author {\n\t\tpointer-events: none;\n\t\tcursor: auto;\n\t}\n\n\t");
  4497. })(DisableUsernameLinks || (DisableUsernameLinks = {}));
  4498. ////////////////////////
  4499. // LinksOpenNewTab.ts //
  4500. ////////////////////////
  4501. var LinksOpenNewTab;
  4502. (function (LinksOpenNewTab) {
  4503. // INITIALIZATION
  4504. var $base = $('<base target="_blank">');
  4505. Elements.$head.append($base);
  4506. // Options
  4507. var enabled = true;
  4508. Options.addCheckbox({
  4509. label: 'MAKE ALL LINKS OPEN IN A NEW TAB',
  4510. "default": true,
  4511. section: 'Advanced',
  4512. help: 'Makes all links on the page open in a new tab.',
  4513. onchange: function () {
  4514. enabled = this.prop('checked');
  4515. if (enabled)
  4516. $base.attr('target', '_blank');
  4517. else
  4518. $base.attr('target', '_self');
  4519. }
  4520. });
  4521. })(LinksOpenNewTab || (LinksOpenNewTab = {}));
  4522. /////////////////////////
  4523. // DisableShortcuts.ts //
  4524. /////////////////////////
  4525. var DisableShortcuts;
  4526. (function (DisableShortcuts) {
  4527. // INITIALIZATION
  4528. // Options
  4529. var enabled = true;
  4530. Options.addCheckbox({
  4531. label: 'DISABLE OBSTRUCTIVE BROWSER SHORTCUTS',
  4532. "default": true,
  4533. section: 'Advanced',
  4534. help: 'Disables certain obstructive browser keyboard shortcuts. This currently disables the following: Ctrl+0 (Zoom Reset), Ctrl+[1-9] (Switch Tabs)',
  4535. onchange: function () {
  4536. enabled = this.prop('checked');
  4537. }
  4538. });
  4539. // EVENTS
  4540. $(document).on('keydown', function (e) {
  4541. if (!enabled)
  4542. return;
  4543. // Ctrl Hotkeys
  4544. if (e.ctrlKey) {
  4545. // Ctrl+0 (Zoom Reset)
  4546. if (e.keyCode == 48)
  4547. e.preventDefault();
  4548. // Ctrl+[1-9] (Switch Tabs)
  4549. if (e.keyCode >= 49 && e.keyCode <= 57)
  4550. e.preventDefault();
  4551. }
  4552. });
  4553. })(DisableShortcuts || (DisableShortcuts = {}));
  4554. ////////////////////////
  4555. // ContentPosition.ts //
  4556. ////////////////////////
  4557. var ContentPosition;
  4558. (function (ContentPosition) {
  4559. // INITIALIZATION
  4560. Elements.$body.attr('data-ContentPosition', 'Center');
  4561. // Options
  4562. Options.addSelect({
  4563. label: 'CONTENT POSITION',
  4564. options: ['Left', 'Center', 'Right'],
  4565. section: 'Advanced',
  4566. "default": 1,
  4567. help: 'Adjusts the position of the main content section.',
  4568. onchange: function () {
  4569. Elements.$body.attr('data-ContentPosition', this.val());
  4570. }
  4571. });
  4572. // Styles
  4573. Styles.add("\n\n\t#lc-body[data-ContentPosition='Left'] div.content {\n\t\tmargin: 0;\n\t}\n\t#lc-body[data-ContentPosition='Center'] div.content {\n\t\tmargin: 0 auto;\n\t}\n\t#lc-body[data-ContentPosition='Right'] div.content {\n\t\tfloat: right;\n\t}\n\n\t");
  4574. })(ContentPosition || (ContentPosition = {}));
  4575. ////////////////////////////////
  4576. // StandardizeNumberFormat.ts //
  4577. ////////////////////////////////
  4578. var StandardizeNumberFormat;
  4579. (function (StandardizeNumberFormat) {
  4580. // UTILITY
  4581. // Format a number string with a character separator (e.g. 1,000,000)
  4582. function delimit(str, char) {
  4583. return str.replace(/\B(?=(\d{3})+(?!\d))/g, char);
  4584. }
  4585. // Trim specified leading and trailing characters in a string
  4586. function trim(str, chars) {
  4587. var i = 0;
  4588. var l = str.length;
  4589. var start = 0;
  4590. var end = l - 1;
  4591. for (i = 0; i < l; i++) {
  4592. if (chars.indexOf(str.charAt(i)) == -1) {
  4593. start = i;
  4594. break;
  4595. }
  4596. }
  4597. for (i = l - 1; i >= 0; i--) {
  4598. if (chars.indexOf(str.charAt(i)) == -1) {
  4599. end = i;
  4600. break;
  4601. }
  4602. }
  4603. return str.slice(start, end + 1);
  4604. }
  4605. /**
  4606. * Get the first element in the body that is either
  4607. * a text node
  4608. * a node that does not have any children
  4609. */
  4610. function first_node(parent) {
  4611. var contents = parent.childNodes;
  4612. var i = 0;
  4613. for (var l = contents.length; i < l; i++) {
  4614. if (contents[i].nodeType == 3) {
  4615. // text node
  4616. // check if empty
  4617. if (contents[i].textContent.trim().length > 0)
  4618. return contents[i];
  4619. }
  4620. if (contents[i].nodeType == 1) {
  4621. // element node
  4622. var elem = contents[i];
  4623. if (elem.children.length == 0) {
  4624. // no more children
  4625. break;
  4626. }
  4627. // parent node - recurse to return its first node
  4628. return first_node(elem);
  4629. }
  4630. }
  4631. return contents[i];
  4632. }
  4633. // INITIALIZATION
  4634. var enabled = false;
  4635. var format = function (str) { return str; };
  4636. // Possible format functions
  4637. // (this is to avoid the use of anonymous functions, improving performance)
  4638. var FormatFuncs;
  4639. (function (FormatFuncs) {
  4640. function Commas(str) {
  4641. return delimit(str, ',');
  4642. }
  4643. FormatFuncs.Commas = Commas;
  4644. function Spaces(str) {
  4645. return delimit(str, ' ');
  4646. }
  4647. FormatFuncs.Spaces = Spaces;
  4648. function Periods(str) {
  4649. return delimit(str, '.');
  4650. }
  4651. FormatFuncs.Periods = Periods;
  4652. function None(str) {
  4653. return str;
  4654. }
  4655. FormatFuncs.None = None;
  4656. })(FormatFuncs || (FormatFuncs = {}));
  4657. ;
  4658. // Options
  4659. Options.addSelect({
  4660. label: 'STANDARDIZE NUMBER FORMAT',
  4661. options: ['Disabled', 'Spaces', 'Periods', 'Commas', 'None'],
  4662. section: 'Advanced',
  4663. help: 'Standardizes the number count in each message to a format of your choice. Also removes special formatting on the number.',
  4664. onchange: function () {
  4665. var val = this.val();
  4666. if (val == 'Disable') {
  4667. enabled = false;
  4668. return;
  4669. }
  4670. enabled = true;
  4671. format = FormatFuncs[val];
  4672. }
  4673. });
  4674. // EVENTS
  4675. // New update loaded
  4676. Update.loadedNew(function (data) {
  4677. if (!enabled)
  4678. return;
  4679. var first_elem = first_node(data.body_elem.get(0));
  4680. var $first_elem = $(first_elem);
  4681. var body = first_elem.textContent;
  4682. if (!body)
  4683. return;
  4684. // Detect number from string
  4685. // (This algorithm has a few problems, such as "2,000 2 GETS today"
  4686. // producing a detected number of "20002".)
  4687. var l = body.length;
  4688. var num = '';
  4689. var original_num = '';
  4690. var c;
  4691. for (var i = 0; i < l; i++) {
  4692. c = body.charAt(i);
  4693. if (c == '0' || c == '1' || c == '2' || c == '3' || c == '4' ||
  4694. c == '5' || c == '6' || c == '7' || c == '8' || c == '9') {
  4695. num += c;
  4696. original_num += c;
  4697. continue;
  4698. }
  4699. else if (c == ' ' || c == ',' || c == '.') {
  4700. // part of number styling preference
  4701. original_num += c;
  4702. continue;
  4703. }
  4704. else {
  4705. break;
  4706. }
  4707. }
  4708. if (num.length == 0)
  4709. num = null;
  4710. // Replace original_num in first_elem with num
  4711. if (num == null)
  4712. return;
  4713. first_elem.textContent = body.replace(trim(original_num, [' ', ',', '.']), format(num));
  4714. // Remove formatting of parents of first_elem by changing into span
  4715. // Also, headers are not wrapped in p, so replace those with p
  4716. var $parents = $first_elem.parentsUntil('p, div.md');
  4717. var parentsLen = $parents.length;
  4718. if (first_elem.nodeType == 1 && !$first_elem.is('p, div.md')) {
  4719. $parents = $parents.add($first_elem);
  4720. if (parentsLen == 0) {
  4721. // not a paragraph, but still no parent? must be a header (h1)
  4722. // convert to p
  4723. $first_elem.replaceWith('<p>' + first_elem.textContent + '</p>');
  4724. }
  4725. }
  4726. var $this;
  4727. $parents.each(function (index, element) {
  4728. $this = $(this);
  4729. if ($this.parent().is(data.body_elem)) {
  4730. // if the direct parent is the body element,
  4731. // replace to p instead,
  4732. // since this is definitely not a p itself
  4733. $this.replaceWith('<p>' + this.textContent + '</p>');
  4734. return;
  4735. }
  4736. $this.replaceWith('<span>' + this.textContent + '</span>');
  4737. });
  4738. });
  4739. })(StandardizeNumberFormat || (StandardizeNumberFormat = {}));
  4740.  
  4741. })();
Add Comment
Please, Sign In to add comment