Y-marker

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Y-marker
// @namespace   trespassersW
// @description Sets up keyboard shortcuts for placing bookmarks and navigation inside webpage.
// @include        http://*
// @include        https://*
// @include        file://*
// ** about:config -> greasemonkey.fileIsGreaseable <- true
// @version 16.03.04
// @license  MIT
// @released 2013-12-11
// @run-at document-end
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant unsafeWindow
// ==/UserScript==
/*  
 16.03.04 bookmarklets works in chrome
 16.02.26 
  [+] hotkey configuration dialogue invoked from GM menu;
  [+] Bookmarlets interface: 
   javascript:postMessage('Y-marker0','*') - jump to bookmark # 0
   javascript:postMessage('Y-marker0=','*') - set up bookmark #0
 16.02.22 
  [+] press Alt-Shift-0 twice to set starting Y-position for whole domain; 
* 13-12-12 click on msg removes all marks
* 1.1 don't run in editable fields
*/
(function(){ "use strict";
if(top!=self || !document || !document.body ) return;
var W = unsafeWindow || window; // localStorage doesn't live in sandbox?
/* key kombinations to invoke the skript */
var kShift = 1,  kCtrl = 2, kAlt = 4, kWin = 8; 
var kJump = kAlt; 
var kMark = kAlt+kShift;

/* sec; 0 to disable; -1 status bar only */
var tipShowtime = 2.2;  

var kNobs = "-0123456789";
var kKeys = { 
 173: 0, 48: 1, 49: 2, 50: 3, 51: 4, 52: 5, 53: 6, 54: 7, 55: 8, 56: 9, 57: 10,
 189: 0, /* chrome, opera caps lock */
 109: 0  /* opera */
};
var minus1="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
    min1=minus1.split(',');
var LH=location.hostname || "Ym", LHP=LH+location.pathname;

function kNob(k){ return kNobs.charAt(k); }

var U=undefined, D=window.document;
var _L=  function() {};
//_L=console.log.bind(console);

var statMsg;
function msg(t,k, y){
 statMsg = "";
 if(U!==k)
  statMsg += "["+kNob(k)+"] ";
 if(U!==y)
  statMsg += ""+pt(y)+"% ";
 statMsg += t;
}

var locStor,locStored;

// see SQlite manager -> %FFpath%\webappstore.sqlite -> find -> key -> contains Ym#

try {
  locStor = W.localStorage;
  locStored=locStor.getItem("Ym#"+location.pathname);
} catch(e){ locStor = locStored =null;  };
  
function pt(y) {
 try{
  y =Math.round(y*1000/(document.body.scrollHeight+1))/10;
  if(y>111.1) y=111.1;
 }catch(e){console.log("math\n"+e); return -1}
 return y;
}

var Ym;
var pz;

function ldYm(){
  var s,t,y;
  if(locStor && locStored){
    pz=locStored.split(",");
    svYm();
    locStor.removeItem("Ym#"+location.pathname);
    console.log('Ym: locS -> GM_S '+LHP);
    locStor = locStored = null;
  }
  s=GM_getValue(Ym,null);
  if(!s){ s=minus1;
  }else {
   try{ y=JSON.parse(s);
   }catch(e){console.log('ldYm: buggy data\n'+e)};
   s=y[LHP] || y[LH] || minus1;
  }

  pz=s.split(",");
  if(y && (y=y['#HotKeys#'])){
   kJump = +y[0], kMark = +y[1];
  }
}

function svYm(d){
 var y={},s=pz.join(",");
 try{
   y=JSON.parse(GM_getValue(Ym,'{}'));
 }catch(e){console.log('svYm: buggy data\n'+e);}
 if(!d) y[LHP]= s; 
 else if(d==='#'){
   y['#HotKeys#']=[kJump,kMark];
 }else{ //  marker for whole domain
   y[LH]= s;
 }
 GM_setValue(Ym,JSON.stringify(y));
}

function rmYm(){
 var y={};
 try{
   y=JSON.parse(GM_getValue(Ym,'{}'));
 }catch(e){console.log('rmYm: buggy data\n'+e); y={}; }
  if(y[LH]) delete y[LH],  _L("rmH: " +LH);
  if(y[LHP]) delete y[LHP],  _L("rmHP: " +LHP);
 GM_setValue(Ym,JSON.stringify(y));
 pz=min1;
}

function jumpY(k){
 var y=pz[k];
 if(y > -1) {
   if( y!= scrollY ){
    pz[0] = scrollY;
    scroll(0,y);
    msg("jump",k,y);
   }else
    msg("here",k,y); 
   return 1;
 }
 msg("none",k); 
 return 0;
}

function markY(k){
 var d="";
 if(k==1 && scrollY == pz[k] ) d="!";
 pz[k]=scrollY;
 if(k>=1)
  svYm(d);
 msg("mark"+d,k,pz[k]);
 return 1;
}

var kMap= {16: kShift, 17:kCtrl, 18: kAlt, 91: kWin};
var keyMod=0;
var kkWin= (kMark | kJump) & kWin;

function klear(e) { 
 var k= e.keyCode; 
 if(!k) return;
 var km=kMap[k];
 if(km) {
  keyMod &= -1 ^ km;
 }
}

function onKeydown(e) {
 try{
 var rc=0, k= e.keyCode; 
 if(!k) return;
 // Don't run in input, select, textarea etc.
 var E = D.activeElement.tagName.toLowerCase();
 if (E == "input" || E == "select" || E == "textarea" ||
    (E=D.activeElement.contentEditable) == "true" ||
     E == "") 
   {  keyMod = 0; return; }
 
 var km =kMap[k]; // ff doesnt track metaKey. stsuko
 if(km) {
   keyMod |= km;
   if( kkWin && km === kWin ) //???
    e.preventDefault(), e.stopPropagation();
   return;
 }
 km=keyMod; 
 if( U=== (k=kKeys[k]) ){ keyMod = 0; return; }
 if( km === kJump ) {
   rc = jumpY(k);
 }else if( km === kMark ) {
   rc = markY(k);
 }else {
  return;
 }
 statSay();
 if(rc) e.preventDefault(),e.stopPropagation();
 }catch(e){console.log('smth wromg\n'+e)};
}
 
function mk(p, t, id, s) {
  var e = D.createElement(t);
  e.id=id;
  e.style.cssText = s;
  return p.appendChild(e);
};

/* RIP status bar replacement */
var sb;
function _css(){ 
if(!sb) sb = mk(
//D.body, 
D.documentElement, // 2014-06-30 ???!!1 
'section', "Y-marker-userjs-inf",
"position: fixed!important;\
z-index: 214748!important;\
top: 0px; right: 1px; bottom: auto; left: auto;\
background: rgba(221,255,221,.75)!important;\
padding: 2px 3px 2px 8px; margin:0;\
border: none;\
border-radius: 12px 3px 3px 12px;\
color: #131!important;\
opacity: 1; display:none;\
font: normal 12px/14px sans-serif !important;\
text-shadow: #373 2px 2px 4px, #7F7 -2px -2px 4px;\
cursor:no-drop;\
"
);
/* click removes all  */
if(tipShowtime>0){
 sb.addEventListener("click",function (e) {
  rmYm(); //locStor.removeItem(Ym);
  ldYm(); noTout();
 },false);
};
}


var tO;
function onTout(){
 tO=0; sb.style.display="none";
}
function noTout(){ 
 if(tO) clearTimeout(tO), onTout();
}

function statSay(t) {
 if(statMsg){
 _css();
   if(tipShowtime) 
     W.status=statMsg;
   if(tipShowtime>0){
     noTout();
     sb.innerHTML= statMsg;
     sb.style.display="block";
     tO=setTimeout(onTout, (t? t: tipShowtime)*1000);
   }
 } 
 statMsg="";
}

Ym="Ym#";//+location.pathname;

ldYm();
for(var k=1; k<2; k++){ /* pz.length */
 if(pz[k]>-1){ 
  scroll(0,pz[k]); 
  msg('jump',k,pz[k]);
  statSay(5);
  break; 
 }
};

function $i(id, parent){
	return (parent || document).getElementById(id)
}

var hk;
function saveHk(){
 function K(id, b)
 { return $i('Ym-bt'+id).checked? b: 0 }
  kJump=K('JS',kShift)|K('JA',kAlt)|K('JW',kWin)|K('JC',kCtrl);
  kMark=K('MS',kShift)|K('MA',kAlt)|K('MW',kWin)|K('MC',kCtrl);
  svYm('#');
}

function setKeys(){
 function J(j){return kJump&j? 'checked': ''}
 function M(m){return kMark&m? 'checked': ''}
  if(hk)   hk.parentNode.removeChild(hk);
  hk = D.createElement('section');
  hk.style.cssText = 
"position: fixed!important;\
z-index: 214748!important;\
top: 10px; left: 16px; bottom: auto; right: auto;\
background: rgb(221,255,221)!important;\
padding: 4px; margin:0;\
border: 1px solid #131;\
border-radius: 3px; \
color: #131!important;\
opacity: 1; display:block;\
font: normal 12px/14px sans-serif !important;\
";
  hk.innerHTML=('\
<form style="padding:0;margin:0"><b>\
<center style="padding-bottom:.5em">Y-marker hotkeys</center></b>\
<b style="width:3em;display:inline-block;">Jump</b>: &nbsp;&nbsp;\
Shift<input type=checkbox id=Ym-btJS '+J(kShift)+'> &nbsp;&nbsp;\
Alt<input type=checkbox id=Ym-btJA '+J(kAlt)+'> &nbsp;&nbsp;\
Win<input type=checkbox id=Ym-btJW '+J(kWin)+'> &nbsp;&nbsp;\
Ctrl<input type=checkbox id=Ym-btJC '+J(kCtrl)+'> <br>\
<b style="width:3em;display:inline-block;">Mark</b>: &nbsp;&nbsp;\
Shift<input type=checkbox id=Ym-btMS '+M(kShift)+'> &nbsp;&nbsp;\
Alt<input type=checkbox id=Ym-btMA '+M(kAlt)+'> &nbsp;&nbsp;\
Win<input type=checkbox id=Ym-btMW '+M(kWin)+'> &nbsp;&nbsp;\
Ctrl<input type=checkbox id=Ym-btMC '+M(kCtrl)+'><hr><center>\
<input type="button" tabIndex=1 value="Save" id="Ym-btSave"> &nbsp;\
<input type="button" tabIndex=2 value="Cancel" id="Ym-btCancel">\
</center></form>\
');
hk.addEventListener("click",function(e){
 var t= e.target,c=0;
 if(!t || !t.id) return;
 if(t.id=="Ym-btSave")try{
   c=1;
   saveHk();
 }catch(e){console.log("Ym: problem in hotkey dialog\n"+e)}
 else if(t.id=="Ym-btCancel"){
   c=1;
 }
 if(c){
   e.preventDefault(), e.stopPropagation();
   hk.parentNode.removeChild(hk),hk=null;
 }
},false);
 D.documentElement.appendChild(hk);
}

function wMsg(e){
 //event.source!=window in Chrome
 if(typeof e.data==='string' && e.data.substr(0,8)==='Y-marker'){
  e.stopPropagation();
  if(e.data === 'Y-marker hotkeys')
    return setKeys();
  var m=e.data.match(/Y-marker\s*([0-9-])(\=?)/);
  var p = (m[1]==='-')?0: +m[1]+1;
  if(m[2])
    markY(p);
  else
    jumpY(p);
  statSay();
 } 
}

addEventListener("keydown",onKeydown,false);
addEventListener("keyup",klear,false);
GM_registerMenuCommand("Y-marker hotkeys", setKeys);

window.addEventListener("message", wMsg, false);

})();