· 9 years ago · Aug 16, 2017, 11:06 PM
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
25var VERSION = 'v1.5.3';
26// Client's username
27var USER = $('#header .user a[href]').html();
28var chu = 0;
29// Thread ID
30var 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)
42var 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)
162var 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 'Æ', 'Á', 'Â', 'À', 'Α', 'Å',
270 'Ã', 'Ä', 'Β', 'Ç', 'Χ', '‡', 'Δ',
271 'Ð', 'É', 'Ê', 'È', 'Ε', 'Η', 'Ë',
272 'Γ', 'Í', 'Î', 'Ì', 'Ι', 'Ï', 'Κ',
273 'Λ', 'Μ', 'Ñ', 'Ν', 'Œ', 'Ó', 'Ô',
274 'Ò', 'Ω', 'Ο', 'Ø', 'Õ', 'Ö', 'Φ',
275 'Π', '″', 'Ψ', 'Ρ', 'Š', 'Σ', 'Þ', 'Τ',
276 'Θ', 'Ú', 'Û', 'Ù', 'Υ', 'Ü', 'Ξ',
277 'Ý', 'Ÿ', 'Ζ', 'á', 'â', '´', 'æ',
278 'à', 'ℵ', 'α', '&', '∧', '∠', ''',
279 'å', '≈', 'ã', 'ä', '„', 'β', '¦',
280 '•', '∩', 'ç', '¸', '¢', 'χ', 'ˆ',
281 '♣', '≅', '©', '↵', '∪', '¤', '⇓',
282 '†', '↓', '°', 'δ', '♦', '÷',
283 'é', 'ê', 'è', '∅', ' ', ' ',
284 'ε', '≡', 'η', 'ð', 'ë', '€', '∃',
285 'ƒ', '∀', '½', '¼', '¾', '⁄',
286 'γ', '≥', '>', '⇔', '↔', '♥', '…',
287 'í', 'î', '¡', 'ì', 'ℑ',
288 '∞',
289 '∫', 'ι', '¿', '∈', 'ï', 'κ', '⇐',
290 'λ', '⟨', '«', '←', '⌈', '“', '≤',
291 '⌊', '∗', '◊', '‎', '‹', '‘', '<',
292 '¯', '—', 'µ', '·', '−', 'μ', '∇',
293 ' ', '–', '≠', '∋', '¬', '∉', '⊄', 'ñ',
294 'ν', 'ó', 'ô', 'œ', 'ò', '‾', 'ω',
295 'ο', '⊕', '∨', 'ª', 'º', 'ø', 'õ',
296 '⊗', 'ö', '¶', '∂', '‰', '⊥', 'φ',
297 'π', 'ϖ', '±', '£', '′', '∏', '∝',
298 'ψ', '"', '⇒', '√', '⟩', '»', '→',
299 '⌉', '”', 'ℜ', '®', '⌋', 'ρ', '‏',
300 '›', '’', '‚', 'š', '⋅', '§', '­',
301 'σ', 'ς', '∼', '♠', '⊂', '⊆', '∑',
302 '¹', '²', '³', '⊃', '⊇', 'ß', 'τ',
303 '∴', 'θ', 'ϑ', ' ', 'þ', '˜',
304 '×', '™', '⇑', 'ú', '↑', 'û', 'ù',
305 '¨', 'ϒ', 'υ', 'ü', '℘', 'ξ', 'ý',
306 '¥', 'ÿ', 'ζ', '‍', '‌',
307 ];
308 /**
309 * According to the OWASP rules:
310 *
311 * & --> &
312 * < --> <
313 * > --> >
314 * " --> "
315 * ' --> ' ' is not recommended
316 * / --> / 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 = ["", """, "&", "'", "/", "<", ">", "" /* 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 += '&';
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 += ''';
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 <ol> or <ul>.
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 += "<ul>" + text.s + "</ul>"
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 += "<li>" + text.s + "</li>"
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 += "<p>" + text.s + "</p>";
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 += "<tr>" + text.s + "</tr>";
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 += "<td>" + text.s + "</td>";
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 += "<a href=\""+ text.s + "\">" + text.s + "</a>";
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 <strong> 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 <em> 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 = "<img src=\"" + link.s + "\" title=\"" + title.s + "\" alt=\"" + alt.s + "\"/>";"
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 += "<br/>";
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 = "<a href=\"" + link.s + "\" title=\"" + title.s + "\">" + content.s + "</a>";
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 <em> and <strong> 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
3729var 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/////////////////
3781var 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///////////////
3812var 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////////////////
3828var 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///////////////
4015var 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//////////////////
4132var 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/////////////////////////
4156var 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//////////////////////////
4282var 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////////////////////
4317var 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////////////////////////////
4349var 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/////////////////////////////
4482var 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////////////////////////
4501var 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/////////////////////////
4525var 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////////////////////////
4557var 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////////////////////////////////
4578var 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})();