<script type="text/javascript">
(function(){
/*
TODO:
1. In IE7, a strange document selection remains (harmless) after
+ selection-less document quote.
2. Keybindings ?
3. Configuration ?
4. Nicer looking error dialog.
*/
// browser identification variables
// (some unused for now, but will inevitably be used in future bug
+-fixes)
//
var isOpera = !!window.opera; /
+/ is the browser Opera ?
var isSafari = (typeof(window.devicePixelRatio) != 'undefined'); /
+/ is the browser Safari ?
var isIE = /*@cc_on!@*/false; /
+/ is the browser Internet Explorer ?
var isIE7 = false /*@cc_on || @_jscript_version >= 5.7 @*/; /
+/ is the browser IE version 7 (or later) ?
// filter a list (for older javascript implmentations)
var filter = function (l,f) {
var a = `[`];
for (var i = 0; i<l.length; i++) if (f(l`[i`])) a.push(l`[i`]);
return a;
};
// check a node has the given className
var hasClass = function(node, cname) {
var cnre = new RegExp('\\b'+cname+'\\b');
return cnre.test(""+node.className) ? 1 : 0;
};
// does this node have a parentNode (somewhere up the chain) with
+the given class
var hasParentWithClass = function (node, cname) {
var pnode = node.parentNode;
if (!pnode || (typeof(pnode.className)!='string')) return false;
return hasClass(pnode, cname) || hasParentWithClass(pnode, cname
+);
};
// cross-platform event handler attacher
var addEvent = !isIE
? function (obj, type, fn) { obj.addEventListener(type, fn, fals
+e); }
: function (obj, type, fn) {
obj`['e'+type+fn`] = fn;
obj`[type+fn`] = function(){ obj`['e'+type+fn`](window.event
+); };
obj.attachEvent('on'+type,obj`[type+fn`]);
};
// translate entity-encoded html back to original form
var unescapeHTML = function (html) {
return html.replace('<', '<')
.replace('>', '>')
.replace('"', '"')
.replace('&', '&');
};
// save/restore scroll offsets for specified elements... this help
+s in IE, but is overridden by the 'focus' on the textarea if that com
+es into play.
var saveScrolls, restoreScrolls;
(function(){
var _elems;
saveScrolls = function(list) { _elems = `[`]; for (var i=0; i<l
+ist.length; i++) _elems.push(`[list`[i`],+list`[i`].scrollTop`]); };
restoreScrolls = function() { for (var i=0; i<_elems.length; i++
+) _elems`[i`]`[0`].scrollTop = _elems`[i`]`[1`]; };
})();
// get top-level user selection object
var getSel = window.getSelection
? function () { return window.getSelection(); }
: function () {
if (!document.selection) return null;
return document.selection.createRange();
};
// convert selection object to (Text/W3C)Range object
var sel2Range = isIE
? function (sel) { return sel; }
: function (sel) {
if (sel.getRangeAt) return sel.getRangeAt(0);
var r = document.createRange();
r.setStart(sel.anchorNode,sel.anchorOffset);
r.setEnd(sel.focusNode,sel.focusOffset);
return r;
};
// get HTML of document selection
var getDocSelHTML = isIE
? function (s) { return s.htmlText }
: function (s) {
var t = document.createElement('DIV');
t.appendChild(sel2Range(s).cloneContents());
return t.innerHTML;
};
// get inner HTML of selected fragment
var getSelHTML = function () {
var s = getSel();
// nothing selected ?
if (((''+s.htmlText) == '') || !s || s == '') {
// get the whole parent post
var previews = filter(document.getElementsByTagName('DIV'), fu
+nction(n){ return n.className == 'preview'; });
if (!previews || !previews.length) return '';
return previews`[0`].innerHTML.replace(/\s*<hr\s*\/?>\s*<p>\s*
+<i>In reply to<\/i>`[\s\S`]+/i,'');
} else {
// otherwise, get only the (document) selection
var inCode = selectionCompletelyInsideCodeBlock(s);
return (inCode ? '<c>\n' : '') +
getDocSelHTML(s) +
(inCode ? '\n</'+'c>' : '');
}
};
// test if the selection is completely inside a <code> block.
var selectionCompletelyInsideCodeBlock = isIE
? function (sel) {
var pnode = sel.parentElement();
return ((pnode != null) &&
(hasClass(pnode, 'codeblock') ||
hasParentWithClass(pnode, 'codeblock'))) ? 1 : 0;
}
: function (sel) {
return ((sel.anchorNode != null) &&
(sel.focusNode != null) &&
hasParentWithClass(sel.anchorNode, 'codeblock') &&
hasParentWithClass(sel.focusNode, 'codeblock')) ? 1
+: 0;
};
// get the caret (cursor) position in the supplied textarea/text f
+ield (for Internet Explorer)
// XXX: should we minus selection length from this value ?
var getRealIECaretIdx = function (fld){
// formula to decode microsoft selection bookmark string
var calcBmrk = function(bk) {
return (bk.charCodeAt(0)-1) +
(bk.charCodeAt(3)-1) * 65536 +
(bk.charCodeAt(2)-1);
};
// count number of newlines in str
var countNL = function(str) {
var m = (/\r\n/g).exec(str);
if (!m || !m.length) return 0;
return m.length-1;
};
// get caret index from the text selection by way of the nasty M
+S-selection bookmark string
var p = -1;
fld.focus();
if (fld && fld.createTextRange) {
var r = document.selection.createRange().duplicate();
r.setEndPoint('StartToEnd',r);
var s = document.body.createTextRange();
s.moveToElementText(fld);
p = calcBmrk(r.getBookmark())-calcBmrk(s.getBookmark());
var rLen = 0;
do {
var BrLen = rLen;
rLen = countNL(fld.value.substring(0,p+rLen+1));
} while(BrLen!=rLen);
p += rLen;
}
return p;
};
// IE-specific code to get/set simulated textarea caret index
var mkUpdateCaret,getIECaretIdx;
(function(){
// variable to monitor caret index in comment textarea
var cIdx = 0;
// (build) generic textarea event handler
mkUpdateCaret = function (fld) {
var _fld = fld;
return function() { // close over _fld and cIdx
cIdx = getRealIECaretIdx(_fld);
};
};
// simple getter for simulated caret index
getIECaretIdx = function () { return cIdx; };
})();
// get the caret (cursor) position in the supplied text/textarea f
+ield
var getCaretIdx = isIE
? getIECaretIdx
: function (fld) {
return (fld.selectionStart || fld.selectionStart == '0')
? fld.selectionStart
: 0;
};
// set the caret (cursor) position in the supplied textarea (or te
+xt) field
var setCaretIdx = isIE
? function(fld, idx) {
fld.focus();
var sel = document.selection.createRange();
sel.moveStart ('character', -fld.value.length);
sel.moveStart ('character', idx);
sel.moveEnd ('character', 0);
sel.select();
}
: function(fld, idx) {
if (!(fld.selectionStart || fld.selectionStart == '0')) retu
+rn; // why?
fld.selectionStart = idx;
fld.selectionEnd = idx;
fld.focus();
};
// insert text at cursor (in textarea)
var insertAtCursor = function(fld, text) {
var idx = getCaretIdx(fld);
var fln = fld.value.length - idx;
var val = fld.value;
fld.value = val.substr(0, idx) + text + val.substr(idx, fln);
setCaretIdx(fld, idx + text.length);
};
// turn supplied HTML into text suitable for pasting into Perlmonk
+s comment textarea
var quoteHTML = function (html) {
var sig = 0;
return html.replace(
/* build translation regexp */
(new RegExp(`[
'<pre\\s+class=`[\'"`]?code`[\'"`]?>`[\\s\\S`]*?<tt\\sclass=
+`[\'"`]?codetext`[\'"`]?>(`[\\s\\S`]+?)<\\/tt>`[\\s\\S`]*?<\/pre>', /
+* codeblock */
'<tt\\sclass=`[\'"`]?inlinecode`[\'"`]?>(`[\\s\\S`]*?)<\\/tt
+>', /
+* inlinecode */
'`[^<>`]+',
+ /
+* plain text */
'<a\\s+href=`[\'"`]?(`[^\'">`]+)`[\'"`]?>(.+?)<\\/a>',
+ /
+* anchor tag */
'<'+'!--`[\\s\\S`]*?-->',
+ /
+* comment tag */
'<`[^<>`]*>'
+ /
+* other tag */
`].join('|'), "ig")),
/* pass matches to this function */
function (m, c, c2, href, t) {
if (m.match(/^<div\sclass=`["'`]?pmsig`["'`]?>/i)) {
sig++;
return "";
}
if (sig) {
if (m.match(/<div\b/i)) sig++;
else if (m.match(/<\/div\b/i)) sig--;
return "";
}
if (m.match(/<{1}!--/)) return "";
if (c2 != null && c2 != "") c = c2;
if (c != null && c!= "")
return (function(cc){
return cc.match(/<\/c>/i)
? '<code>'+cc+'<'+'/code>'
: '<c>'+cc+'<'+'/c>';
})(c.replace(/\n<font color=`['"`]?red`['"`]?>\+<\/font>/i
+g, '')
.replace(/>/g, '>')
.replace(/</g, '<')
.replace(/&/g, '&'));
if (href != "" && href != null && t != "" && t != null) retu
+rn monkLink(unescapeHTML(href), unescapeHTML(t)) || m;
return m;
});
};
// reverse-engineer PM markup from the supplied link (and optional
+ title)
var monkLink = function (url, text) {
var A,L;
if (A = /^(?:http:\/\/(?:www\.)?perlmonks\.(?:org|com|net)\/|\/)
+?(?:index\.pl)?\?(node(_id)?=(`[^;&`]+)|.+)$/.exec(url)) {
L = (A`[3`] == null) ? `['href://','?'+A`[1`]`] : (/^\d+$/.tes
+t(A`[3`])) ? `['id://',A`[3`]`] : `['',unescape(A`[3`])`]; // normal
+ PM-Link
} else if (A = /^http:\/\/perlmonks\.thepen\.com\/(.+)\.html$/.e
+xec(url)) {
L = (/^\d+$/.test(A`[1`])) ? `['id://',A`[1`]`] : `['',unescap
+e(A`[1`])`]; // the pe
+n
} else if (A = /^http:\/\/search\.cpan\.org\/search\?mode=module
+\&query=(.+)$/.exec(url)) {
L = `['cpan://',unescape(A`[1`])`];
+ // search
+ cpan link
} else if (A = /^http:\/\/search\.cpan\.org\/perldoc\?(.+)$/.exe
+c(url)) {
L = `['module://',unescape(A`[1`])`];
+ // cpan d
+ocumentation link
} else if (A = /^http:\/\/search\.cpan\.org\/dist\/(`[^\/`]+)\/?
+$/.exec(url)) {
L = `['dist://',unescape(A`[1`])`];
+ // cpan d
+istribution link
} else if (A = /^http:\/\/cpan\.uwinnipeg\.ca\/search\?query=(.
++)&mode=module$/.exec(url)) {
L = `['kobes://',unescape(A`[1`])`];
+ // kobes
+cpan link
} else if (A = /^http:\/\/perldoc\.perl\.org\/(?:functions\/)?(.
++)\.html$/.exec(url)) {
L = `['doc://',unescape(A`[1`]).replace(/\//g, '::')`];
+ // perldo
+c link
} else if (/^https?:\/\//.test(url)) {
L = `['',url`];
+ // other
+link
}
return (!L) ? null : "`["+L`[0`]+L`[1`]+((text == L`[1`]) ? '' :
+ '|'+text)+"`]";
};
// (build) quote button click event handler
var qfunc = function(fs) {
var _fs = fs;
return function() { // close over _fs
try {
saveScrolls(document.body, _fs);
insertAtCursor(_fs, ('\n<blockquote>\n' + quoteHTML(getSelHT
+ML()) + '\n</blockquote>\n').replace(/\n+/g,'\n').replace(/<p>\s*<\/p
+>/g,''));
restoreScrolls();
} catch (e) {
alert('quote error: '+e); // FIXME
}
return false;
};
};
// IE 6/7 doesn't have per-element selection, so you can't remembe
+r the textarea cursor location when selecting text in the
// page to be quoted into the textarea. This workaround records th
+e caret index (in the textarea) after every useful event.
var monitorCaret = function (fld) {
var up = mkUpdateCaret(fld);
var events = `['keydown', 'keyup', 'paste', 'change', 'mouseup',
+ 'mousedown', 'dragstart', 'dragend'`];
for (var x=0; x<events.length; x++) addEvent(fld, events`[x`], u
+p);
};
// on document load, create the 'quote' button and attach its han
+dler function.
addEvent(window,'load',function(e){
// only do the following for the 'Comment on' page
if (document.body.id != 'id-3333') return;
// if we've not already been run...
if (document.getElementById('fnh_quote')) return;
// find the comment textarea
var nodes = document.getElementsByName('note_doctext');
if (!nodes || !nodes.length) return;
var tArea = nodes`[0`];
// add the 'quote' button to the UI
var q = document.createElement('BUTTON');
q.appendChild(document.createTextNode('quote'));
tArea.parentNode.appendChild(q);
q.onclick = qfunc(tArea);
q.id = 'fnh_quote';
// work around IE selection deficiences
if (isIE) monitorCaret(tArea);
});
})();
</script>