Y-marker

Sets up keyboard shortcuts for placing bookmarks and navigation inside webpage.

  1. // ==UserScript==
  2. // @name Y-marker
  3. // @namespace trespassersW
  4. // @description Sets up keyboard shortcuts for placing bookmarks and navigation inside webpage.
  5. // @include http://*
  6. // @include https://*
  7. // @include file://*
  8. // ** about:config -> greasemonkey.fileIsGreaseable <- true
  9. // @version 16.03.04
  10. // @license MIT
  11. // @released 2013-12-11
  12. // @run-at document-end
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_registerMenuCommand
  16. // @grant unsafeWindow
  17. // ==/UserScript==
  18. /*
  19. 16.03.04 bookmarklets works in chrome
  20. 16.02.26
  21. [+] hotkey configuration dialogue invoked from GM menu;
  22. [+] Bookmarlets interface:
  23. javascript:postMessage('Y-marker0','*') - jump to bookmark # 0
  24. javascript:postMessage('Y-marker0=','*') - set up bookmark #0
  25. 16.02.22
  26. [+] press Alt-Shift-0 twice to set starting Y-position for whole domain;
  27. * 13-12-12 click on msg removes all marks
  28. * 1.1 don't run in editable fields
  29. */
  30. (function(){ "use strict";
  31. if(top!=self || !document || !document.body ) return;
  32. var W = unsafeWindow || window; // localStorage doesn't live in sandbox?
  33. /* key kombinations to invoke the skript */
  34. var kShift = 1, kCtrl = 2, kAlt = 4, kWin = 8;
  35. var kJump = kAlt;
  36. var kMark = kAlt+kShift;
  37.  
  38. /* sec; 0 to disable; -1 status bar only */
  39. var tipShowtime = 2.2;
  40.  
  41. var kNobs = "-0123456789";
  42. var kKeys = {
  43. 173: 0, 48: 1, 49: 2, 50: 3, 51: 4, 52: 5, 53: 6, 54: 7, 55: 8, 56: 9, 57: 10,
  44. 189: 0, /* chrome, opera caps lock */
  45. 109: 0 /* opera */
  46. };
  47. var minus1="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
  48. min1=minus1.split(',');
  49. var LH=location.hostname || "Ym", LHP=LH+location.pathname;
  50.  
  51. function kNob(k){ return kNobs.charAt(k); }
  52.  
  53. var U=undefined, D=window.document;
  54. var _L= function() {};
  55. //_L=console.log.bind(console);
  56.  
  57. var statMsg;
  58. function msg(t,k, y){
  59. statMsg = "";
  60. if(U!==k)
  61. statMsg += "["+kNob(k)+"] ";
  62. if(U!==y)
  63. statMsg += ""+pt(y)+"% ";
  64. statMsg += t;
  65. }
  66.  
  67. var locStor,locStored;
  68.  
  69. // see SQlite manager -> %FFpath%\webappstore.sqlite -> find -> key -> contains Ym#
  70.  
  71. try {
  72. locStor = W.localStorage;
  73. locStored=locStor.getItem("Ym#"+location.pathname);
  74. } catch(e){ locStor = locStored =null; };
  75. function pt(y) {
  76. try{
  77. y =Math.round(y*1000/(document.body.scrollHeight+1))/10;
  78. if(y>111.1) y=111.1;
  79. }catch(e){console.log("math\n"+e); return -1}
  80. return y;
  81. }
  82.  
  83. var Ym;
  84. var pz;
  85.  
  86. function ldYm(){
  87. var s,t,y;
  88. if(locStor && locStored){
  89. pz=locStored.split(",");
  90. svYm();
  91. locStor.removeItem("Ym#"+location.pathname);
  92. console.log('Ym: locS -> GM_S '+LHP);
  93. locStor = locStored = null;
  94. }
  95. s=GM_getValue(Ym,null);
  96. if(!s){ s=minus1;
  97. }else {
  98. try{ y=JSON.parse(s);
  99. }catch(e){console.log('ldYm: buggy data\n'+e)};
  100. s=y[LHP] || y[LH] || minus1;
  101. }
  102.  
  103. pz=s.split(",");
  104. if(y && (y=y['#HotKeys#'])){
  105. kJump = +y[0], kMark = +y[1];
  106. }
  107. }
  108.  
  109. function svYm(d){
  110. var y={},s=pz.join(",");
  111. try{
  112. y=JSON.parse(GM_getValue(Ym,'{}'));
  113. }catch(e){console.log('svYm: buggy data\n'+e);}
  114. if(!d) y[LHP]= s;
  115. else if(d==='#'){
  116. y['#HotKeys#']=[kJump,kMark];
  117. }else{ // marker for whole domain
  118. y[LH]= s;
  119. }
  120. GM_setValue(Ym,JSON.stringify(y));
  121. }
  122.  
  123. function rmYm(){
  124. var y={};
  125. try{
  126. y=JSON.parse(GM_getValue(Ym,'{}'));
  127. }catch(e){console.log('rmYm: buggy data\n'+e); y={}; }
  128. if(y[LH]) delete y[LH], _L("rmH: " +LH);
  129. if(y[LHP]) delete y[LHP], _L("rmHP: " +LHP);
  130. GM_setValue(Ym,JSON.stringify(y));
  131. pz=min1;
  132. }
  133.  
  134. function jumpY(k){
  135. var y=pz[k];
  136. if(y > -1) {
  137. if( y!= scrollY ){
  138. pz[0] = scrollY;
  139. scroll(0,y);
  140. msg("jump",k,y);
  141. }else
  142. msg("here",k,y);
  143. return 1;
  144. }
  145. msg("none",k);
  146. return 0;
  147. }
  148.  
  149. function markY(k){
  150. var d="";
  151. if(k==1 && scrollY == pz[k] ) d="!";
  152. pz[k]=scrollY;
  153. if(k>=1)
  154. svYm(d);
  155. msg("mark"+d,k,pz[k]);
  156. return 1;
  157. }
  158.  
  159. var kMap= {16: kShift, 17:kCtrl, 18: kAlt, 91: kWin};
  160. var keyMod=0;
  161. var kkWin= (kMark | kJump) & kWin;
  162.  
  163. function klear(e) {
  164. var k= e.keyCode;
  165. if(!k) return;
  166. var km=kMap[k];
  167. if(km) {
  168. keyMod &= -1 ^ km;
  169. }
  170. }
  171.  
  172. function onKeydown(e) {
  173. try{
  174. var rc=0, k= e.keyCode;
  175. if(!k) return;
  176. // Don't run in input, select, textarea etc.
  177. var E = D.activeElement.tagName.toLowerCase();
  178. if (E == "input" || E == "select" || E == "textarea" ||
  179. (E=D.activeElement.contentEditable) == "true" ||
  180. E == "")
  181. { keyMod = 0; return; }
  182. var km =kMap[k]; // ff doesnt track metaKey. stsuko
  183. if(km) {
  184. keyMod |= km;
  185. if( kkWin && km === kWin ) //???
  186. e.preventDefault(), e.stopPropagation();
  187. return;
  188. }
  189. km=keyMod;
  190. if( U=== (k=kKeys[k]) ){ keyMod = 0; return; }
  191. if( km === kJump ) {
  192. rc = jumpY(k);
  193. }else if( km === kMark ) {
  194. rc = markY(k);
  195. }else {
  196. return;
  197. }
  198. statSay();
  199. if(rc) e.preventDefault(),e.stopPropagation();
  200. }catch(e){console.log('smth wromg\n'+e)};
  201. }
  202. function mk(p, t, id, s) {
  203. var e = D.createElement(t);
  204. e.id=id;
  205. e.style.cssText = s;
  206. return p.appendChild(e);
  207. };
  208.  
  209. /* RIP status bar replacement */
  210. var sb;
  211. function _css(){
  212. if(!sb) sb = mk(
  213. //D.body,
  214. D.documentElement, // 2014-06-30 ???!!1
  215. 'section', "Y-marker-userjs-inf",
  216. "position: fixed!important;\
  217. z-index: 214748!important;\
  218. top: 0px; right: 1px; bottom: auto; left: auto;\
  219. background: rgba(221,255,221,.75)!important;\
  220. padding: 2px 3px 2px 8px; margin:0;\
  221. border: none;\
  222. border-radius: 12px 3px 3px 12px;\
  223. color: #131!important;\
  224. opacity: 1; display:none;\
  225. font: normal 12px/14px sans-serif !important;\
  226. text-shadow: #373 2px 2px 4px, #7F7 -2px -2px 4px;\
  227. cursor:no-drop;\
  228. "
  229. );
  230. /* click removes all */
  231. if(tipShowtime>0){
  232. sb.addEventListener("click",function (e) {
  233. rmYm(); //locStor.removeItem(Ym);
  234. ldYm(); noTout();
  235. },false);
  236. };
  237. }
  238.  
  239.  
  240. var tO;
  241. function onTout(){
  242. tO=0; sb.style.display="none";
  243. }
  244. function noTout(){
  245. if(tO) clearTimeout(tO), onTout();
  246. }
  247.  
  248. function statSay(t) {
  249. if(statMsg){
  250. _css();
  251. if(tipShowtime)
  252. W.status=statMsg;
  253. if(tipShowtime>0){
  254. noTout();
  255. sb.innerHTML= statMsg;
  256. sb.style.display="block";
  257. tO=setTimeout(onTout, (t? t: tipShowtime)*1000);
  258. }
  259. }
  260. statMsg="";
  261. }
  262.  
  263. Ym="Ym#";//+location.pathname;
  264.  
  265. ldYm();
  266. for(var k=1; k<2; k++){ /* pz.length */
  267. if(pz[k]>-1){
  268. scroll(0,pz[k]);
  269. msg('jump',k,pz[k]);
  270. statSay(5);
  271. break;
  272. }
  273. };
  274.  
  275. function $i(id, parent){
  276. return (parent || document).getElementById(id)
  277. }
  278.  
  279. var hk;
  280. function saveHk(){
  281. function K(id, b)
  282. { return $i('Ym-bt'+id).checked? b: 0 }
  283. kJump=K('JS',kShift)|K('JA',kAlt)|K('JW',kWin)|K('JC',kCtrl);
  284. kMark=K('MS',kShift)|K('MA',kAlt)|K('MW',kWin)|K('MC',kCtrl);
  285. svYm('#');
  286. }
  287.  
  288. function setKeys(){
  289. function J(j){return kJump&j? 'checked': ''}
  290. function M(m){return kMark&m? 'checked': ''}
  291. if(hk) hk.parentNode.removeChild(hk);
  292. hk = D.createElement('section');
  293. hk.style.cssText =
  294. "position: fixed!important;\
  295. z-index: 214748!important;\
  296. top: 10px; left: 16px; bottom: auto; right: auto;\
  297. background: rgb(221,255,221)!important;\
  298. padding: 4px; margin:0;\
  299. border: 1px solid #131;\
  300. border-radius: 3px; \
  301. color: #131!important;\
  302. opacity: 1; display:block;\
  303. font: normal 12px/14px sans-serif !important;\
  304. ";
  305. hk.innerHTML=('\
  306. <form style="padding:0;margin:0"><b>\
  307. <center style="padding-bottom:.5em">Y-marker hotkeys</center></b>\
  308. <b style="width:3em;display:inline-block;">Jump</b>: &nbsp;&nbsp;\
  309. Shift<input type=checkbox id=Ym-btJS '+J(kShift)+'> &nbsp;&nbsp;\
  310. Alt<input type=checkbox id=Ym-btJA '+J(kAlt)+'> &nbsp;&nbsp;\
  311. Win<input type=checkbox id=Ym-btJW '+J(kWin)+'> &nbsp;&nbsp;\
  312. Ctrl<input type=checkbox id=Ym-btJC '+J(kCtrl)+'> <br>\
  313. <b style="width:3em;display:inline-block;">Mark</b>: &nbsp;&nbsp;\
  314. Shift<input type=checkbox id=Ym-btMS '+M(kShift)+'> &nbsp;&nbsp;\
  315. Alt<input type=checkbox id=Ym-btMA '+M(kAlt)+'> &nbsp;&nbsp;\
  316. Win<input type=checkbox id=Ym-btMW '+M(kWin)+'> &nbsp;&nbsp;\
  317. Ctrl<input type=checkbox id=Ym-btMC '+M(kCtrl)+'><hr><center>\
  318. <input type="button" tabIndex=1 value="Save" id="Ym-btSave"> &nbsp;\
  319. <input type="button" tabIndex=2 value="Cancel" id="Ym-btCancel">\
  320. </center></form>\
  321. ');
  322. hk.addEventListener("click",function(e){
  323. var t= e.target,c=0;
  324. if(!t || !t.id) return;
  325. if(t.id=="Ym-btSave")try{
  326. c=1;
  327. saveHk();
  328. }catch(e){console.log("Ym: problem in hotkey dialog\n"+e)}
  329. else if(t.id=="Ym-btCancel"){
  330. c=1;
  331. }
  332. if(c){
  333. e.preventDefault(), e.stopPropagation();
  334. hk.parentNode.removeChild(hk),hk=null;
  335. }
  336. },false);
  337. D.documentElement.appendChild(hk);
  338. }
  339.  
  340. function wMsg(e){
  341. //event.source!=window in Chrome
  342. if(typeof e.data==='string' && e.data.substr(0,8)==='Y-marker'){
  343. e.stopPropagation();
  344. if(e.data === 'Y-marker hotkeys')
  345. return setKeys();
  346. var m=e.data.match(/Y-marker\s*([0-9-])(\=?)/);
  347. var p = (m[1]==='-')?0: +m[1]+1;
  348. if(m[2])
  349. markY(p);
  350. else
  351. jumpY(p);
  352. statSay();
  353. }
  354. }
  355.  
  356. addEventListener("keydown",onKeydown,false);
  357. addEventListener("keyup",klear,false);
  358. GM_registerMenuCommand("Y-marker hotkeys", setKeys);
  359.  
  360. window.addEventListener("message", wMsg, false);
  361.  
  362. })();