Keyboard navigation

Keyboard based navigation

当前为 2022-06-26 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name Keyboard navigation
// @version 1.9.1
// @author Sly_North
// @description Keyboard based navigation
// @grant none
// @run-at document-start
// @namespace Sly_North
// @include *
// @license MIT
// ==/UserScript==

// Click first link (tag='a' or 'span') with text matching one of 'keywords' regex
function ClickLink(tag, keywords) {
  var elts = document.getElementsByTagName(tag);
  for (var i = 0; i < elts.length; ++i) {
    var elt = elts[i];
    for (k in keywords)
    {
      var regexp = keywords[k];
      if (elt.textContent.match(keywords[k])) {
        console.log('Clicking ', elt.textContent);
        elt.click();
        return true;
      }
    }
  }
  return false;
}

// Make 'link' visibly selected.
var lastSel; // previous selected link
function SelectLink(link, links) {
  if (lastSel) lastSel.style.backgroundColor = "";
  lastSel = link;

  link.focus();
  link.style.backgroundColor = "orange";
}

// Select either link with preferred text or closer to the center.
function SelectBestLink() {
  var links = document.getElementsByTagName('a');
  var curr = document.activeElement;
  var viewport = window.visualViewport;
  var screenHeight = viewport.height;
  var midX = viewport.width/2;
  var midY = screenHeight/2;
  var minD = 1000000000;
  var best;

  var keywords = [/cliquez sur ce lien/i, /#unread/, /Discussions/];

  for (var i = 0; i < links.length; ++i) {
    var link = links[i];
    var br = link.getBoundingClientRect();
    var d = Math.abs(br.left - midX) + Math.abs(br.top - midY);
    if ((link == curr)) continue;

    // Insert here other preferred link texts.
    for (var k in keywords) {
      if (link.text.match(keywords[k])) {
        // link.click();
        SelectLink(link, links);
        return;
      }
    }

    // Select link to current page, if not already selected
    if (link.href == document.URL) {
      if ((br.top >= 0) && (br.top < screenHeight)) {
        // If on start, focus it, without highlighting it.
        SelectLink(link, links);
        return;
      }
    }
    // Select link closer to the center
    if (d < minD) {
      minD = d;
      best = link;
    }

  }
  if (best) {
    // If on start, focus it, without highlighting it.
    console.log("Select link", best.text);
    SelectLink(best, links);
  }
}

// Skip most simple cookie forms.
function SkipCookieWall() {
  var elts = document.getElementsByTagName('span');
  for (i in elts) {
    var t = elts[i].innerText;
    if (!t) continue;
    if (t.match(/Continuer sans accepter/) || t.match(/CONTINUER SANS ACCEPTER/) || t.match(/Continue without agree/) || t.match(/Only allow essential cookies/)) {
      console.log("Clic ", t);
      elts[i].click();
      return;
    }
  }
  console.log('Cookie skip not found');
}

function focusSearchInput() {
  var inputs = document.getElementsByTagName('input');
  for (let i of inputs) {
    if (i.type === 'text') {
      let rect = i.getBoundingClientRect();
      if (rect.width > 0 && rect.height > 0) {
    	  // console.log(' - Found text input');
        i.focus();
        return;
      }
    }
  }
}


// Unblock the page scrolling.
function Unfreeze() {
  document.body.classList.remove('content-blurred');
  document.body.classList.remove('noscroll');
  document.documentElement.style.overflow = "scroll";
  document.body.style.overflow = "scroll";
  console.log('Unfreeze overflow');
}

// Listen for a few control+alt + key
document.addEventListener('keydown', function(e) {
  // console.log('ctrl=',e.ctrlKey,' alt=',e.altKey, ' meta=', e.metaKey, ' key=',e.keyCode);
  if (e.shiftKey || !e.ctrlKey || (!e.altKey && !e.metaKey)) return;

  // Space=select best link, enter=click best link, #/del=GMail's delete old messages in trash, o[/p]=previous/next page, /=search box
  switch (e.keyCode) {
    case 32: case 220: SelectBestLink(); return;
    case 13: case 90: ClickLink('a', [/cliquez sur ce lien/, /click this link/, /unread#unread/]); return;
    case 46: case 51: ClickLink('span', [/^delete forever/]); return;
    case 191: focusSearchInput(); return;
   	case 85: Unfreeze(); return;
    case 67: SkipCookieWall(); return;
    case 79: case 219:
      if (!ClickLink('a', [/Précédent/i, /Prev/, /Previous page/, /Older/]))
        ClickLink('i', [/ chevron_left /]);
      return;
    case 80: case 221:
      if (!ClickLink('a', [/Suivant/i, /Next/, /Next page/, /Newer/]))
        ClickLink('i', [/ chevron_right /]);
      return;
  }

  // Select direction in which to look for 'best' link
  var dirX = 0, dirY = 0;
  var scaleX = 1; // used to allow going up/down to a link not on a straight alignement (angle > 45°)
  var maxDY = 10000000; // used to strongly limit vertical search when going left/right
  var scaleWidth = 0; // To select left/center/right of elements
  if (e.keyCode == 37 || e.keyCode == 72)      { dirX = -1; scaleWidth = 0; maxDY = 50; } // left
  else if (e.keyCode == 39 || e.keyCode == 76) { dirX = +1; scaleWidth = 1; maxDY = 50; } // right
  else if (e.keyCode == 38 || e.keyCode == 75) { dirY = -1; scaleWidth = 0.5; scaleX = 0.25; } // up
  else if (e.keyCode == 40 || e.keyCode == 74) { dirY = +1; scaleWidth = 0.5; scaleX = 0.25; } // down
  else {
    // console.log('Key ignored:', e.keyCode);
    return;
  }

  // Look for next link in a given direction
  var adx = Math.abs(dirX), ady = Math.abs(dirY);
  var curr = document.activeElement;
  var currBR = curr.getBoundingClientRect();
  var currX = currBR.left + currBR.width*scaleWidth;
  var currY = currBR.top  + currBR.height/2;
  var links = document.getElementsByTagName('a');
  var best;
  var minD = 1500;

  for (var i = 0; i < links.length; ++i) {
    var link = links[i];
    if (link != curr) {
      var br2 = link.getBoundingClientRect();
      if (br2.width == 0 && br2.height == 0) continue;

      var dx = (br2.left + br2.width*scaleWidth - currX) * scaleX;
      var dy = br2.top + br2.height/2 - currY;
      var adx = Math.abs(dx);
      var ady = Math.abs(dy);
      if (ady > maxDY) continue;
      if ((adx > ady) != (dirX != 0)) continue;
      var dot = dx * dirX + dy * dirY;
      if (dot <= 0) continue;
      if (dot < minD) {
        // console.log("   new best link dx = ",dx," dy = ", dy, "  minD = ", minD, " ", link.text);
        minD = dot;
        best = link;
      }
    }
  }
  if (best) SelectLink(best, links);
}, false);

setTimeout(() => {SkipCookieWall();}, 1000);
// setTimeout(() => {SkipCookieWall();}, 1500);

console.log('key navigation handler ready');