/**
 * $Id: ann.js 2207 2009-09-25 03:36:33Z dan42 $
 * (c) 2006 Anime News Network
 * desc: General javascript definitions
 */
if (typeof(debug)=='string' && debug=='on'){
  true_alert = alert;
  debug_alert = 
  window.alert = function(msg){
    window.alert.invocations = window.alert.invocations+1 || 0;
    if (window.alert.invocations < 10)
      true_alert(msg);
    else if (!confirm(msg))
      window.alert = null;
  }
}
else
  debug_alert = function(msg){}

document.fully_loaded = false;
Event.observe(window, "load", function(){ document.fully_loaded = true; });

function ann_init(arg) {
  if (typeof(arg) == 'number') arguments.callee.max = arg;
  if (!ann_init_setup()) return;//not ready; DOM has not finished loading
  if (document.dom_loaded_time) return;
  document.dom_loaded_time = new Date();
  ann_init_elements(arguments.callee.max);
  ads_by();
  init();
}

function ann_init_setup()
{
  if ($('DOM_end')) return true;
  if (!already_called(arguments.callee)) {
    //setup callbacks (only once) for when DOM has finished loading
    if (document.addEventListener) //ideal method
      document.addEventListener("DOMContentLoaded", ann_init, false);
    setTimeout(ann_init,1);//fast fallback
    Event.observe(window,'load',ann_init);//slow fallback
  }
  return false;
}

function already_called(callee) {
  if (callee.done) return true;
  callee.done = true;
  return false;
}

ann_ad = {};
function set_sponsor(ad_type, name, sponsor_id)
{
  var container;
  switch(ad_type){
    case "BANNER": container = $('banner-container');  break;
    case "RECT":   container = $('rect-container');    break;
    case "VIDEO":  container = $('video-player-area'); break;
  }
  if (container){
    if (name){
      container.writeAttribute('sponsor', name);
      container.show();
    }
    else{
      container.removeAttribute('sponsor');
      container.hide();
    }
    ads_by();
    var off = container.select('div.turn-off')[0];
    if (sponsor_id && off){
      off.onclick = function(){
        new Ajax.Updater(container.id, '/my/subscription/turn_off!?advertiser='+sponsor_id, {asynchronous:true, evalScripts:true});
      }
      off.innerHTML = 'no more ads from '+name;
    }
  }
}

function ads_by()
{
  var node = $('ads_by');
  if (!node) return;
  
  var arr = ['video-player-area', 'banner-container', 'rect-container'];
  arr = arr.map(function(elem){
    if (elem = $(elem))
      if (elem.style.visibility != 'hidden')
        if (elem.style.display != 'none')
          return elem.readAttribute('sponsor');
    return null;
  });
  
  arr = arr.compact().uniq();
  node.innerHTML = arr.size()==0 ? '' : 'Ads by '+arr.join(' &amp; ');
}

function ann_init_elements(max) {
  var list = document.all || document.getElementsByTagName('*');
  var i=0, j=list.length-1;
  var to_init = [];
  
  //start looking for 'ann_init' from both ends of the page and stop when
  //we have found them all (max). This optimization avoids a LOT of needless
  //CPU churning when we have very long pages (e.g. /MyAnime/public.php)
  while (i <= j && i < 2000)
  { 
    if (list[i].getAttribute('ann_init')){
      to_init.push($(list[i]));
      if (--max == 0) break;
    }
    if (i == j) break;
    i++;
    if (list[j].getAttribute('ann_init')){
      to_init.push($(list[j]));
      if (--max == 0) break;
    }
    j--;
  }
  
  //init the elements after collecting them all, because the ann_init script 
  //may modify the number of elements in the DOM.
  for (i=0; i<to_init.length; i++)
    ann_init_element(to_init[i]);
}

function ann_init_element(elem) {
  var initparam = elem.getAttribute('ann_init');
  try {
    eval(initparam.gsub(/\bthis\b/,'elem'));
  }
  catch(e){
    debug_alert('ann_init failed for\n'+initparam+'\n\nmsg:\n'+e.message);
  }
  /*
  This does not work at all in IE6
  not even an exception is raised
  try {
    elem.ann_init = eval('(function(){ '+initparam+' })');
  }
  catch(e) {
    elem.ann_init = function(){ eval(initparam); };
  }
  elem.ann_init();
  */
}

function show_label_over(textfield, show) {
  textfield = $(textfield);
  var list = textfield.parentNode.childNodes;
  var label = null;
  for (var i=0; i<list.length; i++)
    if (list[i].tagName == 'LABEL')
      label = list[i];
    else if (list[i] == textfield)
      break;
  if (!show)
    label.style.visibility = 'hidden';
  else {
    if (textfield.value.match(/^\s*$/))
      label.style.visibility = 'visible';
    else
      label.style.visibility = 'hidden';
  }
}

function check_mandatory_fields(submit_button) {
  var list = submit_button.form.elements;
  for (var i=0; i<list.length; i++)
    if (list[i].type == 'text' || list[i].type == 'password')
      if (list[i].value.match(/^\s*$/) && list[i].getAttribute('optional') != 'true')
      { list[i].focus();
        alert('oops, you forgot to type in something');
        return false;
      }
}

function register_event_handler(obj, eventname, fn) {
  if (obj.addEventListener)
    obj.addEventListener(eventname, fn, false);
  else
    obj.attachEvent('on'+eventname, fn);
}

function toggle_visibility(id, make_visible) {
  var obj = $(id).style;
  if (make_visible == null) 
    make_visible = (obj.display == 'none');
  obj.display = make_visible ? '' : 'none';
}

function toggle_login_box() {
  toggle_visibility('login-info');
  show_label_over('login_name', true);
  show_label_over('login_pwd', true);
}

function validateNumeric(e, fp){
  if (e.charCode >= 48 && e.charCode <= 57) return true;//number
  if (e.charCode == 46 && fp) return true;//dot
  if (e.charCode == 0) return true;//control char
  return false;
}

function windowHeight() {
  if( typeof( window.innerHeight ) == 'number' )
    //Non-IE
    return window.innerHeight;
    
  if( document.documentElement && document.documentElement.clientHeight ) 
    //IE 6+ in 'standards compliant mode'
    return document.documentElement.clientHeight;
    
  if( document.body && document.body.clientHeight )
    //IE 4 compatible
    return document.body.clientHeight;

  return null;
}

function find_by_tag_name(collection, tag) {
  for (i=0; i<collection.length; i++)
    if (collection[i].tagName == tag) 
      return collection[i];
  return null;
}

function check_presence_of_ad(container, rect) {
  var ad = find_by_tag_name(container.childNodes, 'DIV');
  if (!ad) return;
  
  //NOTE: the container has to be visible otherwise the ad's offsetHeight will be zero
  if (ad.offsetHeight > 40){
    if (rect) put_rectangle_above_fold();
    return;
  }
  
  //hide the ad since it seems to be empty
  container.style.visibility = 'hidden';
  //but just in case the ad has simply not loaded *yet*, re-check every 100ms
  //for up to two seconds after the page has fully loaded.
  var fn, cpt = 20;
  fn = function(){
    if (ad.offsetHeight > 40){
      container.style.visibility = 'visible';
      ads_by();
      if (rect) put_rectangle_above_fold();
    }
    else{
      if (document.fully_loaded) cpt -= 1;
      if (cpt > 0)
        setTimeout(fn, 100);
      else{
        container.style.display = 'none';
        ads_by();
      }
    }
  };
  fn();
}

function put_rectangle_above_fold() {
  //return;
  if (Element.visible('rect-container')) {
    //maximum height of rect-container to display above fold
    var maxh = 0
      + parseInt(Element.getStyle('rect-container', 'padding-top'))
      + Element.getHeight($('rect-container').firstDescendant()) //"advertisement" label
      + 250 //show 250px of the rectangle above the fold
      + parseInt(Element.getStyle('rect-container', 'padding-bottom'));
    
    //compute height of rect-spacer in order to put rectangle right above the fold
    var h = windowHeight()
      - Position.cumulativeOffset($('rect-spacer'))[1]
      - parseInt(Element.getStyle('rect-container', 'margin-top'))
      - Math.min(maxh, Element.getHeight('rect-container'))
      - 2;
    
    //use this height only if it's bigger than the default (minimum) height
    if (h > Element.getHeight('rect-spacer'))
    {
      $('rect-spacer').style.height = h+'px';
    }
  }
}

function realurl(link, url)
{
  var tracker = link.href;
  link.href = url;
  link.observe('mousedown',function(){ link.href = tracker; });
  link.observe('click',function(){ link.href = tracker; });
  link.observe('mouseout',function(){ link.href = url; });
}


//add the mailto link by javascript to confuse harvesters
function mlink(link) {
  var list = link.childNodes;
  var span = list[list.length-1];
  link.removeChild(span);
  var n = span.childNodes.length;
  var name, host = span.childNodes[n-2].childNodes[0].nodeValue;
  if (n == 3) {
    //name is first part of email
    name = list[0].nodeValue;
    link.appendChild(document.createTextNode('@'+host));
  }
  else {
    //name is different from email
    name = span.childNodes[n-4].childNodes[0].nodeValue;
  }
  link.href = 'mailto:'+name+'@'+host;
}

function post_with_confirmation(url, msg)
{
  if (typeof(url) == 'object') url = url.href.replace('(js)','');
  
  if (msg == '' || confirm(msg)) {
    var f = document.createElement('form');
    document.body.appendChild(f);
    f.method = 'POST';
    f.action = url;
    f.submit();
  }
}



/**
 * Author: Istvan Pusztai
 * Date: 2006-10-30
 * Last Modified: 2006-11-10
 * This javascript uses the prototype framework.
 */

function init()
{
  // quit if this function has already been called
  if(arguments.callee.done) return;
  // flag this function so we don't do the same thing twice
  arguments.callee.done = true;
	
	tabsParentNode = $('tabs');
	menusParentNode = $('menus');
	totalTabsLength = 0;

	// Traverse DOM to attach events to tabs
	for(var i = 0; i < tabsParentNode.childNodes.length; i++) 
		if(tabsParentNode.childNodes[i].nodeName == "DIV")
		{
			var child = tabsParentNode.childNodes[i];
			Event.observe(child.id, 'mouseover', tabMouseOver, false);
			Event.observe(child.id, 'mouseout', tabMouseOut, false);
			totalTabsLength += child.offsetWidth;
			if(child.className == "tab_selected")
			{
				defaultTab = child;
				previousTab = defaultTab;
			}
		}
	//if(totalTabsLength > 460) menusParentNode.style.width = (totalTabsLength + 15) + "px";
	// Attach events to menus
	for(var i = 0; i < menusParentNode.childNodes.length; i++)
		if(menusParentNode.childNodes[i].nodeName == "DIV")
		{
			var child = menusParentNode.childNodes[i];
			Event.observe(child.id, 'mouseover', menuMouseOver, false);
			Event.observe(child.id, 'mouseout', menuMouseOut, false);
			if(child.className == "show")
			{
				defaultMenu = child;
				previousMenu = defaultMenu;
			}
		}
		noSection = (defaultMenu == null) ? true : false;
} // END onLoadEvent()

// Constants
var DELAY_TABSELECT = 200;
var DELAY_REVERT = 500; 

// Variables to be initialized during the DOM traversal for event attachement
var defaultTab = null;
var defaultMenu = null;
var previousTab = null;
var previousMenu  = null;
var currentTab = null;
var currentMenu = null;
var noSection;

// Variables to be initialized during events
var keyPressTimer = null;
var tabMouseOverTimer = null;
var tabMouseOutTimer = null;
var menuMouseOutTimer = null;
var autoRevertTimer = null;

// Hover states
var tabHovered = false;
var menuHovered = false;

// Parent nodes (container divs) of tabs and menus
var tabsParentNode = null;
var menusParentNode = null;

// User bar variables
var currentPageBookmarked = false;


// Attach keyPress event to listen to keystrokes
Event.observe(document, 'keypress', keyPressEvent, false);

///////////////////////////////////////////////////////////////////////////////////////////
// + TABBED MENU
///////////////////////////////////////////////////////////////////////////////////////////
function switchTab(revertme)
{
	clearTimeout(autoRevertTimer);
	
	if(previousMenu != null)
		previousMenu.className = "hide"; // set previous submenu to hidden
		
	currentMenu.className = ""; // set the target submenu visible
	
	if(previousTab != null)
	{
		if(noSection)
			previousTab.className = ""; // set previous tab to unselected
		else
		{
			if(previousTab.id == defaultTab.id)
				previousTab.className = "on";
			else
				previousTab.className = ""; // set previous tab to unselected
		}
	}
	currentTab.className = "tab_selected"; // set current tab to selected
	
	previousMenu = currentMenu;
	previousTab = currentTab;

	if(revertme == true)
		autoRevertTimer = setTimeout(revert, DELAY_REVERT);
}
function revert()
{
	if(!tabHovered && !menuHovered)
	{
		if(!noSection)
		{
			currentTab = defaultTab;
			currentMenu = defaultMenu;
			switchTab(false);
		}
		else if(previousMenu != null)
		{
			previousMenu.className = "hide"; // set previous submenu to hidden
			previousTab.className = ""; // set previous tab to unselected
		}
	}
}
function tabMouseOver(e)
{
	tabHovered = true;
	if(tabMouseOutTimer != null) clearTimeout(tabMouseOutTimer);
	if(tabMouseOverTimer != null) clearTimeout(tabMouseOverTimer);
	
	var targ = Event.element(e); // find the target from Event e
	var targetMenu = $(targ.alt);
	
	if(targetMenu == null)
		return;
	
	currentTab = targ.parentNode.parentNode;
	currentMenu = targetMenu;
	
	tabMouseOverTimer = setTimeout(switchTab, DELAY_TABSELECT); // menu id is located in the image tag's alt property
}
function tabMouseOut(e)
{
	tabHovered = false;
	if(tabMouseOverTimer != null) clearTimeout(tabMouseOverTimer);
	if(tabMouseOutTimer != null) clearTimeout(tabMouseOutTimer);
	tabMouseOutTimer = setTimeout(revert, DELAY_REVERT);
}
function menuMouseOut(e)
{
	menuHovered = false;
}
function menuMouseOver(e)
{
	menuHovered = true;
}
function scroll_to_menu(id)
{
  scroll(2000,0);
  var tab = $("tab_"+id);
  if (!tab) return;
  currentTab = tab
  currentMenu = $("menu_"+id);
  switchTab(false);  
}
function keyPressEvent(e)
{
	if(menuMouseOutTimer != null) clearTimeout(menuMouseOutTimer);
	if(tabMouseOutTimer != null) clearTimeout(tabMouseOutTimer);
	if(keyPressTimer != null) clearTimeout(keyPressTimer);
	//keyPressTimer = setTimeout(revert, DELAY_REVERT);
	
	var targ = Event.element(e); // find the target from Event e
	if(targ.nodeName == "INPUT" || targ.nodeName == "TEXTAREA"  || targ.nodeName == "SELECT") // Disable tab switchTabing if focus is in an input or textarea
		return;
		
	var code;
	if(!e)
		var e = window.event;
	if(e.keyCode)
		code = e.keyCode;
	else if(e.charCode)
		code = e.charCode;
	switch(code)
	{
		case 78:
		case 110: // n
      scroll_to_menu("news");
			break;
		case 86:
		case 118: // v
			scroll_to_menu("views");
			break;
		case 69:
		case 101: // e
			scroll_to_menu("encyclopedia");
			break;
		/*case 73:
		case 105: // i
			scroll_to_menu("interactive");
			break;*/
		case 70:
		case 102: // f
			scroll_to_menu("forum");
			break;
		case 65:
		case 97: // a
			scroll_to_menu("admin");
			break;
		case 77:
		case 109: // m
			scroll_to_menu("myann");
			break;
		case 83:
		case 115: // s
			scroll_to_menu("sponsor");
			break;
	}
}

///////////////////////////////////////////////////////////////////////////////////////////
// + FLOATING MENU(S)
///////////////////////////////////////////////////////////////////////////////////////////

var openedId = null;
var openedSrc = null;
var clickWithinMenu = false;

var curContainer = null;
var aniCounter = 0;
var aniCurrrentSL = 0;

function display(id, sourceId)
{
	if(openedId == id)
	{
		hideOpened();
		return;
	}
	
	hideOpened();
	var target = $(id);
	var source = $(sourceId);
	openedSrc = source;
	
	openedId = id;
	pos = Position.cumulativeOffset(source);
	if(sourceId == "filter_btn")
	{
		target.style.left = (pos[0] - 65) + "px";
		//target.style.top = (pos[1] + 7) + "px";//Firefox
		//target.style.top = (pos[1] + 22) + "px";//IE
		target.style.top = (34) + "px";//both
	}
	else
	{
		target.style.left = pos[0] + "px";
		target.style.top = (pos[1] + 19) + "px";
	}
	target.style.visibility = "visible";
	
	Event.observe(target, 'click', clickFloatingMenuEvent, true);
	Event.observe(source, 'click', clickFloatingMenuEvent, true);
	Event.observe(document, 'click', clickEvent, false);
}
function clickFloatingMenuEvent()
{
	clickWithinMenu = true;
}
function clickEvent()
{
	if(clickWithinMenu)
	{
		clickWithinMenu = false;
	}
	else
	{
		Event.stopObserving($(openedId), 'click', clickFloatingMenuEvent, true);
		Event.stopObserving(openedSrc, 'click', clickFloatingMenuEvent, true);
		Event.stopObserving(document, 'click', clickEvent, false);
		hideOpened();
	}	
}
function hideOpened()
{
	if(openedId != null)
	{
		$(openedId).style.visibility = "hidden";
		openedId = null;
	}
}
function sforward(id)
{
	curContainer = $(id);
	aniCurrrentSL = curContainer.scrollLeft;
	aniRight();
}
function sback(id)
{
	curContainer = $(id);
	aniCurrrentSL = curContainer.scrollLeft;
	aniLeft();
}
function aniRight()
{
	if(aniCounter <= 15)
	{
		curContainer.scrollLeft = easeInOut(aniCurrrentSL, aniCurrrentSL + 300, 15, aniCounter, 2.5);
		aniCounter++;
		setTimeout(aniRight, 24);
	}
	else
		aniCounter = 0;
}
function aniLeft()
{
	if(aniCounter <= 15)
	{
		curContainer.scrollLeft = easeInOut(aniCurrrentSL, aniCurrrentSL -300 , 15, aniCounter, 2.5);
		aniCounter++;
		setTimeout(aniLeft, 24);
	}
	else
		aniCounter = 0;
}
function easeInOut(minValue, maxValue, totalSteps, actualStep, powr)
{ 
    var delta = maxValue - minValue; 
    var step = minValue+(Math.pow(((1 / totalSteps) * actualStep), powr) * delta); 
    return Math.ceil(step) 
}

///////////////////////////////////////////////////////////////////////////////////////////
// + SEARCH
///////////////////////////////////////////////////////////////////////////////////////////
function fsearch(type)
{
	$('netnav_search_type').value = type;
	$('netnav_search').submit();
}
function imgSwap(res, source)
{
	source.alt = source.src;
	source.src = res;
	source.onmouseout = function() { source.src = source.alt ;};
}

var gallery_id = -1;
function show_gallery(n) {
  var img;
  if ($('gallery_pic'+n)) {
    for (j=-1; j<=1; j++) {
      if (img = $('gallery_img'+(n+j))) {
        img.src = img.getAttribute('xsrc');
      }
    }
    if (gallery_id >= 0) $('gallery_pic'+gallery_id).hide();
    $('gallery_pic'+n).show();
    gallery_id = n;
  }
}

function init_tri_state_checkboxes()
{
  var inputs = document.getElementsByTagName('input');
  for (i=0; i<inputs.length; i++) {
    if (inputs[i].type == "checkbox") {
      var chk = $(inputs[i])
      if (m = chk.id.match(/(tristate\d+)in/)) {
        var img = $(document.createElement("img"));
        img.setStyle({border: '1px solid black', padding: '1px', width: '9px', height: '9px', margin: '0 2px -2px 0'});
        img.setAttribute('tristate', m[1]);
        chk.insert({before: img});
        Event.observe(img, 'click', toggle_tri_state);
        display_tri_state(img);
        chk.hide();
      }
    }
  }
}

function toggle_tri_state(event){
  var img = Event.element(event);
  var chk_in = $(img.getAttribute('tristate')+'in');
  var chk_ex = $(img.getAttribute('tristate')+'ex');
  
  if (!chk_in.checked && !chk_ex.checked) {
    chk_in.checked = true;
    chk_ex.checked = false;
    if (chk_in.value == "erotica") $('AO').checked = true;
  }
  else if (chk_in.checked && !chk_ex.checked) {
    chk_in.checked = false;
    chk_ex.checked = true;
  }
  else {
    chk_in.checked = false;
    chk_ex.checked = false;
  }
  
  display_tri_state(img);
}

function display_tri_state(img) {
  var chk_in = $(img.getAttribute('tristate')+'in');
  var chk_ex = $(img.getAttribute('tristate')+'ex');
  
  if (chk_in.checked && !chk_ex.checked)
    img.src = '/stylesheets/system/confirm.gif';
  else if (!chk_in.checked && chk_ex.checked)
    img.src = '/stylesheets/system/delete.gif';
  else
    img.src = '/img/spacer.gif';
}

function hovr(obj)
{
  if (Prototype.Browser.IE6)
  {
    Event.observe(obj,'mouseover',function(){obj.addClassName('hover')});
    Event.observe(obj,'mouseout',function(){obj.removeClassName('hover')});
  }
}

function on_off(id, on)
{
  var elem = $(id);
  elem[i].disabled = !on;
  elem = $$('#'+id+' input');
  for (var i=0; i<elem.length; i++) elem[i].disabled = !on;
  elem = $$('#'+id+' select');
  for (var i=0; i<elem.length; i++) elem[i].disabled = !on;
}

function textlimit(obj)
{
  obj.style.maxWidth = obj.getWidth()+'px';
  obj.style.width = "100%";
}


Encyc = {
  
  toggle_all_operations: function() {
    if (!Encyc.all_items) {
      Encyc.all_items = $$('span.encyc-op');
      Encyc.all_items.each(Encyc.init_operations);
    }
    for (var i=0; i<Encyc.all_items.length; i++) {
      Encyc.all_items[i].toggleClassName('pin');
    }
  },
  
  init_operations: function(elem, is_mouseover) {
    elem = $(elem);
    if (!elem.getAttribute('ann_loaded')) {
      elem.firstDescendant().insert({after: '<span class="o">'+Encyc.generate_op_html(elem)+'</span>'});
      Encyc.observe_operations(elem, is_mouseover==true)
      elem.setAttribute('ann_loaded', true);
    }
  },
  
  observe_operations: function(op_container){
    var icon = op_container.firstDescendant();
    icon.observe('click', function(){ op_container.toggleClassName('pin') });
  },
  
  edit_url: function(code) {
    var part = code.split('-');
    switch(part[0])
    { case 'AI': return 'addinfo.php?anime_id='+part[1]+'&seq='+part[2];
      case 'PI': return 'addinfo.php?person_id='+part[1]+'&seq='+part[2];
      case 'CI': return 'addinfo.php?company_id='+part[1]+'&seq='+part[2];
      default: return 'anime-editcredits.php?code='+code;
    }
  },
  
  generate_op_html:  function(elem) {
    var arr = new Array;
    var code = elem.getAttribute("ann_code");
    var edit = elem.getAttribute("ann_edit");
    var audit = elem.getAttribute("ann_audit");
    var err = elem.getAttribute("ann_err");
    var source = elem.getAttribute("ann_source");
    if (source.length <= 3) source ='';
    
    if (edit == 1)
      arr.push('<a href="'+Encyc.edit_url(code)+'" class="own">edit</a>');

    if (audit)
      arr.push('<a href="audit/new?for='+audit+'">audit</a>')
    else if (!err)
      arr.push('<a href="error-report.php?code='+code+'" title="Report an error with this">error</a>');
    else{
      var label = edit == 1 ? '<strong>error</strong>' : 'error';
      var title = err == 1 ? '1 person reported an error with this' : err+' people reported errors with this';
      arr.push('<a href="error-report.php?code='+code+'" title="'+title+'" class=RED>'+label+'</a>');
    }

    if (source != '')
      arr.push('<a href="javascript:void(0)" onclick="Encyc.toggle_source(this)">source</a>');
    else if (edit == 1)
      arr.push('<a href="'+Encyc.edit_url(code)+'" class=RED>no source</a>');
    else
      arr.push('<a>no source</a>');
    
    return arr.join('<wbr>');
  },
  
  toggle_source: function(link){
    var ops = $(link);
    link.blur();
    while (!ops.getAttribute('ann_code')) ops = ops.parentNode;
    ops.toggleClassName('show-source');
    
    if (!link.hasClassName('src')){
      link.addClassName('src');
      var username = Encyc.user_lookup_table[ops.getAttribute('ann_user')];
      var str = '<tr><td>'+Encyc.urls_to_links(ops.getAttribute('ann_source'))+'</td></tr>';
      if (username) str += '<tr><td>Added by: '+username+'</td></tr>';
      link.insert({after: '<span class="src"><table class="layout" border="0" cellspacing="0">'+str+'</table></span>'});
      var span = link.next();
      var max_width = document.viewport.getWidth() - 10 - span.cumulativeOffset().left;
      if (max_width > 500) max_width = 500;
      span.setStyle({width: max_width+'px'});
    }
  },
  
  urls_to_links_RX: new RegExp(
    //prefix:
    "(?:(http)s?://|www\.)" +
    //hostport:
    "[-.:a-z0-9]+" +
    //hpath:
    "(?:" +
      "/(?:" +
        "[#*'(,.;:@?]*" +
        "(?:[-$_+a-z0-9&=/)~!]|%[0-9a-f]{2})" +
      ")*" +
    ")?", "i"),
    
  urls_to_links_REPL: function(m){
    var url = m[0];
    var label = url.gsub(/&[a-z]+;|\W/, function(x){return x[0]+'<wbr>'} );
    if (m[1] != 'http') url = 'http://'+url;
    return '<a href="'+url+'">'+label+'</a>';
  },
  
  urls_to_links: function(str){
    return str.gsub(Encyc.urls_to_links_RX, Encyc.urls_to_links_REPL);
  }
  
};

