mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
356 lines
11 KiB
JavaScript
356 lines
11 KiB
JavaScript
|
/* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Distributed under the BSD license:
|
||
|
*
|
||
|
* Copyright (c) 2010, Ajax.org B.V.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
* * Neither the name of Ajax.org B.V. nor the
|
||
|
* names of its contributors may be used to endorse or promote products
|
||
|
* derived from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
define(function(require, exports, module) {
|
||
|
"use strict";
|
||
|
|
||
|
var keys = require("./keys");
|
||
|
var useragent = require("./useragent");
|
||
|
var dom = require("./dom");
|
||
|
|
||
|
exports.addListener = function(elem, type, callback) {
|
||
|
if (elem.addEventListener) {
|
||
|
return elem.addEventListener(type, callback, false);
|
||
|
}
|
||
|
if (elem.attachEvent) {
|
||
|
var wrapper = function() {
|
||
|
callback.call(elem, window.event);
|
||
|
};
|
||
|
callback._wrapper = wrapper;
|
||
|
elem.attachEvent("on" + type, wrapper);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
exports.removeListener = function(elem, type, callback) {
|
||
|
if (elem.removeEventListener) {
|
||
|
return elem.removeEventListener(type, callback, false);
|
||
|
}
|
||
|
if (elem.detachEvent) {
|
||
|
elem.detachEvent("on" + type, callback._wrapper || callback);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Prevents propagation and clobbers the default action of the passed event
|
||
|
*/
|
||
|
exports.stopEvent = function(e) {
|
||
|
exports.stopPropagation(e);
|
||
|
exports.preventDefault(e);
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
exports.stopPropagation = function(e) {
|
||
|
if (e.stopPropagation)
|
||
|
e.stopPropagation();
|
||
|
else
|
||
|
e.cancelBubble = true;
|
||
|
};
|
||
|
|
||
|
exports.preventDefault = function(e) {
|
||
|
if (e.preventDefault)
|
||
|
e.preventDefault();
|
||
|
else
|
||
|
e.returnValue = false;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* @return {Number} 0 for left button, 1 for middle button, 2 for right button
|
||
|
*/
|
||
|
exports.getButton = function(e) {
|
||
|
if (e.type == "dblclick")
|
||
|
return 0;
|
||
|
if (e.type == "contextmenu" || (e.ctrlKey && useragent.isMac))
|
||
|
return 2;
|
||
|
|
||
|
// DOM Event
|
||
|
if (e.preventDefault) {
|
||
|
return e.button;
|
||
|
}
|
||
|
// old IE
|
||
|
else {
|
||
|
return {1:0, 2:2, 4:1}[e.button];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
exports.capture = function(el, eventHandler, releaseCaptureHandler) {
|
||
|
function onMouseUp(e) {
|
||
|
eventHandler && eventHandler(e);
|
||
|
releaseCaptureHandler && releaseCaptureHandler(e);
|
||
|
|
||
|
exports.removeListener(document, "mousemove", eventHandler, true);
|
||
|
exports.removeListener(document, "mouseup", onMouseUp, true);
|
||
|
exports.removeListener(document, "dragstart", onMouseUp, true);
|
||
|
}
|
||
|
|
||
|
exports.addListener(document, "mousemove", eventHandler, true);
|
||
|
exports.addListener(document, "mouseup", onMouseUp, true);
|
||
|
exports.addListener(document, "dragstart", onMouseUp, true);
|
||
|
|
||
|
return onMouseUp;
|
||
|
};
|
||
|
|
||
|
exports.addMouseWheelListener = function(el, callback) {
|
||
|
if ("onmousewheel" in el) {
|
||
|
var factor = 8;
|
||
|
exports.addListener(el, "mousewheel", function(e) {
|
||
|
if (e.wheelDeltaX !== undefined) {
|
||
|
e.wheelX = -e.wheelDeltaX / factor;
|
||
|
e.wheelY = -e.wheelDeltaY / factor;
|
||
|
} else {
|
||
|
e.wheelX = 0;
|
||
|
e.wheelY = -e.wheelDelta / factor;
|
||
|
}
|
||
|
callback(e);
|
||
|
});
|
||
|
} else if ("onwheel" in el) {
|
||
|
exports.addListener(el, "wheel", function(e) {
|
||
|
e.wheelX = (e.deltaX || 0) * 5;
|
||
|
e.wheelY = (e.deltaY || 0) * 5;
|
||
|
callback(e);
|
||
|
});
|
||
|
} else {
|
||
|
exports.addListener(el, "DOMMouseScroll", function(e) {
|
||
|
if (e.axis && e.axis == e.HORIZONTAL_AXIS) {
|
||
|
e.wheelX = (e.detail || 0) * 5;
|
||
|
e.wheelY = 0;
|
||
|
} else {
|
||
|
e.wheelX = 0;
|
||
|
e.wheelY = (e.detail || 0) * 5;
|
||
|
}
|
||
|
callback(e);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
exports.addMultiMouseDownListener = function(el, timeouts, eventHandler, callbackName) {
|
||
|
var clicks = 0;
|
||
|
var startX, startY, timer;
|
||
|
var eventNames = {
|
||
|
2: "dblclick",
|
||
|
3: "tripleclick",
|
||
|
4: "quadclick"
|
||
|
};
|
||
|
|
||
|
exports.addListener(el, "mousedown", function(e) {
|
||
|
if (exports.getButton(e) != 0) {
|
||
|
clicks = 0;
|
||
|
} else if (e.detail > 1) {
|
||
|
clicks++;
|
||
|
if (clicks > 4)
|
||
|
clicks = 1;
|
||
|
} else {
|
||
|
clicks = 1;
|
||
|
}
|
||
|
if (useragent.isIE) {
|
||
|
var isNewClick = Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5;
|
||
|
if (isNewClick) {
|
||
|
clicks = 1;
|
||
|
}
|
||
|
if (clicks == 1) {
|
||
|
startX = e.clientX;
|
||
|
startY = e.clientY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
eventHandler[callbackName]("mousedown", e);
|
||
|
|
||
|
if (clicks > 4)
|
||
|
clicks = 0;
|
||
|
else if (clicks > 1)
|
||
|
return eventHandler[callbackName](eventNames[clicks], e);
|
||
|
});
|
||
|
|
||
|
if (useragent.isOldIE) {
|
||
|
exports.addListener(el, "dblclick", function(e) {
|
||
|
clicks = 2;
|
||
|
if (timer)
|
||
|
clearTimeout(timer);
|
||
|
timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600);
|
||
|
eventHandler[callbackName]("mousedown", e);
|
||
|
eventHandler[callbackName](eventNames[clicks], e);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function normalizeCommandKeys(callback, e, keyCode) {
|
||
|
var hashId = 0;
|
||
|
if ((useragent.isOpera && !("KeyboardEvent" in window)) && useragent.isMac) {
|
||
|
hashId = 0 | (e.metaKey ? 1 : 0) | (e.altKey ? 2 : 0)
|
||
|
| (e.shiftKey ? 4 : 0) | (e.ctrlKey ? 8 : 0);
|
||
|
} else {
|
||
|
hashId = 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0)
|
||
|
| (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
|
||
|
}
|
||
|
|
||
|
if (!useragent.isMac && pressedKeys) {
|
||
|
if (pressedKeys[91] || pressedKeys[92])
|
||
|
hashId |= 8;
|
||
|
if (pressedKeys.altGr) {
|
||
|
if ((3 & hashId) != 3)
|
||
|
pressedKeys.altGr = 0
|
||
|
else
|
||
|
return;
|
||
|
}
|
||
|
if (keyCode === 18 || keyCode === 17) {
|
||
|
var location = e.location || e.keyLocation;
|
||
|
if (keyCode === 17 && location === 1) {
|
||
|
ts = e.timeStamp;
|
||
|
} else if (keyCode === 18 && hashId === 3 && location === 2) {
|
||
|
var dt = -ts;
|
||
|
ts = e.timeStamp;
|
||
|
dt += ts;
|
||
|
if (dt < 3)
|
||
|
pressedKeys.altGr = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (keyCode in keys.MODIFIER_KEYS) {
|
||
|
switch (keys.MODIFIER_KEYS[keyCode]) {
|
||
|
case "Alt":
|
||
|
hashId = 2;
|
||
|
break;
|
||
|
case "Shift":
|
||
|
hashId = 4;
|
||
|
break;
|
||
|
case "Ctrl":
|
||
|
hashId = 1;
|
||
|
break;
|
||
|
default:
|
||
|
hashId = 8;
|
||
|
break;
|
||
|
}
|
||
|
keyCode = 0;
|
||
|
}
|
||
|
|
||
|
if (hashId & 8 && (keyCode === 91 || keyCode === 93)) {
|
||
|
keyCode = 0;
|
||
|
}
|
||
|
|
||
|
if (!hashId && keyCode === 13) {
|
||
|
if (e.location || e.keyLocation === 3) {
|
||
|
callback(e, hashId, -keyCode)
|
||
|
if (e.defaultPrevented)
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// If there is no hashId and the keyCode is not a function key, then
|
||
|
// we don't call the callback as we don't handle a command key here
|
||
|
// (it's a normal key/character input).
|
||
|
if (!hashId && !(keyCode in keys.FUNCTION_KEYS) && !(keyCode in keys.PRINTABLE_KEYS)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
return callback(e, hashId, keyCode);
|
||
|
}
|
||
|
|
||
|
var pressedKeys = null;
|
||
|
var ts = 0;
|
||
|
exports.addCommandKeyListener = function(el, callback) {
|
||
|
var addListener = exports.addListener;
|
||
|
if (useragent.isOldGecko || (useragent.isOpera && !("KeyboardEvent" in window))) {
|
||
|
// Old versions of Gecko aka. Firefox < 4.0 didn't repeat the keydown
|
||
|
// event if the user pressed the key for a longer time. Instead, the
|
||
|
// keydown event was fired once and later on only the keypress event.
|
||
|
// To emulate the 'right' keydown behavior, the keyCode of the initial
|
||
|
// keyDown event is stored and in the following keypress events the
|
||
|
// stores keyCode is used to emulate a keyDown event.
|
||
|
var lastKeyDownKeyCode = null;
|
||
|
addListener(el, "keydown", function(e) {
|
||
|
lastKeyDownKeyCode = e.keyCode;
|
||
|
});
|
||
|
addListener(el, "keypress", function(e) {
|
||
|
return normalizeCommandKeys(callback, e, lastKeyDownKeyCode);
|
||
|
});
|
||
|
} else {
|
||
|
var lastDefaultPrevented = null;
|
||
|
|
||
|
addListener(el, "keydown", function(e) {
|
||
|
pressedKeys[e.keyCode] = true;
|
||
|
var result = normalizeCommandKeys(callback, e, e.keyCode);
|
||
|
lastDefaultPrevented = e.defaultPrevented;
|
||
|
return result;
|
||
|
});
|
||
|
|
||
|
addListener(el, "keypress", function(e) {
|
||
|
if (lastDefaultPrevented && (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey)) {
|
||
|
exports.stopEvent(e);
|
||
|
lastDefaultPrevented = null;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
addListener(el, "keyup", function(e) {
|
||
|
pressedKeys[e.keyCode] = null;
|
||
|
});
|
||
|
|
||
|
if (!pressedKeys) {
|
||
|
pressedKeys = Object.create(null);
|
||
|
addListener(window, "focus", function(e) {
|
||
|
pressedKeys = Object.create(null);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (window.postMessage && !useragent.isOldIE) {
|
||
|
var postMessageId = 1;
|
||
|
exports.nextTick = function(callback, win) {
|
||
|
win = win || window;
|
||
|
var messageName = "zero-timeout-message-" + postMessageId;
|
||
|
exports.addListener(win, "message", function listener(e) {
|
||
|
if (e.data == messageName) {
|
||
|
exports.stopPropagation(e);
|
||
|
exports.removeListener(win, "message", listener);
|
||
|
callback();
|
||
|
}
|
||
|
});
|
||
|
win.postMessage(messageName, "*");
|
||
|
};
|
||
|
}
|
||
|
|
||
|
|
||
|
exports.nextFrame = window.requestAnimationFrame ||
|
||
|
window.mozRequestAnimationFrame ||
|
||
|
window.webkitRequestAnimationFrame ||
|
||
|
window.msRequestAnimationFrame ||
|
||
|
window.oRequestAnimationFrame;
|
||
|
|
||
|
if (exports.nextFrame)
|
||
|
exports.nextFrame = exports.nextFrame.bind(window);
|
||
|
else
|
||
|
exports.nextFrame = function(callback) {
|
||
|
setTimeout(callback, 17);
|
||
|
};
|
||
|
});
|