frisch's UserScript Extender

Extends the document with a new namespace for user script crosswide functions to utilize. Has no use alone but can be accessed by other scripts using document.fExt

  1. // ==UserScript==
  2. // @name frisch's UserScript Extender
  3. // @namespace http://null.frisch-live.de/
  4. // @version 0.76
  5. // @description Extends the document with a new namespace for user script crosswide functions to utilize. Has no use alone but can be accessed by other scripts using document.fExt
  6. // @author frisch
  7. // @include *
  8. // @require http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
  9. // @grant GM_setClipboard
  10. // ==/UserScript==
  11. if(document.fExt === undefined) {
  12. console.log("Initializing frisch's UserScript Extender...");
  13.  
  14. // Initialization
  15. fExt = {};
  16. fExt.jq = $.noConflict();
  17. // Overwrites the contains expression to be case-insensitive
  18. fExt.jq.expr[':'].contains = function(a, i, m) {
  19. return fExt.jq(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
  20. };
  21. fExt.settings = {
  22. minShowTime: 3000, // in milliseconds
  23. maxShowTime: 7000, // in milliseconds
  24. position: "BottomRight", // Position for the popup menu: TopRight, TopLeft, BottomRight, BottomLeft
  25. animationType: "fade", // Animation for the popup menu: slide, fade, none
  26. animationSpeed: 750,
  27. customContextMenu: true,
  28. hideContextMenuOnLeave: true, // automatically fades out the custom context menu when it loses focus, otherwhise only hides when you click somewhere
  29. hideContextMenuDistance: 50, // the distance the cursor has to travel outside of the context menu to hide it
  30. progressType: "progressbar", // Possible values: hourglass, progressbar
  31. toleranceX: 20, // pixels as tolerance for the context menu, adjust the position slightly
  32. toleranceY: 30, // pixels as tolerance for the context menu, adjust the position slightly
  33. ctxActorMark: true, // set to true to highlight the actor of the context menu when active
  34. ctxActorMarkThickness: 2, // set the thickness of the highlighting border as pixel
  35. };
  36. fExt.popupQueue = [];
  37. fExt.popping = false;
  38.  
  39. var hourGlass0 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICgkAYfvBAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAKpJREFUSMetlcEOwyAMQ+39/z97h0nrOkjihPZAAYlXVPALcPgQgE7WEzhA8LODKYLfZoTg7dVGcOm0ENx2bQTDgYVgOiwRLCdSBK2pEEHAB6wI2pvaIgj0AReC87SpOtbXEz5Ivq97Zrs/UWvsO8eovTnci6RYPs5VVu6vKkyqFZjFWZ5FI6EYOkiEIlNIgdLUUOJGqmpK+U/rGpSFn8KiYWHiA8X1NM14AxEBKx1JZtGVAAAAAElFTkSuQmCC";
  40. var hourGlass25 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICgR+0Id8AAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAKhJREFUSMetlUkOxCAMBLv4/589h5koG3hjckgAydVtR8bS5oMk24lH+iKsoSx+3zqC++tA1C1cAQ0LT0AewXSZRrDcpBC42xBBeOAiSB0tEUh5wBtB2tQUgVQHnAj63WbRbx3/uA8cfbv3bLWIlxJQT+FQDy4LInUFLkYUHl1ZI6PuIciGrxIZidxdF+TV5y6GH05YC2rqbxc0p8JjsFi/k7aH62436wPGSjUSU11z+AAAAABJRU5ErkJggg==";
  41. var hourGlass50 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICTqUnMkUAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAKpJREFUSMe1lMsOgzAMBHfy///sHopEQsGv0BxAMtpZJ8ErbS4k2Y4e6YuwhrM43nUE66OKmJVM9SRiNWb5lkBc+r4ACi08AZIteAAfQVhwEaRKjwikPOAXQbqpWwRSHXAi6E+bRdc63sgDx9/Wma0e4nQE1LdwyAlmjMhdQRcjkkeRRcbd62L48jhvRuzupx7lPHIizTo/IC35Taxbdwxoy6d7tb8NY2Z9AJxfMxVU9/UgAAAAAElFTkSuQmCC";
  42. var hourGlass75 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICTPtQHGwAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAK1JREFUSMetlUkSgzAMBKf5/5/FIUnFG9ZiOEAVaHqE8Qjp8ECSneiRPggrOIvvNY+gP2URrZLmfhDRG9M9CyCGvgeAi2DSMNVsECwULOoeECzrWVptW4gANi1EATOCcFNLBFIe8EdQT5t5O+t6Yx5s/K3PbHYRmyUg/wo/dycgeO5yurh8+b4LEkkIhsly342k3Bkolt89FOQPQ9Vqe5iifJhxVk/S8c/1NM26AScHLxnJI48dAAAAAElFTkSuQmCC";
  43. var hourGlass100 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICSxgSHxFAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAKpJREFUSMetlUsShDAIBem5/52ZxbhIIhA+40Kr9L0GCSEiwwsR0YkfkR9CG5GF51lHsN+qiNXJ8j6J2AOzfUsgjrwPwBXBy8NLEyAwHBg6B4GpxwxlIHDUONkeCFwtbr0WBIGSoOQPglBHuOiaUl0Ql876/GMejH6BhL1ZRM0pSdqLjaT5fCnYk5tJa+tG0X4ZKFrvHhp2Z6hqr4dp2o8xof2dND5cp7tZvgvYKh27llJDAAAAAElFTkSuQmCC";
  44.  
  45. var pbar0 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCSsaEe8UvgAAALZJREFUSMfdll0OxCAIhAfDjfT+J5Az0adumo1FUXSTnacmVj8ZflpSVQAAEaGlnPPnWUTg1X0+v73wBFjro/C0AvGIIwAj0bF1yHNjD+KKqAWZsam1h98gMwDrYrya7BF7Tes8kJGLpRMQACBVBREpNkpVKeGQvhuWIqtQRHQ5R16FgXrDNQzUs/aYdf9X3nR/akspZqJnh2yt9UflPfPj4Sl59vTCSl/xzii29ZHlSIoaMb31C3teW2ty8diEAAAAAElFTkSuQmCC";
  46. var pbar25 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCSsrQDEUhAAAALZJREFUSMfdlssNxCAMRMfIHUH/FcQ1zZ6yyoEYzG+lnVMkAg+PP4mQBACICGrKOX+fzQxR3efr2wtPgLfeC08zkIh0BaAnOvUOeW5sQUIR1SAjNtX26BtkBOBdTGeT3WOva10E0nOxdAICAEISIkJsFElJOCTtqCIZrUIz43SOoloGag3XZaCWtces+7/ylvtTW0pxEz06ZK/r+lF5j/x4REpeI70w01e6M4ptfeQ5klaNmNb6B29WW2uHr2HMAAAAAElFTkSuQmCC";
  47. var pbar50 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCSs7XYYE4AAAALVJREFUSMfdlssNxCAMRMfIHZn+Kwg1zZ6yyoEYzG+lnVMkAg+PP4mQBACICGoys+9zKQVR3efr2wtPgLfeC08zkIh0BaAnOvUOeW5sQUIR1SAjNtX26BtkBOBdTGeT3WOva10E0nOxdAICAEISIkJsFElJOKThHJmZdNjL6RxFtQzUGq7LQK3qO2bd/5W33J/anLOb6NEhe13Xj8p75McjUvIa6YWZvtKdUWzrI8+RtGrEtNY/mktYa4XsqhcAAAAASUVORK5CYII=";
  48. var pbar75 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCSwJ2hDDpwAAAK1JREFUSMftllEOAyEIRAfDjfD+J9AzsV/bbBpFQWuTpvNlgvJwNCipKgCAiNCSiLzGtVZ4defn3oQnwIrPwtMKxCPeAZjZHVtJngtHENeOWpCITa013INEAFZhvHrYM/aa1nkgM4WlExAAIFUFESk+KFWlhEPinclEhN7s1eUz8uoPCuv3rjfdT23O2ewC0SZbSvnSZYh8PDydnXuBnZDtLcgqNp2AuEAjW0fxC+YyVWuwau4lAAAAAElFTkSuQmCC";
  49. var pbar100 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCTAYVte+CAAAAKJJREFUSMftllEOxCAIRAfDjfT+J9AzzX510w+loLabbDpfJigPR4MKSQCAiKCnnPN33FpDVEd+HU04A6y4F55WIBHpDoBnd2olOS+8goR21IPM2NRboyPIDMAqTFcP22OvaV0E4iksPQEBACEJESFuFElJeEgv6AX9MUiOp7aUYnaB2SZba/2RdTMfj0hn11FgJ2T5mfAAbrl1liNpRxJP/APHYkptRNVvhwAAAABJRU5ErkJggg==";
  50.  
  51. var loader0, loader25, loader50, loader75, loader100;
  52.  
  53. switch(fExt.settings.progressType){
  54. case "progressbar":
  55. loader0 = pbar0;
  56. loader25 = pbar25;
  57. loader50 = pbar50;
  58. loader75 = pbar75;
  59. loader100 = pbar100;
  60. break;
  61. case "hourglass":
  62. loader0 = hourGlass0;
  63. loader25 = hourGlass25;
  64. loader50 = hourGlass50;
  65. loader75 = hourGlass75;
  66. loader100 = hourGlass100;
  67. break;
  68. default:
  69. loader0 = hourGlass0;
  70. loader25 = hourGlass25;
  71. loader50 = hourGlass50;
  72. loader75 = hourGlass75;
  73. loader100 = hourGlass100;
  74. break;
  75. }
  76.  
  77. var topZIndex = 0;
  78. fExt.TopZIndex = function() {
  79. return topZIndex;
  80. };
  81. fExt.jq("*").each(function(){
  82. var thisZIndex = parseInt(fExt.jq(this).css('z-index'));
  83. if(!isNaN(thisZIndex) && thisZIndex > topZIndex)
  84. topZIndex = thisZIndex + 1;
  85. });
  86.  
  87. // Custom Elements
  88. fExt.fExtPopup = fExt.jq('<div id="fExtPopup" class="fExtElement" style="display:none; position:fixed; width:auto; height:auto; background-color:#545454; padding:10px; border-color:white; border-style:groove; color:white; z-index: ' + topZIndex + '; text-align:center; font-size:12px;"></div>');
  89. fExt.jq("body").append(fExt.fExtPopup);
  90.  
  91. fExt.fExtMessage = fExt.jq('<div id="fExtMessage" class="fExtElement" style="display:none; position:fixed; width:auto; height:auto; background-color:#545454; padding:25px; border-color:white; border-style:groove; color:white; z-index: ' + topZIndex + '; text-align:center; font-size:large; padding:20px;"></div>');
  92. fExt.fExtMessageText = fExt.jq('<span style="display: inline-block;"></span>');
  93. fExt.fExtMessageText.appendTo(fExt.fExtMessage);
  94. fExt.fExtMessage.appendTo("body");
  95.  
  96. // Functions
  97. fExt.setLoading = function(percentage){
  98. if(percentage >= 100){
  99. fExt.jq("link[rel='icon']").attr("href", loader100);
  100. fExt.jq("link[rel='shortcut icon']").attr("href", loader100);
  101.  
  102. setTimeout(function(){ fExt.setLoading(-1); },fExt.settings.minShowTime);
  103. }
  104. else if(percentage >= 75){
  105. fExt.jq("link[rel='icon']").attr("href", loader75);
  106. fExt.jq("link[rel='shortcut icon']").attr("href", loader75);
  107. }
  108. else if(percentage >= 50){
  109. fExt.jq("link[rel='icon']").attr("href", loader50);
  110. fExt.jq("link[rel='shortcut icon']").attr("href", loader50);
  111. }
  112. else if(percentage >= 25){
  113. fExt.jq("link[rel='icon']").attr("href", loader25);
  114. fExt.jq("link[rel='shortcut icon']").attr("href", loader25);
  115. }
  116. else if(percentage >= 0){
  117. fExt.jq("link[rel='icon']").attr("href", loader0);
  118. fExt.jq("link[rel='shortcut icon']").attr("href", loader0);
  119. }
  120. else {
  121. fExt.jq("link[rel='icon']").attr("href", siteFavIconHref);
  122. fExt.jq("link[rel='shortcut icon']").attr("href", siteFavIconHref);
  123. }
  124. };
  125.  
  126. fExt.enqueuePopup = function(msg) {
  127. fExt.popupQueue.push(msg);
  128. };
  129.  
  130. fExt.dequeuePopup = function() {
  131. var rv;
  132. if(fExt.popupQueue.length > 0){
  133. rv = fExt.popupQueue[0];
  134. fExt.popupQueue.splice(0, 1);
  135. }
  136. return rv;
  137. };
  138.  
  139. var ignoreZIndexelementIDs = ["fExtPopup","fExtMessage","fExtContextMenu","fExtUtilSelectorBox"];
  140. fExt.reassignZIndex = function(){
  141. fExt.jq("*").each(function(){
  142. var jqElement = fExt.jq(this);
  143. if(jqElement.css('z-index') >= topZIndex && fExt.jq.inArray(this.id, ignoreZIndexelementIDs) < 0) {
  144. jqElement.css("z-index", topZIndex - 1);
  145. }
  146. });
  147. };
  148.  
  149. fExt.createStyle = function(newClass) {
  150. fExt.jq( "<style>" + newClass + "</style>" ).appendTo("head");
  151. };
  152.  
  153. fExt.center = function (element,w) {
  154. if(w !== undefined)
  155. element.width(w);
  156.  
  157. element.css("position", "fixed")
  158. .css("top", ((fExt.jq(window).height() - fExt.jq(element).outerHeight()) / 2) + "px")
  159. .css("left", ((fExt.jq(window).width() - fExt.jq(element).outerWidth()) / 2) + "px");
  160. };
  161.  
  162. fExt.popup = function(msg) {
  163. if(msg !== undefined){
  164. if(fExt.popping){
  165. fExt.enqueuePopup(msg);
  166. return;
  167. }
  168. fExt.popping = true;
  169. fExt.fExtPopup.html(msg);
  170.  
  171. fExt.show(fExt.fExtPopup);
  172.  
  173. var showTime = msg.length * 50;
  174.  
  175. if(showTime < fExt.settings.minShowTime)
  176. showTime = fExt.settings.minShowTime;
  177. else if(showTime > fExt.settings.maxShowTime)
  178. shotTime = fExt.settings.maxShowTime;
  179.  
  180. setTimeout(function(){
  181. fExt.hide(fExt.fExtPopup);
  182. setTimeout(function(){
  183. fExt.fExtPopup.text('');
  184. fExt.popping = false;
  185. fExt.popup(fExt.dequeuePopup());
  186. },fExt.settings.animationSpeed);
  187. },showTime);
  188. }
  189. };
  190.  
  191. fExt.clipboard = function(action, text) {
  192. var clipboardCopy, retVal;
  193. action = action.toLowerCase();
  194.  
  195. if(action === "copy") {
  196. GM_setClipboard(text);
  197. return true;
  198. }
  199.  
  200. clipboardCopy = document.createElement('textarea');
  201. document.body.appendChild(clipboardCopy);
  202. clipboardCopy.value = text;
  203. clipboardCopy.select();
  204.  
  205. var msg;
  206. try {
  207. var successful = document.execCommand(action);
  208. if(successful)
  209. msg = action + " successful";
  210. else
  211. msg = "Could not perform Clipboard-Action " + action;
  212.  
  213. if(clipboardCopy.value.length <= 50)
  214. msg += " (" + clipboardCopy.value + ")";
  215.  
  216. document.fExt.popup(msg);
  217. }
  218. catch (err) {
  219. document.fExt.popup("Error on Clipboard-Action " + action +": " + err);
  220. }
  221.  
  222. if(clipboardCopy)
  223. clipboardCopy.remove();
  224. };
  225.  
  226. fExt.message = function(msg) {
  227. if(msg !== undefined && msg.length > 0){
  228. fExt.fExtMessageText.text(msg);
  229.  
  230. if(!fExt.fExtMessage.is(":visible"))
  231. fExt.show(fExt.fExtMessage);
  232.  
  233. fExt.center(fExt.jq("div#fExtMessage"));
  234. }
  235. else {
  236. fExt.hide(fExt.fExtMessage);
  237. setTimeout(function(){
  238. fExt.fExtMessageText.text('');
  239. },fExt.settings.animationSpeed);
  240. }
  241. };
  242.  
  243. fExt.rotate = function(element, rotation) {
  244. var jqEl = fExt.jq(element);
  245. var degree = jqEl.data('rotation');
  246.  
  247. if (!degree)
  248. degree = 0;
  249.  
  250. degree += rotation;
  251.  
  252. jqEl.css('-webkit-transform','rotate(' + degree + 'deg)');
  253. jqEl.data('rotation',degree);
  254. };
  255.  
  256. fExt.zoom = function(element, zoom){
  257. var jqEl = fExt.jq(element);
  258. var zValue = parseFloat(jqEl.css('zoom'));
  259. var zAdd = parseFloat(zoom) / 100;
  260.  
  261. if(!zValue)
  262. zValue = 1.0;
  263.  
  264. var zNew = zValue + zAdd;
  265. jqEl.css('zoom', zNew);
  266. };
  267.  
  268. fExt.zoomIn = function(element, zoom){
  269. fExt.zoom(element, zoom);
  270. };
  271.  
  272. fExt.zoomOut = function(element, zoom){
  273. fExt.zoom(element, zoom * -1);
  274. };
  275.  
  276. switch(fExt.settings.animationType) {
  277. case "fade":
  278. fExt.show = function(element, speed, callback) {
  279. fExt.jq(element).fadeIn(speed >= 0 ? speed : fExt.settings.animationSpeed, callback);
  280. };
  281. fExt.hide = function(element, speed, callback) {
  282. fExt.jq(element).fadeOut(speed >= 0 ? speed : fExt.settings.animationSpeed, callback);
  283. };
  284. break;
  285. case "slide":
  286. fExt.show = function(element, speed, callback) {
  287. fExt.jq(element).slideDown(speed >= 0 ? speed : fExt.settings.animationSpeed, callback);
  288. };
  289. fExt.hide = function(element, speed, callback) {
  290. fExt.jq(element).slideUp(speed >= 0 ? speed : fExt.settings.animationSpeed, callback);
  291. };
  292. break;
  293. default:
  294. fExt.show = function(element, speed, callback) {
  295. fExt.jq(element).show(speed >= 0 ? speed : fExt.settings.animationSpeed, callback);
  296. };
  297. fExt.hide = function(element, speed, callback) {
  298. fExt.jq(element).hide(speed >= 0 ? speed : fExt.settings.animationSpeed, callback);
  299. };
  300. break;
  301. }
  302.  
  303. switch(fExt.settings.position) {
  304. case "TopRight":
  305. fExt.fExtPopup.attr("style", fExt.fExtPopup.attr("style") + "top:50px; right:50px;");
  306. break;
  307. case "TopLeft":
  308. fExt.fExtPopup.attr("style", fExt.fExtPopup.attr("style") + "top:50px; left:50px;");
  309. break;
  310. case "BottomRight":
  311. fExt.fExtPopup.attr("style", fExt.fExtPopup.attr("style") + "bottom:50px; right:50px;");
  312. break;
  313. case "BottomLeft":
  314. fExt.fExtPopup.attr("style", fExt.fExtPopup.attr("style") + "bottom:50px; left:50px;");
  315. break;
  316. default:
  317. break;
  318. }
  319.  
  320. var siteFavIcon = fExt.jq("link[rel='icon']");
  321. if(siteFavIcon === undefined) {
  322. fExt.jq("head").append('<link id="favIcon" rel="icon" href="');
  323. siteFavIcon = fExt.jq("link[rel='icon']");
  324. }
  325. else {
  326. siteFavIcon.attr("id","favIcon");
  327. }
  328. var siteFavIconHref = siteFavIcon.attr("href");
  329.  
  330. fExt.getSelection = function() {
  331. return window.getSelection().toString();
  332. };
  333.  
  334. fExt.getSource = function(element) {
  335. var retVal;
  336.  
  337. var jqTarget = fExt.jq(element);
  338. var ucTagName = element.tagName.toUpperCase();
  339.  
  340. switch(ucTagName) {
  341. case "IMG":
  342. retVal = element.src;
  343. break;
  344. case "A":
  345. retVal = element.href;
  346. break;
  347. case "VIDEO":
  348. var videoSource = jqTarget.find("source");
  349. retVal = (videoSource.length > 0) ? videoSource.get(0).src : undefined;
  350. break;
  351. case "INPUT":
  352. return jqTarget.val();
  353. default:
  354. jqTarget = fExt.jq(element).closest("a");
  355. if(jqTarget.length > 0)
  356. retVal = jqTarget.attr("href");
  357. break;
  358. }
  359.  
  360. if(retVal) {
  361. if(!retVal.match("http.*")) {
  362. retVal = window.location.origin + retVal;
  363. }
  364. }
  365. else {
  366. retVal = fExt.jq(element).text();
  367. }
  368.  
  369. return retVal;
  370. };
  371.  
  372. fExt.createStyle(".fExtLoader { background:url('" + loader50 + "') #EFF7FF no-repeat top center; }");
  373.  
  374. // ContextMenu
  375. if(fExt.settings.customContextMenu) {
  376. fExt.ctxMenu = [];
  377.  
  378. // Styles
  379. fExt.createStyle("#fExtContextMenu,#fExtContextMenu * { text-align: left !important; text-decoration: none !important; color: #fff; z-index: " + (topZIndex + 10) + "; }");
  380. fExt.createStyle("#fExtContextMenu { position: fixed; }");
  381. fExt.createStyle("#fExtContextMenu,.ctxSubList { font-size: 14px; background-color: #263238; width: 300px; height: auto; padding: 0;}");
  382. fExt.createStyle("#fExtContextMenu a,#fExtContextMenu hr,#fExtContextMenu li { width: 100%;}");
  383. fExt.createStyle("#fExtContextMenu .ctxElement { float: left; clear: left;}");
  384. fExt.createStyle("#fExtContextMenu li>a,#fExtContextMenu li>div,.ctxSubList li>a,.ctxSubList li>div { padding: 8px;}");
  385. fExt.createStyle("#fExtContextMenu li hr { margin: 0; border-style: solid; border-color: #666; border-width: 1px 0 0 0;}");
  386. fExt.createStyle("#fExtContextMenu li.ctxSub { cursor: default; font-weight: bold;}");
  387. fExt.createStyle("#fExtContextMenu li.ctxSub * { cursor: pointer; font-weight: normal;}");
  388. fExt.createStyle("#fExtContextMenu li { list-style-type: none; margin: 0 !important;}");
  389. fExt.createStyle("#fExtContextMenu li.ctxSub:hover>div.ctxSubLabel,");
  390. fExt.createStyle("#fExtContextMenu li.ctxSub:hover>div.ctxArrow {}");
  391. fExt.createStyle("#fExtContextMenu li.ctxItem { padding-left: 5px;}");
  392. fExt.createStyle("#fExtContextMenu li.ctxItem { border: none; position: initial; box-sizing: border-box; transition: all 250ms ease; }");
  393. fExt.createStyle("#fExtContextMenu li.ctxItem:hover { background: #3a7999; color: #3a7999; box-shadow: inset 0 0 0 3px #3a7999; }");
  394. fExt.createStyle("#fExtContextMenu li.ctxSub.disabled div.ctxSubLabel:hover,");
  395. fExt.createStyle("#fExtContextMenu li.ctxItem.disabled:hover a,#fExtContextMenu li.disabled div.ctxSubLabel,#fExtContextMenu li.disabled a { opacity: 0.70; font-style: italic; padding-left: 7px !important;}");
  396. fExt.createStyle("#fExtContextMenu li.ctxSeparator { display: inline-block;}");
  397. fExt.createStyle("#fExtContextMenu li.ctxSub div.ctxSubLabel { padding-left: 10px !important; float: left; clear: left;}");
  398. fExt.createStyle("#fExtContextMenu li.ctxSub div.ctxArrow { float: right; clear: right;}");
  399. fExt.createStyle("#fExtContextMenu li.ctxSub ul.ctxSubList { position: absolute; height: auto;}");
  400. fExt.createStyle(".fExtCtxHighlighter { position: absolute; background-color: red; display: block;}");
  401.  
  402. // Default variables
  403. fExt.ctxMenu.uniqueID = 1;
  404. fExt.ctxMenu.allItems = [];
  405. fExt.ctxMenu.actor = undefined;
  406. fExt.ctxMenu.actorStyles = '';
  407. fExt.ctxMenu.html = fExt.jq('<ul id="fExtContextMenu" class="ctxElement" style="display: none;"></ul>');
  408. fExt.ctxMenu.html.appendTo("body");
  409. fExt.ctxMenu.Show = function() { fExt.show(fExt.ctxMenu.html, 0); };
  410. fExt.ctxMenu.Hide = function(speed) {
  411. fExt.hide(fExt.ctxMenu.html, speed, function(){
  412. if(fExt.ctxMenu.actor) {
  413. if (fExt.settings.ctxActorMark)
  414. fExt.jq(".fExtCtxHighlighter").remove();
  415. }
  416. });
  417. };
  418.  
  419. // Private Methods
  420. ctxCtor = function(ret) {
  421. ret.ItemText = function(value) {
  422. if(value === undefined)
  423. return this.label.text();
  424.  
  425. this.label.text(value);
  426. };
  427. ret.Toggle = function(enabled) {
  428. if(enabled === true || (enabled === undefined && this.hasClass("disabled")))
  429. this.removeClass("disabled");
  430. else
  431. this.addClass("disabled");
  432. };
  433. ret.IsDisabled = function() {
  434. return this.hasClass("disabled");
  435. };
  436. ret.ID = function() {
  437. return this.itemId;
  438. };
  439. ret.Remove = function() {
  440. if(ret.hasClass("ctxSub")) {
  441. ret.Clear();
  442. }
  443. else if(ret.sub) {
  444. ret.sub.Items.splice(ret.sub.Items.indexOf(ret),1);
  445. }
  446. ret.remove();
  447. };
  448. ret.ClickCloses = true;
  449. };
  450.  
  451. getParentFrom = function(sub) {
  452. if(sub !== undefined)
  453. return fExt.jq(sub).find("ul:first");
  454. else
  455. return fExt.ctxMenu.html;
  456. };
  457.  
  458. setZIndex = function(item, parent) {
  459. var parentzIndex = parent.css("z-index");
  460. parentzIndex++;
  461. item.css("z-index", parentzIndex);
  462. item.children().each(function() { fExt.jq(this).css("z-index", parentzIndex); });
  463. };
  464.  
  465. // Public Methods
  466. fExt.ctxMenu.addItem = function(label, unused, sub) {
  467. console.log("The method 'addItem' is obsolete, please use 'addCtxItem(label, sub)'!");
  468. return fExt.ctxMenu.addCtxItem(label, sub);
  469. };
  470.  
  471. fExt.ctxMenu.addCtxItem = function(label, sub) {
  472. var ret = fExt.jq('<li class="ctxItem ctxElement"></li>');
  473. var retObj = fExt.jq('<a href="#" class="ctxElement">' + label + '</a>');
  474. retObj.appendTo(ret);
  475. ret.label = retObj;
  476.  
  477. if(sub) {
  478. sub.Add(ret);
  479. ret.sub = sub;
  480. }
  481. else {
  482. ret.appendTo(fExt.ctxMenu.html);
  483. setZIndex(ret, fExt.ctxMenu.html);
  484. }
  485.  
  486. ctxCtor(ret);
  487.  
  488. ret.Attribute = function(attribute, value) {
  489. if(value === undefined)
  490. return ret.attr(attribute);
  491. else
  492. ret.attr(attribute, value);
  493. };
  494.  
  495. ret.Trigger = function(){
  496. fExt.ctxMenu.actor = ret;
  497. if(ret.Action)
  498. ret.Action(undefined, ret, fExt.ctxMenu.actor);
  499. };
  500.  
  501. ret.Action = undefined;
  502. fExt.ctxMenu.assignID(ret);
  503. return ret;
  504. };
  505.  
  506. fExt.ctxMenu.addSub = function(label, orientation, sub) {
  507. console.log("The method 'addSub' is obsolete, please use 'addCtxSub(label, sub, orientation)'!");
  508. return fExt.ctxMenu.addCtxSub(label, sub, orientation);
  509. };
  510.  
  511. fExt.ctxMenu.addCtxSub = function(label, sub, orientation) {
  512. if(orientation !== undefined)
  513. console.log("The orientation parameter is obsolete, please use addCtxSub(label, sub).");
  514.  
  515. var ret = fExt.jq('<li class="ctxElement ctxSub"><div class="ctxElement ctxArrow">&gt;</div></li>');
  516. var list = fExt.jq('<ul class="ctxElement ctxSubList" style="display: none"></ul>');
  517. list.appendTo(ret);
  518. ret.list = list;
  519. var element = fExt.jq('<div class="ctxElement ctxSubLabel">' + label + ' </div>');
  520. element.appendTo(ret);
  521. ret.label = element;
  522.  
  523. ctxCtor(ret);
  524.  
  525. ret.Items = [];
  526.  
  527. var parentMenu = getParentFrom(sub);
  528.  
  529. if(sub)
  530. sub.Add(ret);
  531. else {
  532. ret.appendTo(parentMenu);
  533. setZIndex(ret, parentMenu);
  534. }
  535.  
  536. ret.Add = function(item) {
  537. ret.Items.push(item);
  538. item.appendTo(ret.list);
  539. setZIndex(item, parentMenu);
  540. };
  541.  
  542. ret.Clear = function() {
  543. while(ret.Items.length > 0) {
  544. ret.Items[0].remove();
  545. ret.Items.splice(0, 1);
  546. }
  547. };
  548.  
  549. return ret;
  550. };
  551.  
  552. fExt.ctxMenu.addSeparator = function(sub) {
  553. var ret = fExt.jq('<li class="ctxElement ctxSeparator"><hr/></li>');
  554.  
  555. sub.Add(ret);
  556.  
  557. return ret;
  558. };
  559.  
  560. fExt.ctxMenu.assignID = function(item) {
  561. if(item.ID() !== undefined)
  562. console.log("Item already has an ID: " + item.ID());
  563.  
  564. item.itemId = fExt.ctxMenu.uniqueID;
  565. item.data("Item-ID", item.itemId);
  566. fExt.ctxMenu.uniqueID++;
  567. fExt.ctxMenu.allItems.push(item);
  568. };
  569.  
  570. fExt.ctxMenu.getItem = function(id) {
  571. if(id !== undefined) {
  572. for(i = 0;i < fExt.ctxMenu.allItems.length; i++) {
  573. var item = fExt.ctxMenu.allItems[i];
  574. if(id === item.ID())
  575. return item;
  576. }
  577. }
  578.  
  579. return undefined;
  580. };
  581.  
  582. // Events
  583. fExt.ctxMenu.html.on("click", "li", function( event ) {
  584. var sender = fExt.ctxMenu.getItem(fExt.jq(this).data("Item-ID"));
  585. if(sender === undefined || sender.Action === undefined)
  586. return true;
  587.  
  588. event.preventDefault();
  589. if(!sender.IsDisabled())
  590. sender.Action(event, sender, fExt.ctxMenu.actor);
  591.  
  592. if(sender.ClickCloses)
  593. fExt.ctxMenu.Hide(0);
  594.  
  595. return false;
  596. });
  597.  
  598. fExt.ctxMenu.html.on("mouseenter", "li.ctxSub", function(event) {
  599. var sub = fExt.jq(this);
  600. var subOffs = sub.offset();
  601. var ul = sub.children("ul.ctxSubList:first");
  602.  
  603. if(ul.children("li.ctxItem, li.ctxSub").length === 0)
  604. return;
  605.  
  606. fExt.show(ul, 0);
  607.  
  608. var height = ul.height();
  609. var width = ul.width();
  610. var y = subOffs.top;
  611. var x = subOffs.left + sub.width();
  612. if ((x + width) >= window.screen.availWidth)
  613. x = subOffs.left - ul.width();
  614. if ((y + height + 100 - window.scrollY) >= window.screen.availHeight)
  615. y = y - height + fExt.settings.toleranceY;
  616.  
  617. if ((y - window.scrollY) < 0)
  618. y = window.scrollY;
  619. if ((x - window.scrollX) < 0)
  620. x = window.scrollX;
  621.  
  622. if (y < event.clientY && (y + height) <= event.clientY)
  623. y += fExt.settings.toleranceY;
  624.  
  625. ul.offset({ top: y, left: x});
  626. });
  627.  
  628. fExt.ctxMenu.html.on("mouseleave", "li.ctxSub", function(event) {
  629. var ul = fExt.jq(this).children("ul.ctxSubList:first");
  630. fExt.hide(ul, fExt.settings.animationSpeed);
  631. });
  632.  
  633. fExt.ctxMenu.html.on("mouseenter", "li.ctxSub", function(event) {
  634. var ul = fExt.jq(this).children("ul.ctxSubList:first");
  635.  
  636. if(fExt.jq(this).find("li.ctxItem").length > 0)
  637. fExt.show(ul, 0);
  638. });
  639.  
  640. fExt.customContextMenuHandler = function (event) {
  641. fExt.ctxMenu.actor = fExt.jq(event.target);
  642.  
  643. fExt.ctxMenu.html.trigger("fExtContextMenuOpening", [fExt.ctxMenu.actor, event]);
  644.  
  645. if(event.isDefaultPrevented())
  646. return true;
  647.  
  648. event.preventDefault();
  649.  
  650. if (fExt.settings.ctxActorMark) {
  651. fExt.jq(".fExtCtxHighlighter").remove();
  652.  
  653. var offs = fExt.ctxMenu.actor.offset();
  654. var coords = [{
  655. name: "top",
  656. x: offs.left,
  657. y: offs.top - fExt.settings.ctxActorMarkThickness,
  658. height: fExt.settings.ctxActorMarkThickness,
  659. width: fExt.ctxMenu.actor.width(),
  660. },{
  661. name: "left",
  662. x: offs.left - fExt.settings.ctxActorMarkThickness,
  663. y: offs.top,
  664. height: fExt.ctxMenu.actor.height(),
  665. width: fExt.settings.ctxActorMarkThickness,
  666. },{
  667. name: "bottom",
  668. x: offs.left,
  669. y: offs.top + fExt.ctxMenu.actor.height() + fExt.settings.ctxActorMarkThickness,
  670. height: fExt.settings.ctxActorMarkThickness,
  671. width: fExt.ctxMenu.actor.width(),
  672. },{
  673. name: "right",
  674. x: offs.left + fExt.ctxMenu.actor.width() + fExt.settings.ctxActorMarkThickness,
  675. y: offs.top,
  676. height: fExt.ctxMenu.actor.height(),
  677. width: fExt.settings.ctxActorMarkThickness,
  678. },];
  679.  
  680. var zIndex = fExt.ctxMenu.actor.css("z-index");
  681. if(zIndex != "auto")
  682. zIndex++;
  683.  
  684. for(var ind in coords){
  685. fExt.jq("<div class='fExtCtxHighlighter' style='position: absolute; z-index: " + zIndex + "; width: " + coords[ind].width + "px; height: " + coords[ind].height + "px; left: " + coords[ind].x + "px; top: " + coords[ind].y + "px;'></div>").appendTo("body");
  686. }
  687. }
  688.  
  689. var y = event.clientY - fExt.settings.toleranceY,
  690. x = event.clientX - fExt.settings.toleranceX;
  691. if ((x + fExt.ctxMenu.html.width()) >= window.screen.availWidth)
  692. x = x - fExt.ctxMenu.html.width() + (fExt.settings.toleranceX * 1.5);
  693. if ((y + fExt.ctxMenu.html.height() + 100) >= window.screen.availHeight)
  694. y = y - fExt.ctxMenu.html.height() + (fExt.settings.toleranceY * 1.5);
  695.  
  696. if (y < 0)
  697. y = 0;
  698. if (x < 0)
  699. x = 0;
  700.  
  701. fExt.ctxMenu.html.css({
  702. top: y,
  703. left: x
  704. });
  705.  
  706. fExt.reassignZIndex();
  707.  
  708. fExt.ctxMenu.Show();
  709.  
  710. return false;
  711. };
  712.  
  713. if(fExt.settings.hideContextMenuOnLeave) {
  714. var ctxLeaveCheck = {
  715. distance: 0,
  716. x: null,
  717. y: null,
  718. detectDistance: false
  719. };
  720. ctxLeaveCheck.reset = function(){
  721. this.distance = 0;
  722. this.x = null;
  723. this.y = null;
  724. this.detectDistance = false;
  725. };
  726.  
  727. fExt.jq(document).mousemove(function(event) {
  728. if(!ctxLeaveCheck.detectDistance)
  729. return true;
  730.  
  731.  
  732. if(!ctxLeaveCheck.x) {
  733. ctxLeaveCheck.x = event.clientX;
  734. ctxLeaveCheck.y = event.clientY;
  735. return;
  736. }
  737.  
  738. ctxLeaveCheck.distance += Math.sqrt(Math.pow(ctxLeaveCheck.y - event.clientY, 2) + Math.pow(ctxLeaveCheck.x - event.clientX, 2));
  739.  
  740. if(ctxLeaveCheck.distance > fExt.settings.hideContextMenuDistance) {
  741. ctxLeaveCheck.reset();
  742. fExt.ctxMenu.html.trigger("fExtContextMenuClosing", [fExt.ctxMenu.actor, event]);
  743. fExt.ctxMenu.Hide();
  744. }
  745. else {
  746. ctxLeaveCheck.x = event.clientX;
  747. ctxLeaveCheck.y = event.clientY;
  748. }
  749. });
  750.  
  751. fExt.ctxMenu.html.mouseleave(function(event) {
  752. ctxLeaveCheck.detectDistance = true;
  753. });
  754. fExt.ctxMenu.html.mouseenter(function() {
  755. ctxLeaveCheck.reset();
  756. fExt.ctxMenu.html.finish();
  757. fExt.ctxMenu.Show(0);
  758. });
  759. }
  760. else {
  761. fExt.jq("body").click(function(event) {
  762. if(!fExt.jq(this).hasClass("ctxElement") && fExt.ctxMenu.html.is(":visible")) {
  763. fExt.ctxMenu.html.trigger("fExtContextMenuClosing", [fExt.ctxMenu.actor, event]);
  764.  
  765. if(event.isDefaultPrevented())
  766. return true;
  767.  
  768. fExt.ctxMenu.Hide();
  769. }
  770. });
  771. }
  772.  
  773. fExt.jq(document).on('contextmenu', function(event) {
  774. if(!fExt.jq(event.target).hasClass("ctxElement")) {
  775. if(!event.shiftKey) {
  776. fExt.customContextMenuHandler(event);
  777. }
  778. }
  779. else
  780. fExt.ctxMenu.Hide();
  781. });
  782. }
  783. else {
  784. fExt.ctxMenu.uniqueID = 1;
  785. fExt.ctxMenu.allItems = [];
  786. fExt.ctxMenu.actor = undefined;
  787. fExt.ctxMenu.Show = function() {
  788. console.log("ContextMenu is turned off");
  789. };
  790. fExt.ctxMenu.Hide = function() {
  791. console.log("ContextMenu is turned off");
  792. };
  793. fExt.ctxMenu.addItem = function() {
  794. console.log("ContextMenu is turned off");
  795. };
  796. fExt.ctxMenu.addCtxItem = function() {
  797. console.log("ContextMenu is turned off");
  798. };
  799. fExt.ctxMenu.addSub = function() {
  800. console.log("ContextMenu is turned off");
  801. };
  802. fExt.ctxMenu.addCtxSub = function() {
  803. console.log("ContextMenu is turned off");
  804. };
  805. fExt.ctxMenu.addSeparator = function() {
  806. console.log("ContextMenu is turned off");
  807. };
  808. fExt.ctxMenu.assignID = function() {
  809. console.log("ContextMenu is turned off");
  810. };
  811. fExt.ctxMenu.getItem = function() {
  812. console.log("ContextMenu is turned off");
  813. };
  814. fExt.customContextMenuHandler = function () {
  815. console.log("ContextMenu is turned off");
  816. };
  817. }
  818.  
  819.  
  820. // Finalize
  821. document.fExt = fExt;
  822.  
  823. fExt.fExtPopup.click(function(e){
  824. fExt.popupQueue = [];
  825. fExt.hide(fExt.jq(this));
  826. });
  827.  
  828. fExt.fExtMessage.click(function(e){
  829. fExt.hide(fExt.jq(this));
  830. });
  831.  
  832. console.log("frisch's UserScript Extender initialized!");
  833. }