jump-to-anchor

Context menu item to jump to the closest anchor to the selected text (if any) or to the right-click point otherwise.

  1. // ==UserScript==
  2. // @name jump-to-anchor
  3. // @version 0.2
  4. // @description Context menu item to jump to the closest anchor to the selected text (if any) or to the right-click point otherwise.
  5. // @license MIT
  6. // @author eight04 <eight04@gmail.com>, YFdyh000
  7. // @incompatible chrome
  8. // @incompatible opera
  9. // @incompatible safari
  10. // @include *
  11. // @require https://greasyfork.org/scripts/33034-gm-context/code/GM_context.js?version=219427
  12. // @grant none
  13. // @namespace https://greasyfork.org/users/3017-yfdyh000
  14. // ==/UserScript==
  15.  
  16. /* global GM_context */
  17.  
  18. (function () {
  19. const all_context = ["page", "selection", "editable", "image", "link"];
  20. const menuitem = {
  21. label: "Jump to anchor (from click location)",
  22. onclick: function() { jumpToAnchor() }
  23. };
  24. const menuitem_selected = {
  25. label: "Jump to anchor (from highlighted selection)",
  26. onclick: function () { jumpToAnchor() } // TODO: keep the selection
  27. };
  28.  
  29. GM_context.add({
  30. context: all_context,
  31. items: [menuitem]
  32. });
  33. GM_context.add({
  34. context: all_context,
  35. items: [menuitem_selected],
  36. oncontext(e) {
  37. if (!getSelection()) return false;
  38. }
  39. });
  40.  
  41. function getSelection() {
  42. return document.getSelection().toString();
  43. }
  44. var x, y;
  45. window.addEventListener('click', function (e) {
  46. // Avoid grabbing for the actual selection
  47. // Doesn't seem to execute on single click anyways
  48. // but add for good measure
  49. if (e.button === 2) {
  50. x = e.clientX;
  51. y = e.clientY;
  52. }
  53. }, true);
  54. function jumpToAnchor() {
  55. x = Math.max(0, Math.min(window.innerWidth, x));
  56. y = Math.max(0, Math.min(window.innerHeight, y));
  57.  
  58. var node;
  59. var hasSelection = getSelection();
  60. if (hasSelection) {
  61. // For some reason, we can't just check ourselves here for
  62. // getSelection().anchorNode, as it is always present
  63. node = document.getSelection().anchorNode;
  64. }
  65. else {
  66. var caretPosition = document.caretPositionFromPoint(x, y);
  67. node = caretPosition.offsetNode;
  68. }
  69.  
  70. function findDeepestLastChild(elem) {
  71. var oldElem;
  72. do {
  73. oldElem = elem;
  74. elem = elem.lastElementChild;
  75. } while (elem);
  76. return oldElem;
  77. }
  78. function foundAnchor(nde) {
  79. if (nde.id || (nde.name && nde.nodeName.toLowerCase() === 'a')) {
  80. location.hash = '#' + (nde.id || nde.name);
  81. return true;
  82. }
  83. }
  84.  
  85. do {
  86. if (foundAnchor(node)) {
  87. break;
  88. }
  89.  
  90. if (node.previousElementSibling) {
  91. node = findDeepestLastChild(node.previousElementSibling);
  92. }
  93. else {
  94. node = node.parentNode;
  95. }
  96. } while (node);
  97. }
  98. })();