Userscripts Forums bbCode
By Mikado
—
Last update Jul 4, 2008
—
Installed
97 times.
// ==UserScript==
// @name Userscripts Forums bbCode
// @namespace http://userscripts.org/users/31647
// @description User-friendly bbCode with keyboard shortcuts.
// @include http://userscripts.org/forums/*/topics/*
// @include http://userscripts.org/scripts/show/*
// ==/UserScript==
// here you can add/modify CTRL+key shortcuts
// keep in mind that "w" is reserved for hyperlinks ([url=...][/url])
var bbTags = {
b: ['[b]', '[/b]'],
i: ['[i]', '[/i]'],
u: ['[u]', '[/u]'],
p: ['[img]', '[/img]'],
q: ['[quote]', '[/quote]'],
d: ['[code]', '[/code]']
};
// nothing interesting below
function bb2html(text, rec) {
if (!rec) {
var chars = {'"': '"', "'": ''', '<': '<', '>': '>', '&': '&'}, replacer = function(s,m){return chars[s]};
text = text.replace(/^\s+|\s+$/g, '').replace(/[<>&"']/g, replacer);
}
var html = rec ? '' : '<p>', tagRE = /\[([^\]]+)\]|(\n)+/, i, append = '', endBlockRE = /<\/(pre|blockquote)>$/;
while ((i = text.search(tagRE)) != -1) {
var match = RegExp.$2 || RegExp.$1, tag = match.split('=')[0], endtag = locateClosingTag(text, i, tag, 0);
switch (tag) {
case 'quote':
if (!i) html = html.replace(/(<p>|<br>)+$/, '');
var post = match.match(/\d+/);
if (post) {
var userlink = document.evaluate('td/span[@class="fn"]/a', document.getElementById(post + '-row'), null, 8, null).singleNodeValue;
}
if (endtag.success) {
html += text.substr(0, i) + '<blockquote>' + (post && userlink ? '<p><a href="' + userlink.pathname + '"><b>' + userlink.textContent + '</b></a> <a href="#' +
(location.pathname.substr(0, 8) == '/forums/' ? 'posts-' : 'comment-') + post + '">wrote</a>:<p>' : '<p>') + bb2html(text.substring(i + match.length + 2, endtag.value), true) + '</blockquote><p>';
text = text.substr(endtag.value + 8);
} else {
html += text.substr(0, i) + '<blockquote><p>';
text = text.substr(i + match.length + 2);
while (endtag.value--) append = '</blockquote>' + append;
}
break;
case 'code':
var content = text.substring(i + 6, endtag.success ? endtag.value : Infinity), pref = !i || text.substr(0, i).match(/\n\s*$/),
post = !endtag.success || text.length <= endtag.value + 7 || text.substr(endtag.value + 7).match(/^\s*\n/),
usePre = (content.indexOf('\n') > 0) || (pref && post);
if (usePre && !i) html = html.replace(/(<p>|<br>)+$/, '');
if (!endtag.success) while (endtag.value--) append = (usePre ? '</pre>' : '</code>') + append;
html += text.substr(0, i) + (usePre ? '<pre>' : '<code>') + content + (endtag.success ? (usePre ? '</pre><p>' : '</code>') : '');
text = endtag.success ? text.substr(endtag.value + 7) : '';
break;
case 'url':
if (!endtag.success) return null;
var linktext = text.substring(i + match.length + 2, endtag.value), href = match.split('=')[1];
if (href == linktext) html += text.substr(0, i) + href;
else html += text.substr(0, i) + '<a href="' + href + '">' + bb2html(linktext, true) + '</a>';
text = text.substr(endtag.value + 6);
break;
case 'img':
var imgurl;
if (!endtag.success) {
endtag.value = text.substr(i).search(/\s/);
if (endtag.value < 0) endtag.value = Infinity;
else endtag.value += i;
imgurl = text.substring(i + 5, endtag.value);
endtag.value -= 6;
} else {
imgurl = text.substring(i + 5, endtag.value);
}
var pref = !i || text.substr(0, i).match(/\n\s*$/), post = text.length <= endtag.value + 6 || text.substr(endtag.value + 6).match(/^\s*\n/),
addBlock = pref && post ? '<p>' : '';
if (addBlock && !i) html = html.replace(/(<p>|<br>)+$/, '');
html += text.substr(0, i) + addBlock + '<img src="' + imgurl + '">' + addBlock;
text = text.substr(endtag.value + 6);
break;
case '\n':
match = RegExp.lastMatch.length;
if (!i && html.match(/(<p>|<br>)$/)) ;
else if (match == 1) html += text.substr(0, i) + '<br>';
else html += text.substr(0, i) + '<p>';
text = text.substr(i + match);
break;
default:
if (endtag.success) {
html += text.substr(0, i) + '<' + tag + '>' + bb2html(text.substring(i + match.length + 2, endtag.value), true) + '</' + tag + '>';
text = text.substr(endtag.value + tag.length + 3);
} else {
html += text.substr(0, i) + '<' + tag + '>';
text = text.substr(i + match.length + 2);
while (endtag.value--) append = '</' + tag + '>' + append;
}
}
}
return (html + text + append).replace(/(<p>|<br>)+$/, '');
}
function html2bb(text, rec) {
if (!rec) text = text.replace(/<!--(.|\n)*-->/g, '').replace(/^\s+|\s+$|<\/p>/g, '');
text = text.replace(/^(<p>|<br>)+/, '');
var bbCode = '', tagRE = /<([^>]+)>/, i,
chars = {quot: '"', apos: "'", lt: '<', gt: '>', amp: '&'}, replacer = function(s,m){return chars[m]};
while ((i = text.search(tagRE)) != -1) {
var match = RegExp.$1, tag = match.split(' ')[0], endtag = locateClosingTag(text, i, tag, 1).value;
switch (tag) {
case 'blockquote':
if (text.substr(i + 12).match(/^<p><a.*#posts-(\d+).*wrote<\/a>:<p>/)) {
bbCode += text.substr(0, i) + '[quote' + ('=#' + RegExp.$1) + ']' + html2bb(text.substring(text.indexOf('<p>', i + 20), endtag), true) + '[/quote]\n';
} else {
bbCode += text.substr(0, i) + '[quote]' + html2bb(text.substring(i + match.length + 2, endtag), true) + '[/quote]';
}
text = text.substr(endtag + 13);
break;
case 'pre':
var content = text.substring(i + tag.length + 2, endtag);
bbCode += text.substr(0, i) + (text.substr(0, i).match(/\n\s*$/) ? '' : '\n') + '[code]' + html2bb(content, true) + '[/code]\n';
text = text.substr(endtag + 6);
break;
case 'br':
var pref = text.charAt(i - 1) == '\n', post = text.charAt(i + 4) == '\n';
bbCode += text.substr(0, pref ? i - 1 : i) + '\n';
text = text.substr(i + (post ? 5 : 4));
break;
case 'p':
var pref = text.substr(0, i).match(/\n*$/)[0].length, post = text.substr(i + 3).match(/^\n*/)[0].length;
bbCode += text.substr(0, i - pref) + '\n\n';
text = text.substr(i + 3 + post);
break;
case 'a':
var linktext = text.substring(i + match.length + 2, endtag), href = match.match(/href="([^"]+)"/)[1];
if (href == linktext || href.substr(0, linktext.length - 3) + '...' == linktext) bbCode += text.substr(0, i) + href;
else bbCode += text.substr(0, i) + '[url=' + href + ']' + html2bb(linktext, true) + '[/url]';
text = text.substr(endtag + 4);
break;
default:
var content = text.substring(i + tag.length + 2, endtag);
bbCode += text.substr(0, i) + (content && '[' + tag + ']' + html2bb(content, true) + '[/' + tag + ']');
text = text.substr(endtag + tag.length + 3);
}
}
return rec ? bbCode + text : (bbCode + text).replace(/&(quot|apos|lt|gt|amp);/g, replacer);
}
function locateClosingTag(str, start, tag, mode) { // mode: 0 = bbCode, 1 = html
var openTag = (mode ? '<' : '[') + tag, closeTag = (mode ? '</' : '[/') + tag + (mode ? '>' : ']'), level = 0, status = {success: true, value: ''};
start += openTag.length;
while (true) {
var oi = str.indexOf(openTag, start), ci = str.indexOf(closeTag, start);
if (ci == -1) {
status.success = false;
status.value = level;
return status;
}
if (oi != -1 && oi < ci) { level++; start = oi + openTag.length; }
else if (level) { level--; start = ci + closeTag.length; }
else {
status.value = ci;
return status;
}
}
}
function inputHandler(e) {
if (e.keyCode == 27) {
if (this.id == 'GM_post_body')
document.getElementById('reply').style.display = 'none';
else
location.assign('javascript:EditForm.cancel();');
e.preventDefault();
return;
}
var key = String.fromCharCode(e.charCode), cv = this.value, cs = this.selectionStart, ce = this.selectionEnd;
if (e.ctrlKey) {
if (key in bbTags) {
wrapText(this, bbTags[key]);
e.preventDefault();
}
else if (key == 'w') {
var sel = cv.substring(cs, ce);
if (sel && sel.match(/^(http|ftp):\/\//)) {
wrapText(this, '[url=', '][/url]');
this.selectionStart = this.selectionEnd = this.selectionEnd - 6;
}
else wrapText(this, '[url=]', '[/url]');
e.preventDefault();
}
}
else if (e.keyCode == 9) {
e.preventDefault();
wrapText(this, ' ', '');
}
}
function wrapText(field, p1, p2, noInclude) {
if (typeof p1 == 'object') p2 = p1[1], p1 = p1[0];
var cv = field.value, cs = field.selectionStart, ce = field.selectionEnd, ct = field.scrollTop,
insertStr = p1 + cv.substring(cs, ce) + p2, paramPos = p1.indexOf('=]') + 1;
field.value = cv.substr(0, cs) + insertStr + cv.substr(ce);
field.selectionStart = field.selectionEnd = cs + (paramPos || (cs == ce ? p1.length : insertStr.length));
field.scrollTop = ct;
}
function alternateInput(inputArea) {
inputArea.style.display = 'none';
var bbArea = document.createElement('textarea');
bbArea.setAttribute('rows', '8');
bbArea.id = 'GM_' + inputArea.id;
bbArea.value = html2bb(inputArea.value);
inputArea.parentNode.insertBefore(bbArea, inputArea);
bbArea.addEventListener('keypress', inputHandler, false);
inputArea.wrappedJSObject.focus = function() { bbArea.focus(); };
inputArea.form.elements.namedItem('commit').addEventListener('click', function(e) {
inputArea.value = bb2html(bbArea.value);
// e.preventDefault();
// GM_openInTab('data:text/html,' + encodeURI(inputArea.value));
}, false);
}
//replyarea.parentNode.parentNode.cells[1].innerHTML = ...
// quote links
function quote(e) {
e.preventDefault();
var area = document.getElementById('GM_edit_post_body'), edit = document.getElementById('edit'), sel = window.getSelection();
if (!area || (edit.style.display == 'none')) {
if (reply.style.display == 'none') location.assign('javascript:ReplyForm.init();');
area = document.getElementById('GM_post_body');
}
var td = this.parentNode.parentNode, name = document.evaluate('span[@class="fn"]/a', td, null, 8, null).singleNodeValue, post = td.parentNode.cells[1];
if (isChild(sel.anchorNode, post) && isChild(sel.focusNode, post) && sel.toString()) post = sel.toString().replace(/</g, '<').replace(/>/g, '>');
else post = html2bb(post.innerHTML);
wrapText(area, '[quote=#' + this.hash.match(/\d+/) + ']' + post + '[/quote]', '');
}
if (location.pathname.match(/\/new$/)) {
alternateInput(document.getElementById('topic_body'));
with (document.getElementById('GM_topic_body'))
rows = 12, cols = 40, style.width = '99%';
} else {
var posts = document.getElementById('content').getElementsByTagName('table')[0].rows,
replyarea = document.getElementById('post_body'), reply = document.getElementById('reply');
for (var i = 0; i < posts.length; i += 2) {
with (posts[i].cells[0].appendChild(document.createElement('p'))) {
className = 'edit';
innerHTML = '<a href="#posts-' + posts[i].id.match(/\d+/) + '" class="utility">Quote</a>';
firstChild.addEventListener('click', quote, false);
}
}
alternateInput(replyarea);
document.getElementById('content').addEventListener('DOMNodeInserted', function(e) {
if (e.originalTarget.id == 'edit') alternateInput(document.getElementById('edit_post_body'));
}, false);
}
function isChild(c, p) {
do if (c == p) return true; while (c = c.parentNode);
return false;
}