Sequenzia/lib/assets/javascripts/moe-legacy/common.js
2013-10-26 18:06:58 -05:00

1208 lines
33 KiB
JavaScript
Executable File

var DANBOORU_VERSION = {
major: 1,
minor: 13,
build: 0
}
/* If initial is true, this is a notice set by the notice cookie and not a
* realtime notice from user interaction. */
function notice(msg, initial) {
/* If this is an initial notice, and this screen has a dedicated notice
* container other than the floating notice, use that and don't disappear
* it. */
if(initial) {
var static_notice = $("static_notice");
if(static_notice) {
static_notice.update(msg);
static_notice.show();
return;
}
}
start_notice_timer();
$('notice').update(msg);
$('notice-container').show();
}
function number_to_human_size(size, precision)
{
if(precision == null)
precision = 1;
size = Number(size);
if(size.toFixed(0) == 1) text = "1 Byte";
else if(size < 1024) text = size.toFixed(0) + " Bytes";
else if(size < 1024*1024) text = (size / 1024).toFixed(precision) + " KB";
else if(size < 1024*1024*1024) text = (size / (1024*1024)).toFixed(precision) + " MB";
else if(size < 1024*1024*1024*1024) text = (size / (1024*1024*1024)).toFixed(precision) + " GB";
else text = (size / (1024*1024*1024*1024)).toFixed(precision) + " TB";
text = text.gsub(/([0-9]\.\d*?)0+ /, '#{1} ' ).gsub(/\. /,' ');
return text;
}
function time_ago_in_words(from_time, to_time)
{
if(to_time == null)
to_time = new Date();
var from_time = from_time.valueOf();
var to_time = to_time.valueOf();
distance_in_seconds = Math.abs((to_time - from_time)/1000).round();
distance_in_minutes = (distance_in_seconds/60).round();
if(distance_in_minutes <= 1)
return "1 minute";
if(distance_in_minutes <= 44)
return distance_in_minutes + " minutes";
if(distance_in_minutes <= 89)
return "1 hour";
if(distance_in_minutes <= 1439)
{
var hours = distance_in_minutes / 60;
hours = (hours - 0.5).round(); // round down
return hours + " hours";
}
if(distance_in_minutes <= 2879)
return "1 day";
if(distance_in_minutes <= 43199)
{
var days = distance_in_minutes / 1440;
days = (days - 0.5).round(); // round down
return days + " days";
}
if(distance_in_minutes <= 86399)
return "1 month";
if(distance_in_minutes <= 525959)
{
var months = distance_in_minutes / 43200;
months = (months - 0.5).round(); // round down
return months + " months";
}
var years = (distance_in_minutes / 525960).toFixed(1);
return years + " years";
}
scale = function(x, l1, h1, l2, h2)
{
return ((x - l1) * (h2 - l2) / (h1 - l1) + l2);
}
clamp = function(n, min, max)
{
return Math.max(Math.min(n, max), min);
}
var ClearNoticeTimer;
function start_notice_timer() {
if(ClearNoticeTimer)
window.clearTimeout(ClearNoticeTimer);
ClearNoticeTimer = window.setTimeout(function() {
$('notice-container').hide();
}, 5000);
}
var ClipRange = Class.create({
initialize: function(min, max) {
if (min > max) {
throw "paramError"
}
this.min = min
this.max = max
},
clip: function(x) {
if (x < this.min) {
return this.min
}
if (x > this.max) {
return this.max
}
return x
}
})
Object.extend(Element, {
appendChildBase: Element.appendChild,
appendChild: function(e) {
this.appendChildBase(e)
return e
}
});
Object.extend(Element.Methods, {
showBase: Element.show,
show: function(element, visible) {
if (visible || visible == null)
return $(element).showBase();
else
return $(element).hide();
},
setClassName: function(element, className, enabled) {
if(enabled)
return $(element).addClassName(className);
else
return $(element).removeClassName(className);
},
pickClassName: function(element, classNameEnabled, classNameDisabled, enabled) {
$(element).setClassName(classNameEnabled, enabled);
$(element).setClassName(classNameDisabled, !enabled);
},
isParentNode: function(element, parentNode) {
while(element) {
if(element == parentNode)
return true;
element = element.parentNode;
}
return false;
},
setTextContent: function(element, text)
{
if(element.innerText != null)
element.innerText = text;
else
element.textContent = text;
return element;
},
recursivelyVisible: function(element)
{
while(element != document.documentElement)
{
if(!element.visible())
return false;
element = element.parentNode;
}
return true;
}
});
Element.addMethods()
var KeysDown = new Hash();
/* Many browsers eat keyup events if focus is lost while the button
* is pressed. */
document.observe("blur", function(e) { KeysDown = new Hash(); })
function OnKeyCharCode(key, f, element)
{
if(window.opera)
return;
if(!element)
element = document;
element.observe("keyup", function(e) {
if (e.keyCode != key)
return;
KeysDown.set(KeysDown[e.keyCode], false);
});
element.observe("keypress", function(e) {
if (e.charCode != key)
return;
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey)
return;
if(KeysDown.get(KeysDown[e.keyCode]))
return;
KeysDown.set(KeysDown[e.keyCode], true);
var target = e.target;
if(target.tagName == "INPUT" || target.tagName == "TEXTAREA")
return;
f(e);
e.stop();
e.preventDefault();
});
}
function OnKey(key, options, press, release)
{
if(!options)
options = {};
var element = options["Element"]
if(!element)
element = document;
if(element == document && window.opera && !options.AlwaysAllowOpera)
return;
element.observe("keyup", function(e) {
if (e.keyCode != key)
return;
KeysDown[e.keyCode] = false
if(release)
release(e);
});
element.observe("keydown", function(e) {
if (e.keyCode != key)
return;
if (e.metaKey)
return;
if (e.shiftKey != !!options.shiftKey)
return;
if (e.altKey != !!options.altKey)
return;
if (e.ctrlKey != !!options.ctrlKey)
return;
if (!options.allowRepeat && KeysDown[e.keyCode])
return;
KeysDown[e.keyCode] = true
var target = e.target;
if(!options.AllowTextAreaFields && target.tagName == "TEXTAREA")
return;
if(!options.AllowInputFields && target.tagName == "INPUT")
return;
if(press && !press(e))
return;
e.stop();
e.preventDefault();
});
}
function InitTextAreas()
{
$$("TEXTAREA").each(function(elem) {
var form = elem.up("FORM");
if(!form)
return;
if(elem.set_login_handler)
return;
elem.set_login_handler = true;
OnKey(13, { ctrlKey: true, AllowInputFields: true, AllowTextAreaFields: true, Element: elem}, function(f) {
$(form).simulate_submit();
});
});
}
function InitAdvancedEditing()
{
if(Cookie.get("show_advanced_editing") != "1")
return;
$(document.documentElement).removeClassName("hide-advanced-editing");
}
/* When we resume a user submit after logging in, we want to run submit events, as
* if the submit had happened normally again, but submit() doesn't do this. Run
* a submit event manually. */
Element.addMethods("FORM", {
simulate_submit: function(form)
{
form = $(form);
if(document.createEvent)
{
var e = document.createEvent("HTMLEvents");
e.initEvent("submit", true, true);
form.dispatchEvent(e);
if(!e.stopped)
form.submit();
}
else
{
if(form.fireEvent("onsubmit"))
form.submit();
}
}
});
Element.addMethods({
simulate_anchor_click: function(a, ev)
{
a = $(a);
if(document.dispatchEvent)
{
if(a.dispatchEvent(ev) && !ev.stopped)
window.location.href = a.href;
}
else
{
if(a.fireEvent("onclick", ev))
window.location.href = a.href;
}
}
});
clone_event = function(orig)
{
if(document.dispatchEvent)
{
var e = document.createEvent("MouseEvent");
e.initMouseEvent(orig.type, orig.canBubble, orig.cancelable, orig.view,
orig.detail, orig.screenX, orig.screenY, orig.clientX, orig.clientY,
orig.ctrlKey, orig.altKey, orig.shiftKey, orig.metaKey,
orig.button, orig.relatedTarget);
return Event.extend(e);
}
else
{
var e = document.createEventObject(orig);
return Event.extend(e);
}
}
Object.extend(String.prototype, {
subst: function(subs) {
var text = this;
for(var s in subs)
{
var r = new RegExp("\\${" + s + "}", "g");
var to = subs[s];
if(to == null) to = "";
text = text.replace(r, to);
}
return text;
},
createElement: function() {
var container = document.createElement("div");
container.innerHTML = this;
return container.removeChild(container.firstChild);
}
});
function createElement(type, className, html)
{
var element = $(document.createElement(type));
element.className = className;
element.innerHTML = html;
return element;
}
/* Prototype calls onSuccess instead of onFailure when the user cancelled the AJAX
* request. Fix that with a monkey patch, so we don't have to track changes inside
* prototype.js. */
Ajax.Request.prototype.successBase = Ajax.Request.prototype.success;
Ajax.Request.prototype.success = function()
{
try {
if(this.transport.getAllResponseHeaders() == null)
return false;
} catch (e) {
/* FF throws an exception if we call getAllResponseHeaders on a cancelled request. */
return false;
}
return this.successBase();
}
/* Work around a Prototype bug; it discards exceptions instead of letting them fall back
* to the browser where they'll be logged. */
Ajax.Responders.register({
onException: function(request, exception) {
/* Report the error here; don't wait for onerror to get it, since the exception
* isn't passed to it so the stack trace is lost. */
var data = "";
if(request.url)
data += "AJAX URL: " + request.url + "\n";
try {
var params = request.parameters;
for(key in params)
{
var text = params[key];
var length = text.length;
if(text.length > 1024)
text = text.slice(0, 1024) + "...";
data += "Parameter (" + length + "): " + key + "=" + text + "\n";
}
} catch(e) {
data += "Couldn't get response parameters: " + e + "\n";
}
try {
var text = request.transport.responseText;
var length = text.length;
if(text.length > 1024)
text = text.slice(0, 1024) + "...";
data += "Response (" + length + "): ->" + text + "<-\n";
} catch(e) {
data += "Couldn't get response text: " + e + "\n";
}
ReportError(null, null, null, exception, data);
(function() {
throw exception;
}).defer();
}
});
/*
* In Firefox, exceptions thrown from event handlers tend to get lost. Sometimes they
* trigger window.onerror, but not reliably. Catch exceptions out of event handlers and
* throw them from a deferred context, so they'll make it up to the browser to be
* logged.
*
* This depends on bindAsEventListener actually only being used for event listeners,
* since it eats exceptions.
*
* Only do this in Firefox; not all browsers preserve the call stack in the exception,
* so this can lose information if used when it's not needed.
*/
if(Prototype.Browser.Gecko)
Function.prototype.bindAsEventListener = function()
{
var __method = this, args = $A(arguments), object = args.shift();
return function(event) {
try {
return __method.apply(object, [event || window.event].concat(args));
} catch(exception) {
(function() { throw exception; }).defer();
}
}
}
window.onerror = function(error, file, line)
{
ReportError(error, file, line, null);
}
/*
* Return the values of list starting at idx and moving outwards.
*
* sort_array_by_distance([0,1,2,3,4,5,6,7,8,9], 5)
* [5,4,6,3,7,2,8,1,9,0]
*/
sort_array_by_distance = function(list, idx)
{
var ret = [];
ret.push(list[idx]);
for(var distance = 1; ; ++distance)
{
var length = ret.length;
if(idx-distance >= 0)
ret.push(list[idx-distance]);
if(idx+distance < list.length)
ret.push(list[idx+distance]);
if(length == ret.length)
break;
}
return ret;
}
/* Return the squared distance between two points. */
distance_squared = function(x1, y1, x2, y2)
{
return Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
}
/* Return the size of the window. */
getWindowSize = function()
{
var size = {};
if(window.innerWidth != null)
{
size.width = window.innerWidth;
size.height = window.innerHeight;
}
else
{
/* IE: */
size.width = document.documentElement.clientWidth;
size.height = document.documentElement.clientHeight;
}
return size;
}
/* If 2d canvases are supported, return one. Otherwise, return null. */
create_canvas_2d = function()
{
var canvas = document.createElement("canvas");
if(canvas.getContext && canvas.getContext("2d"))
return canvas;
return null;
}
Prototype.Browser.AndroidWebKit = (navigator.userAgent.indexOf("Android") != -1 && navigator.userAgent.indexOf("WebKit") != -1);
/* Some UI simply doesn't make sense on a touchscreen, and may need to be disabled or changed.
* It'd be nice if this could be done generically, but this is the best available so far ... */
Prototype.BrowserFeatures.Touchscreen = (function() {
/* iOS WebKit has window.Touch, a constructor for Touch events. */
if(window.Touch)
return true;
// Mozilla/5.0 (Linux; U; Android 2.2; en-us; sdk Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1
if(navigator.userAgent.indexOf("Mobile Safari/") != -1)
return true;
// Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_2 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Mobile/8C134
if(navigator.userAgent.indexOf("Mobile/") != -1)
return true;
return false;
})();
/* MI: Using this method to detect if working on mobile browser,
* instead of using Prototype.BrowserFeatures.Touchscreen, because
* desktop Webkit browsers have window.Touch.
* This may not be the best method though.
*/
Prototype.BrowserIsMobile = (function() {
var agent = navigator.userAgent;
return !!(agent.match(/Android/i) ||
agent.match(/BlackBerry/i) ||
agent.match(/iPhone|iPad|iPod/i) ||
agent.match(/Opera Mini/i) ||
agent.match(/IEMobile/i));
})();
/* When element is dragged, the document moves around it. If scroll_element is true, the
* element should be positioned (eg. position: absolute), and the element itself will be
* scrolled. */
DragElement = function(element, options)
{
$(document.body).addClassName("not-dragging");
this.options = options || {};
if (!this.options.condition) this.options.condition = function() { return true }
if(this.options.snap_pixels == null)
this.options.snap_pixels = 10;
this.ignore_mouse_events_until = null;
this.mousemove_event = this.mousemove_event.bindAsEventListener(this);
this.mousedown_event = this.mousedown_event.bindAsEventListener(this);
this.dragstart_event = this.dragstart_event.bindAsEventListener(this);
this.mouseup_event = this.mouseup_event.bindAsEventListener(this);
this.click_event = this.click_event.bindAsEventListener(this);
this.selectstart_event = this.selectstart_event.bindAsEventListener(this);
this.touchmove_event = this.touchmove_event.bindAsEventListener(this);
this.touchstart_event = this.touchstart_event.bindAsEventListener(this);
this.touchend_event = this.touchend_event.bindAsEventListener(this);
this.move_timer_update = this.move_timer_update.bind(this);
this.element = element;
this.dragging = false;
this.drag_handlers = [];
this.handlers = [];
/*
* Starting drag on mousedown works in most browsers, but has an annoying side-
* effect: we need to stop the event to prevent any browser drag operations from
* happening, and that'll also prevent clicking the element from focusing the
* window. Stop the actual drag in dragstart. We won't get mousedown in
* Opera, but we don't need to stop it there either.
*
* Sometimes drag events can leak through, and attributes like -moz-user-select may
* be needed to prevent it.
*/
if(!options.no_mouse)
{
this.handlers.push(element.on("mousedown", this.mousedown_event));
this.handlers.push(element.on("dragstart", this.dragstart_event));
}
if(!options.no_touch)
{
this.handlers.push(element.on("touchstart", this.touchstart_event));
this.handlers.push(element.on("touchmove", this.touchmove_event));
}
/*
* We may or may not get a click event after mouseup. This is a pain: if we get a
* click event, we need to cancel it if we dragged, but we may not get a click event
* at all; detecting whether a click event came from the drag or not is difficult.
* Cancelling mouseup has no effect. FF, IE7 and Opera still send the click event
* if their dragstart or mousedown event is cancelled; WebKit doesn't.
*/
if(!Prototype.Browser.WebKit)
this.handlers.push(element.on("click", this.click_event));
}
DragElement.prototype.destroy = function()
{
this.stop_dragging(null, true);
this.handlers.each(function(h) { h.stop(); });
this.handlers = [];
}
DragElement.prototype.move_timer_update = function()
{
this.move_timer = null;
if(!this.options.ondrag)
return;
if(this.last_event_params == null)
return;
var last_event_params = this.last_event_params;
this.last_event_params = null;
var x = last_event_params.x;
var y = last_event_params.y;
var anchored_x = x - this.anchor_x;
var anchored_y = y - this.anchor_y;
var relative_x = x - this.last_x;
var relative_y = y - this.last_y;
this.last_x = x;
this.last_y = y;
if(this.options.ondrag)
this.options.ondrag({
dragger: this,
x: x,
y: y,
aX: anchored_x,
aY: anchored_y,
dX: relative_x,
dY: relative_y,
latest_event: last_event_params.event
});
}
DragElement.prototype.mousemove_event = function(event)
{
event.stop();
var scrollLeft = (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft);
var scrollTop = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
var x = event.pointerX() - scrollLeft;
var y = event.pointerY() - scrollTop;
this.handle_move_event(event, x, y);
}
DragElement.prototype.touchmove_event = function(event)
{
/* Ignore touches other than the one we started with. */
var touch = null;
for(var i = 0; i < event.changedTouches.length; ++i)
{
var t = event.changedTouches[i];
if(t.identifier == this.dragging_touch_identifier)
{
touch = t;
break;
}
}
if(touch == null)
return;
event.preventDefault();
/* If a touch drags over the bottom navigation bar in Safari and is released while outside of
* the viewport, the touchend event is never sent. Work around this by cancelling the drag
* if we get too close to the end. Don't do this if we're in standalone (web app) mode, since
* there's no navigation bar. */
if(!window.navigator.standalone && touch.pageY > window.innerHeight-10)
{
debug("Dragged off the bottom");
this.stop_dragging(event, true);
return;
}
var x = touch.pageX;
var y = touch.pageY;
this.handle_move_event(event, x, y);
}
DragElement.prototype.handle_move_event = function(event, x, y)
{
if(!this.dragging)
return;
if(!this.dragged)
{
var distance = Math.pow(x - this.anchor_x, 2) + Math.pow(y - this.anchor_y, 2);
var snap_pixels = this.options.snap_pixels;
snap_pixels *= snap_pixels;
if(distance < snap_pixels) // 10 pixels
return;
}
if(!this.dragged)
{
if(this.options.onstartdrag)
{
/* Call the onstartdrag callback. If it returns true, cancel the drag. */
if(this.options.onstartdrag({ handler: this, latest_event: event }))
{
this.dragging = false;
return;
}
}
this.dragged = true;
$(document.body).addClassName(this.overriden_drag_class || "dragging");
$(document.body).removeClassName("not-dragging");
}
this.last_event_params = {
x: x,
y: y,
event: event
};
if(this.dragging_by_touch && Prototype.Browser.AndroidWebKit)
{
/* Touch events on Android tend to queue up when they come in faster than we
* can process. Set a timer, so we discard multiple events in quick succession. */
if(this.move_timer == null)
this.move_timer = window.setTimeout(this.move_timer_update, 10);
}
else
{
this.move_timer_update();
}
}
DragElement.prototype.mousedown_event = function(event)
{
if(!event.isLeftClick())
return;
/* Check if we're temporarily ignoring mouse events. */
if(this.ignore_mouse_events_until != null)
{
var now = (new Date()).valueOf();
if(now < this.ignore_mouse_events_until)
return;
this.ignore_mouse_events_until = null;
}
var scrollLeft = (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft);
var scrollTop = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
var x = event.pointerX() - scrollLeft;
var y = event.pointerY() - scrollTop;
this.start_dragging(event, false, x, y, 0);
}
DragElement.prototype.touchstart_event = function(event)
{
/* If we have multiple touches, find the first one that actually refers to us. */
var touch = null;
for(var i = 0; i < event.changedTouches.length; ++i)
{
var t = event.changedTouches[i];
if(!t.target.isParentNode(this.element))
continue;
touch = t;
break;
}
if(touch == null)
return;
var x = touch.pageX;
var y = touch.pageY;
this.start_dragging(event, true, x, y, touch.identifier);
}
DragElement.prototype.start_dragging = function(event, touch, x, y, touch_identifier)
{
if(this.dragging_touch_identifier != null || (this.options.condition && this.options.condition() === false))
return;
/* If we've been started with a touch event, only listen for touch events. If we've
* been started with a mouse event, only listen for mouse events. We may receive
* both sets of events, and the anchor coordinates for the two may not be compatible. */
this.drag_handlers.push(document.on("selectstart", this.selectstart_event));
this.drag_handlers.push(Element.on(window, "pagehide", this.pagehide_event.bindAsEventListener(this)));
if(touch)
{
this.drag_handlers.push(document.on("touchend", this.touchend_event));
this.drag_handlers.push(document.on("touchcancel", this.touchend_event));
this.drag_handlers.push(document.on("touchmove", this.touchmove_event));
}
else
{
this.drag_handlers.push(document.on("mouseup", this.mouseup_event));
this.drag_handlers.push(document.on("mousemove", this.mousemove_event));
}
this.dragging = true;
this.dragged = false;
this.dragging_by_touch = touch;
this.dragging_touch_identifier = touch_identifier;
this.anchor_x = x;
this.anchor_y = y;
this.last_x = this.anchor_x;
this.last_y = this.anchor_y;
if(this.options.ondown)
this.options.ondown({
dragger: this,
x: x,
y: y,
latest_event: event
});
}
DragElement.prototype.pagehide_event = function(event)
{
this.stop_dragging(event, true);
}
DragElement.prototype.touchend_event = function(event)
{
/* If our touch was released, stop the drag. */
for(var i = 0; i < event.changedTouches.length; ++i)
{
var t = event.changedTouches[i];
if(t.identifier == this.dragging_touch_identifier)
{
this.stop_dragging(event, event.type == "touchcancel");
/*
* Work around a bug on iPhone. The mousedown and mouseup events are sent after
* the touch is released, instead of when they should be (immediately following
* touchstart and touchend). This means we'll process each touch as a touch,
* then immediately after as a mouse press, and fire ondown/onup events for each.
*
* We can't simply ignore mouse presses if touch events are supported; some devices
* will support both touches and mice and both types of events will always need to
* be handled.
*
* After a touch is released, ignore all mouse presses for a little while. It's
* unlikely that the user will touch an element, then immediately click it.
*/
this.ignore_mouse_events_until = (new Date()).valueOf() + 500;
return;
}
}
}
DragElement.prototype.mouseup_event = function(event)
{
if(!event.isLeftClick())
return;
this.stop_dragging(event, false);
}
/* If cancelling is true, we're stopping for a reason other than an explicit mouse/touch
* release. */
DragElement.prototype.stop_dragging = function(event, cancelling)
{
if(this.dragging)
{
this.dragging = false;
$(document.body).removeClassName(this.overriden_drag_class || "dragging");
$(document.body).addClassName("not-dragging");
if(this.options.onenddrag)
this.options.onenddrag(this);
}
this.drag_handlers.each(function(h) { h.stop(); });
this.drag_handlers = [];
this.dragging_touch_identifier = null;
if(this.options.onup)
this.options.onup({
dragger: this,
latest_event: event,
cancelling: cancelling
});
}
DragElement.prototype.click_event = function(event)
{
/* If this click was part of a drag, cancel the click. */
if(this.dragged)
event.stop();
this.dragged = false;
}
DragElement.prototype.dragstart_event = function(event)
{
event.preventDefault();
}
DragElement.prototype.selectstart_event = function(event)
{
/* We need to stop selectstart to prevent drag selection in Chrome. However, we need
* to work around a bug: if we stop the event of an INPUT element, it'll prevent focusing
* on that element entirely. We shouldn't prevent selecting the text in the input box,
* either. */
if(event.target.tagName != "INPUT")
event.stop();
}
/* When element is dragged, the document moves around it. If scroll_element is true, the
* element should be positioned (eg. position: absolute), and the element itself will be
* scrolled. */
WindowDragElement = function(element, extra_opts)
{
this.element = element;
/* MI: Changed the way startdrag is called so
* additional onstartdrag callbacks can be passed.
*/
var startdrag = this.startdrag.bind(this);
var extra_startdrag = extra_opts.startdrag || false;
this.dragger = new DragElement(element, jQuery.extend({}, extra_opts || {}, {
no_touch: true,
ondrag: this.ondrag.bind(this),
onstartdrag: function() {
startdrag();
if (extra_startdrag)
extra_startdrag();
},
}));
}
WindowDragElement.prototype.startdrag = function()
{
this.scroll_anchor_x = (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft);
this.scroll_anchor_y = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
}
WindowDragElement.prototype.ondrag = function(e)
{
var scrollLeft = this.scroll_anchor_x - e.aX;
var scrollTop = this.scroll_anchor_y - e.aY;
scrollTo(scrollLeft, scrollTop);
}
/* element should be positioned (eg. position: absolute). When the element is dragged,
* scroll it around. */
WindowDragElementAbsolute = function(element, ondrag_callback)
{
this.element = element;
this.ondrag_callback = ondrag_callback;
this.disabled = false;
this.dragger = new DragElement(element, {
ondrag: this.ondrag.bind(this),
onstartdrag: this.startdrag.bind(this)
});
}
WindowDragElementAbsolute.prototype.set_disabled = function(b) { this.disabled = b; }
WindowDragElementAbsolute.prototype.startdrag = function()
{
if(this.disabled)
return true; /* cancel */
this.scroll_anchor_x = this.element.offsetLeft;
this.scroll_anchor_y = this.element.offsetTop;
return false;
}
WindowDragElementAbsolute.prototype.ondrag = function(e)
{
var scrollLeft = this.scroll_anchor_x + e.aX;
var scrollTop = this.scroll_anchor_y + e.aY;
/* Don't allow dragging the image off the screen; there'll be no way to
* get it back. */
var window_size = getWindowSize();
var min_visible = Math.min(100, this.element.offsetWidth);
scrollLeft = Math.max(scrollLeft, min_visible - this.element.offsetWidth);
scrollLeft = Math.min(scrollLeft, window_size.width - min_visible);
var min_visible = Math.min(100, this.element.offsetHeight);
scrollTop = Math.max(scrollTop, min_visible - this.element.offsetHeight);
scrollTop = Math.min(scrollTop, window_size.height - min_visible);
this.element.setStyle({left: scrollLeft + "px", top: scrollTop + "px"});
if(this.ondrag_callback)
this.ondrag_callback();
}
WindowDragElementAbsolute.prototype.destroy = function()
{
this.dragger.destroy();
}
/* Track the focused element, and store it in document.focusedElement.. */
function TrackFocus()
{
document.focusedElement = null;
if(document.addEventListener)
{
document.addEventListener("focus", function(e)
{
document.focusedElement = e.target;
}.bindAsEventListener(this), true);
}
document.observe("focusin", function(event) {
document.focusedElement = event.srcElement;
}.bindAsEventListener(this));
}
function FormatError(message, file, line, exc, info)
{
var report = "";
report += "Error: " + message + "\n";
if(info != null)
report += info;
report += "UA: " + window.navigator.userAgent + "\n";
report += "URL: " + window.location.href + "\n";
var cookies = document.cookie;
cookies = cookies.replace(/(pass_hash)=[0-9a-f]{40}/, "$1=(removed)");
try {
report += "Cookies: " + decodeURIComponent(cookies) + "\n";
} catch(e) {
report += "Cookies (couldn't decode): " + cookies + "\n";
}
if("localStorage" in window)
{
/* FF's localStorage is broken; we can't iterate over it. Name the keys we use explicitly. */
var keys = [];
try {
for(key in localStorage)
keys.push(keys);
} catch(e) {
keys = ["sample_urls", "sample_url_fifo", "tag_data", "tag_data_version", "recent_tags", "tag_data_format"];
}
for(var i = 0; i < keys.length; ++i)
{
var key = keys[i];
try {
if(!(key in localStorage))
continue;
var data = localStorage[key];
var length = data.length;
if(data.length > 512)
data = data.slice(0, 512);
report += "localStorage." + key + " (size: " + length + "): " + data + "\n";
} catch(e) {
report += "(ignored errors retrieving localStorage for " + key + ": " + e + ")\n";
}
}
}
if(exc && exc.stack)
report += "\n" + exc.stack + "\n";
if(file)
{
report += "File: " + file;
if(line != null)
report += " line " + line + "\n";
}
return report;
}
var reported_error = false;
function ReportError(message, file, line, exc, info)
{
if(navigator.userAgent.match(/.*MSIE [67]/))
return;
/* Only attempt to report an error once per page load. */
if(reported_error)
return;
reported_error = true;
/* Only report an error at most once per hour. */
if(document.cookie.indexOf("reported_error=1") != -1)
return;
var expiration = new Date();
expiration.setTime(expiration.getTime() + (60 * 60 * 1000));
document.cookie = "reported_error=1; path=/; expires=" + expiration.toGMTString();
var report = FormatError(exc? exc.message:message, file, line, exc, info);
try {
new Ajax.Request("/user/error.json", {
parameters: { report: report }
});
} catch(e) {
alert("Error: " + e);
}
}
function LocalStorageDisabled()
{
if(!("localStorage" in window))
return "unsupported";
var cleared_storage = false;
while(1)
{
try {
/* We can't just access a property to test it; that detects it being disabled in FF, but
* not in Chrome. */
localStorage.x = 1;
if(localStorage.x != 1)
throw "disabled";
delete localStorage.x;
return null;
} catch(e) {
/* If local storage is full, we may not be able to even run this test. If that ever happens
* something is wrong, so after a failure clear localStorage once and try again. This call
* may fail, too; ignore that and we'll catch the problem on the next try. */
if(!cleared_storage)
{
cleared_storage = true;
try {
localStorage.clear();
} catch(e) { }
continue;
}
if(navigator.userAgent.indexOf("Gecko/") != -1)
{
// If the user or an extension toggles about:config dom.storage.enabled, this happens:
if(e.message.indexOf("Security error") != -1)
return "ff-disabled";
}
/* Chrome unhelpfully reports QUOTA_EXCEEDED_ERR if local storage is disabled, which
* means we can't easily detect it being disabled and show a tip to the user. */
return "error";
}
}
}
/* Chrome 10/WebKit braindamage; stop breaking things intentionally just to create
* busywork for everyone else: */
if(!("URL" in window) && "webkitURL" in window)
window.URL = window.webkitURL;
/* For Chrome 9: */
if("createObjectURL" in window && !("URL" in window))
{
window.URL = {
createObjectURL: function(blob) { return window.createObjectURL(blob); },
revokeObjectURL: function(url) { window.revokeObjectURL(url); }
}
}
/* Allow CSS styles for WebKit. */
if(navigator.userAgent.indexOf("AppleWebKit/") != -1)
document.documentElement.className += " webkit";