BBCode in Text

Adds BBCode support to an antique forum.

  1. // ==UserScript==
  2. // @name BBCode in Text
  3. // @namespace http://mailerdaemon.home.comcast.net/
  4. // @include http://forums.secondlife.com/*
  5. // @version 0.2
  6. // @description Adds BBCode support to an antique forum.
  7. // ==/UserScript==
  8.  
  9. //configures how urls are displayed.
  10. const ERRORS = false;
  11. const url_start = 40;
  12. const url_end = 25;
  13. const url_len = url_start + url_end;
  14.  
  15. GM_addStyle = function(css){
  16. style = document.createElement("style");
  17. style.type = "text/css";
  18. style.innerHTML = css;
  19. document.getElementsByTagName('head')[0].appendChild(style);
  20. };
  21. //GM_log = function(text){};
  22.  
  23. GM_addStyle(".GMCode {border: 1px inset ; margin: 0px; padding: 6px; overflow: auto; width: auto; height: auto; text-align: left; font-family:monospace; } .GMCode br {display:none;}")
  24.  
  25. const chars = String.fromCharCode(38, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 254, 255, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 216, 217, 218, 219, 220, 221, 222, 8364, 34, 223, 60, 62, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 8364, 8226, 160, 161);
  26. const entities = new Array ('amp','agrave','aacute','acirc','atilde','auml','aring',
  27. 'aelig','ccedil','egrave','eacute','ecirc','euml','igrave',
  28. 'iacute','icirc','iuml','eth','ntilde','ograve','oacute',
  29. 'ocirc','otilde','ouml','oslash','ugrave','uacute','ucirc',
  30. 'uuml','yacute','thorn','yuml','Agrave','Aacute','Acirc',
  31. 'Atilde','Auml','Aring','AElig','Ccedil','Egrave','Eacute',
  32. 'Ecirc','Euml','Igrave','Iacute','Icirc','Iuml','ETH','Ntilde',
  33. 'Ograve','Oacute','Ocirc','Otilde','Ouml','Oslash','Ugrave',
  34. 'Uacute','Ucirc','Uuml','Yacute','THORN','euro','quot','szlig',
  35. 'lt','gt','cent','pound','curren','yen','brvbar','sect','uml',
  36. 'copy','ordf','laquo','not','shy','reg','macr','deg','plusmn',
  37. 'sup2','sup3','acute','micro','para','middot','cedil','sup1',
  38. 'ordm','raquo','frac14','frac12','frac34',
  39. 'euro','bull','nbsp','iexcl');
  40.  
  41. if(!String.prototype.trim) String.prototype.trim = function() { return this.replace(/^\s*/,'').replace(/\s*$/, ''); }
  42. if(!String.prototype.htmlUnescape) String.prototype.htmlUnescape = function(){
  43. var r = /&(#[0-9]*|[A-Za-z0-9]*);/;
  44. var i = this;
  45. var o = ""
  46. var p;
  47. do
  48. {
  49. if(!(p = r.exec(i)))
  50. return o + i;
  51. if(p.index > 0)
  52. o += i.slice(0, p.index);
  53. i = i.slice(p.index + p[0].length);
  54. if(p[1].charAt(0) == '#')
  55. o += String.fromCharCode(p[1].slice(1))
  56. else
  57. {
  58. var w = entities.indexOf(p[1]);
  59. o += (w != -1)?chars[w]:("&"+p[1]+";");
  60. }
  61. }while(1);
  62. }
  63. if(!String.prototype.htmlEscape) String.prototype.htmlEscape = function(){
  64. var i = this;
  65. for(var k = 0;k < chars.length; k++)
  66. i = i.replace(new RegExp(chars[k], "g"), "&"+entities[k]+";");
  67. return i;
  68. }
  69. if(!Array.prototype.insert) Array.prototype.insert = function( i, v ) {
  70. var b = this.splice( i );
  71. this.push(v);
  72. return this.concat( b );
  73. };
  74.  
  75. var count = 0;
  76. var base = document.URL;
  77. count = base.indexOf("?");
  78. if(count >= 0) base = base.substring(0, count);
  79. base = base.substring(0, base.lastIndexOf("/")+1);
  80. var pattern = /(?:\[(?:[\/\\]?)(url|email|thread|post|indent|code|php|font|size|color|b|u|i|right|left|center|highlight|list|img|[*])(?:|=[^\]]*)\]|((?:https?|mms|rstp|secondlife|ftp|mailto):[^\s\[]*)|((?:[a-z0-9.\-]{3,}\.(?:com|net|org|co\.uk)|(?:(?:0x[0-9a-f]{1,8}|[0-9]{1,3})\.){3}(?:0x[0-9a-f]{1,8}|[0-9]{1,3}))(?:(?:\:[0-9]+|)\/[^\s\[]*|(?=[^0-9a-z\-.]|$))))/im;
  81. var split_pattern = /\[([\/\\]?)([^=\]]*?)(?:\s*=\s*"?([^\]"]*)"?\s*|)\]/im;
  82. count = 0;
  83. var calls = 100000;//stops a race condition that doesn't happen anymore.
  84.  
  85. start();
  86.  
  87. function start()
  88. {
  89. var i, j, divs;
  90. var xpath = "//td[@class='thead']/../../tr[2]/td[position()=2 and @class='alt1']"+"|"+//thread
  91. "//tr[contains(td, 'Preview')]/td[@class='tcat']/../../tr[2]"+"|"+//edit - preview
  92. "//td[@class='thead']/../../tr/td[position()=2 and @class='alt2']/..";//topic review
  93. var res = document.evaluate(xpath, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  94. for (i = 0; divs = res.snapshotItem(i); ++i) {
  95. var t = divs.childNodes.length;
  96. // GM_log(divs.nodeName + "\nChild Count = " + t+ "\n"+divs.innerHTML);
  97. for (t=0; t<divs.childNodes.length; t++){
  98. parse(divs.childNodes[t]);
  99. }
  100. }
  101. if(i > 0 || count > 0)
  102. {
  103. // GM_log(document.URL.concat("\nHad "+count+" broken tag"+((count != 1)?"s":"") + " in "+i +" messages. (debug = " + calls + " )"));
  104. // GM_log(calls);
  105. }
  106. }
  107.  
  108. function parse(element, only, nourl)
  109. {
  110. var table = new Array(0);
  111. var urls = new Array(0);
  112. var badurls = new Array(0);
  113. var t = element.firstChild;
  114. for (;t != null; t = t.nextSibling){
  115. if(--calls < 0)
  116. return;
  117. // GM_log("nodeName = "+t.nodeName+"\nnodeType = "+t.nodeType );
  118. if(t.nodeType == 3)
  119. {
  120. var nodes = pattern;
  121. var p = pattern.exec(t.nodeValue);
  122. if(p != null)
  123. {
  124. // GM_log(p.length+"\n"+p[0]+"\n"+p[1]+"\n"+p[2]+"\n"+p[3]);
  125. // if(p[1] != null || p[2] != null || p[3] != null)
  126. {
  127. var m=document.createTextNode(p[0]);
  128. var n=document.createTextNode(t.nodeValue.substring(p.index + p[0].length));
  129. t.nodeValue = t.nodeValue.substring(0,p.index);
  130. insertAfter(m, t);
  131. insertAfter(n, m);
  132. ++count;
  133. }
  134. if(p[1] != null)
  135. table.push(m);
  136. else if(p[2] != null && !nourl)
  137. urls.push(m);
  138. else if(p[3] != null && !nourl)
  139. badurls.push(m);
  140. t = t.nextSibling;
  141. }
  142. }
  143. else if(t.nodeType == 1)
  144. {
  145. if(t.nodeName != "TEXTAREA" && t.nodeName != "INPUT" && t.nodeName != "PRE")
  146. parse(t, only, nourl);
  147. }
  148. // else
  149. // GM_log("nodeName = "+t.nodeName+"\nnodeType = "+t.nodeType );
  150. }
  151. // if(element.innerHTML)
  152. // GM_log("Count = "+table.length+"\n"+table+"\n" + element.innerHTML);
  153. if(table.length > 0)
  154. tag(table, only);
  155. var i;
  156. for(i = 0; i < urls.length; i++)
  157. {
  158. var t = urls[i];
  159. var v = t;
  160. while(v = v.parentNode)
  161. {
  162. if(v.nodeName == "A" || v.nodeName == "PRE")//keep it honest
  163. break;
  164. }
  165. if(v == null && t.parentNode)
  166. {
  167. // GM_log(t.nodeValue);
  168. var m=document.createElement("A");
  169. m.href = (m.innerHTML = t.nodeValue).htmlUnescape();
  170. if(m.innerHTML.length > url_len)
  171. m.innerHTML = m.innerHTML.slice(0,url_start)+"..."+m.innerHTML.slice(m.innerHTML.length-url_end);
  172. t.parentNode.replaceChild(m, t);
  173. }
  174. }
  175. for(i = 0; i < badurls.length; i++)
  176. {
  177. var t = badurls[i];
  178. var v = t;
  179. while(v = v.parentNode)
  180. {
  181. if(v.nodeName == "A" || v.nodeName == "PRE")//keep it honest
  182. break;
  183. }
  184. if(v == null && t.parentNode)
  185. {
  186. // GM_log(t.nodeValue);
  187. var m=document.createElement("A");
  188. m.href = "http://"+(m.innerHTML = t.nodeValue).htmlUnescape();
  189. if(m.innerHTML.length > url_len)
  190. m.innerHTML = m.innerHTML.slice(0,url_start)+"..."+m.innerHTML.slice(m.innerHTML.length-url_end);
  191. t.parentNode.replaceChild(m, t);
  192. }
  193. }
  194. badurls = urls = table = null;
  195. }
  196.  
  197. /*
  198. function juggle(table)
  199. {
  200. var t;
  201. var map = new Object();
  202. for (t=0; t<table.length; t++){
  203. var type = split_pattern.exec(table[t].nodeValue);
  204. if(type[1] == "")
  205. {
  206. if(map[type[2].toLowerCase()])
  207. ++map[type[2].toLowerCase()];
  208. else
  209. map[type[2].toLowerCase()] = 1;
  210. }
  211. else
  212. {
  213. if(map[type[2].toLowerCase()])
  214. --map[type[2].toLowerCase()];
  215. else
  216. map[type[2].toLowerCase()] = -1;
  217. }
  218. }
  219. map = null;
  220. return table;
  221. }
  222. */
  223. function log(table, sep, msg_a, msg_b)
  224. {
  225. var kw = "";
  226. Array.forEach(table, function(item) {if(kw !="") kw +=sep;kw += item.nodeValue;});
  227. // GM_log(msg_a+kw+msg_b);
  228. }
  229.  
  230. function oc(a)
  231. {
  232. if(a && a.length)
  233. {
  234. var o = {};
  235. for(var i=0;i<a.length;i++)
  236. {
  237. o[a[i]]='';
  238. }
  239. return o;
  240. }
  241. return null;
  242. }
  243.  
  244. function tag(table, only)
  245. {
  246. // log(table, "\n", "tags = [\n","\n]");
  247. var t;
  248. var z = oc(only);
  249. for (t=0; t<table.length; t++){
  250. if(--calls < 0)
  251. return;
  252. var type = split_pattern.exec(table[t].nodeValue);
  253. if(type)//make sure it is infact a valid tag.
  254. {
  255. if(type[1] == "")
  256. {
  257. var c = 1;
  258. var m = t;
  259. var r = type[2].toLowerCase();
  260. var dtype;
  261. var k = table.length - 1;
  262. if(z && !(r in z));//silently ignore missing tags
  263. else if(r == "*")
  264. {
  265. while(t < k && c != 0)
  266. {
  267. if(--calls < 0)
  268. return;
  269. dtype = split_pattern.exec(table[++t].nodeValue);
  270. if(dtype)
  271. {
  272. var dt = dtype[2].toLowerCase();
  273. if(dt == "list")
  274. {
  275. if(dtype[1] == "")
  276. c++;
  277. else
  278. c--;
  279. }
  280. else if(dt == "*" && c == 1)
  281. c = 0;
  282. }
  283. }
  284. if(c > 0)
  285. t++;
  286. }
  287. else
  288. {
  289. while(t < k && c != 0)
  290. {
  291. if(--calls < 0)
  292. return;
  293. dtype = split_pattern.exec(table[++t].nodeValue);
  294. if(dtype)
  295. {
  296. if(dtype[2].toLowerCase() == r)
  297. {
  298. if(dtype[1] == "")
  299. c++;
  300. else
  301. c--;
  302. }
  303. }
  304. }
  305. if(c > 0 && (r == "url" || r =="img") && type[3] && type[3] != "")
  306. {//some idiot didn't close thier URL/IMG tag, *rolls eyes*
  307. // log(table, ", ", "before: " + table.length+"\n")
  308. table.splice(t = m+1, 0, insertAfter(document.createTextNode("[/"+r+"]"), table[m]));
  309. // log(table, ", ", "after: " + table.length+"\n")
  310. insertAfter(document.createTextNode(type[3]), table[m]);
  311. k+=1;//adjust the end...
  312. type[3]="";//makes it work harder
  313. c = 0;
  314. }
  315. }
  316. // if(table.length > t)
  317. // GM_log("( "+(t - m)+", " + c + " ) = " + type[0] +" && " + split_pattern.exec(table[t].nodeValue)[0]);
  318. // else
  319. // GM_log("( "+(t - m)+", " + c + " ) = " + type[0] +" && last");
  320. if(c == 0 || r == "*")
  321. {
  322. var nodes = new Array(0);
  323. var p = null;
  324. var q = null;
  325. if(r == "url" || r == "thread" || r == "post" || r =="email")
  326. {
  327. p = document.createElement("a");
  328. var d = "";
  329. if(type[3] != null)
  330. d = type[3].replace(/ /g,"").htmlUnescape();
  331. if(r == "thread")
  332. p.href = (base + "showthread.php?t=" + d);
  333. else if(r == "post")
  334. p.href = (base + "showthread.php?p=" + d);
  335. else if(d != "")
  336. {
  337. if(r == "email")
  338. p.href = (d.trim().toLowerCase().indexOf("mailto:") != 0)?"mailto:"+d:d;
  339. else if(r == "url")
  340. {
  341. var d1 = d.trim();
  342. var d3 = d1.indexOf("/");
  343. var d4 = d1.indexOf(":");
  344. var d5 = d1.indexOf("?");
  345. if(d4 == -1 || (d4 > d3 && d3 != -1))
  346. {
  347. if(d5 == -1 || d3 != -1)
  348. p.href = "http://"+d;
  349. else
  350. p.href = base + d;
  351. }
  352. else
  353. p.href = d;
  354. }
  355. }
  356. }
  357. else if(r == "color" || r == "size" || r == "font")
  358. {
  359. p = document.createElement("font");
  360. if(r == "color")
  361. p.color = type[3];
  362. else if(r == "size")
  363. p.size = type[3];
  364. else if(r == "font")
  365. p.face = type[3];
  366. }
  367. else if(r == "right" || r == "left" || r == "center")
  368. {
  369. p = document.createElement("div");
  370. if(r == "right")
  371. p.align = "right";
  372. else if(r == "left")
  373. p.align = "left";
  374. else if(r == "center")
  375. p.align = "center";
  376. }
  377. else if(r == "u" || r == "b" || r == "i")
  378. {
  379. p = document.createElement(r);
  380. }
  381. else if(r == "highlight")
  382. {
  383. p = document.createElement("span");
  384. p.className = "highlight";
  385. }
  386. else if(r == "indent")
  387. {
  388. p = document.createElement("blockquote");
  389. q = document.createElement("div");
  390. p.appendChild(q);
  391. }
  392. else if(r == "code" || r == "php")
  393. {
  394. p = document.createElement("div");
  395. p.style.margin="5px 20px 20px";
  396. q = document.createElement("div");
  397. q.className = "smallfont";
  398. q.style.marginBottom="2px";
  399. q.appendChild(document.createTextNode("Code:"));
  400. p.appendChild(q);
  401. q = document.createElement("pre");
  402. q.className = "alt2 GMCode";
  403. q.dir = "ltr";
  404. p.appendChild(q);
  405. }
  406. else if(r == "list")
  407. {//<ol type="1"><li>list item 1</li><li>list item 2</li></ol>
  408. if(type[3] != "" && type[3] != null)
  409. {
  410. p = document.createElement("ol");
  411. p.type=type[3];
  412. }
  413. else
  414. {
  415. p = document.createElement("ul");
  416. }
  417. }
  418. else if(r == "img")
  419. {
  420. p = document.createElement("img");
  421. q = document.createElement("span");
  422. }
  423. else if(r == "*")
  424. {
  425. p = document.createElement("li");
  426. }
  427. if(q == null)
  428. q = p;
  429. if(p != null)
  430. {
  431. var h = table[m];
  432. var w = h.parentNode;
  433. var n = h.nextSibling;
  434. var s = null;
  435. if(table.length > t)
  436. s = table[t];
  437. while(n != s && n != null)
  438. {
  439. y = n.nextSibling;
  440. child = w.removeChild(n);
  441. if(child.nodeName != "BR" || (r != "code" && r != "php"))
  442. q.appendChild(child);
  443. if(child.nodeName == "#text")
  444. child.nodeValue = child.nodeValue.replace(/([\S]{50,52}) /gm,"$1");
  445. n = y;
  446. if(--calls < 0)
  447. return;
  448. }
  449. if(s != null)
  450. {
  451. var rtype = split_pattern.exec(s.nodeValue);
  452. if(rtype[1] != "" && rtype[2].toLowerCase() == r /*/&& w/**/)//catch for lists ~_~
  453. w.removeChild(s);
  454. }
  455. if(r == "code" || r == "php")//keeps it from trying to parse code and php stuff.
  456. {
  457. //FIXME: make the replacement smarter, i'm too lazy.
  458. q.innerHTML = q.innerHTML.replace(/\t/g," ");
  459. //if(r != "code")//oddly enabling this test borks things, not exactly sure what causesit.
  460. m = t;//no internal parsing
  461. if(r == "code")//this is the wrong solutions but it works
  462. parse(q, ["i", "b", "u", "color", "highlight", "url", "thread", "post", "email"], true);
  463. }
  464. else if(r == "img")
  465. {
  466. if("" == (p.src = q.textContent.trim().htmlUnescape()))
  467. p = q;
  468. m = t;//no internal parsing
  469. }
  470. else if((r == "url" || r == "email" || r == "post" || r == "thread") && (type[3]=="" || type[3] == null))
  471. {
  472. s = q.textContent.trim().replace(/ /g,"").htmlUnescape();
  473. if(r == "email")
  474. q.href = (s.toLowerCase().indexOf("mailto:") != 0)?"mailto:"+s:s;
  475. else if(r == "url")
  476. q.href = ((s+"/:").indexOf(":") > s.indexOf("/"))?"http://"+s:s;
  477. else
  478. p.href += s;
  479. if(s.length > url_len)
  480. q.innerHTML = (s.slice(0,url_start)+"..."+s.slice(s.length-url_end)).htmlEscape();
  481. else
  482. q.innerHTML = q.textContent;//strip the html from it, it should be unformated.
  483. m = t;//no internal parsing
  484. }
  485. /*/if(w)/**/
  486. w.replaceChild(p,h);
  487. }
  488. }
  489. else //if(c > 0 && r != "*")
  490. {
  491. ++t;
  492. if(ERRORS)
  493. {
  494. // GM_log("tag error = " + type[0]);
  495. // log(table, "\n", "tags = [\n","\n]");
  496. }
  497. }
  498. if(m+1 < t)
  499. tag(table.slice(m+1, t));
  500. if((r == "*" && c == 0))// || (c == 1 && r != "*"))
  501. --t;
  502. }
  503. else if(ERRORS)
  504. {
  505. // GM_log("tag error = " + type[0]);
  506. // log(table, "\n", "tags = [\n","\n]");
  507. }
  508. }
  509. }
  510. }
  511.  
  512. function insertAfter(insert, after)
  513. {
  514. return after.parentNode.insertBefore(insert, after.nextSibling);
  515. }