xwidget-plus-follow-link.el 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. ;;; xwidget-plus-follow-link.el -- Link navigation in browsers -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
  3. ;; This program is free software: you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;; This program is distributed in the hope that it will be useful,
  8. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. ;; GNU General Public License for more details.
  11. ;; You should have received a copy of the GNU General Public License
  12. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ;;; Commentary:
  14. ;;
  15. ;;; Code:
  16. (require 'xwidget-plus-common)
  17. (defconst xwidget-plus-follow-link-script (--js "
  18. function __xwidget_plus_follow_link_cleanup() {
  19. document.querySelectorAll('a').forEach(a => {
  20. a.classList.remove('xwidget-plus-follow-link-candidate', 'xwidget-plus-follow-link-selected');
  21. });
  22. }
  23. function __xwidget_plus_follow_link_highlight(json, selected) {
  24. var ids = JSON.parse(json);
  25. document.querySelectorAll('a').forEach((a, id) => {
  26. a.classList.remove('xwidget-plus-follow-link-candidate', 'xwidget-plus-follow-link-selected');
  27. if (selected == id) {
  28. a.classList.add('xwidget-plus-follow-link-selected');
  29. a.scrollIntoView({behavior: 'smooth', block: 'center'});
  30. } else if (ids[id]) {
  31. a.classList.add('xwidget-plus-follow-link-candidate');
  32. }
  33. });
  34. }
  35. function __xwidget_plus_follow_link_action(id) {
  36. __xwidget_plus_follow_link_cleanup();
  37. document.querySelectorAll('a')[id].click();
  38. }
  39. function __xwidget_plus_follow_link_links() {
  40. var r = {};
  41. document.querySelectorAll('a').forEach((a, i) => {
  42. if (a.offsetWidth || a.offsetHeight || a.getClientRects().length)
  43. r[i] = a.innerText;
  44. });
  45. return r;
  46. }
  47. " js--))
  48. (defun xwidget-plus-follow-link-candidates ()
  49. "Return the currently selected link and the candidates.
  50. The return value is a list whose first element is the selected
  51. link and the rest are the candidates."
  52. (with-current-buffer (ivy-state-buffer ivy-last)
  53. (cons (ivy-state-current ivy-last) (ivy--filter ivy-text ivy--all-candidates))))
  54. (defun xwidget-plus-follow-link-highlight (xwidget links)
  55. "Highligh LINKS in XWIDGET buffer when updating candidates."
  56. (let* ((candidates (xwidget-plus-follow-link-candidates))
  57. (selected (car candidates))
  58. (cands (cdr candidates))
  59. (cands-id (seq-filter (lambda (v) (seq-contains-p cands (cdr v))) links))
  60. (selected-id (car (rassoc selected links)))
  61. (script (--js "__xwidget_plus_follow_link_highlight('%s', '%s');" js-- (json-serialize cands-id) selected-id)))
  62. (xwidget-webkit-execute-script xwidget script)))
  63. (defun xwidget-plus-follow-link-exit (xwidget)
  64. "Exit follow link mode in XWIDGET."
  65. (let ((script "__xwidget_plus_follow_link_cleanup();"))
  66. (xwidget-webkit-execute-script xwidget script)))
  67. (defun xwidget-plus-follow-link-action (xwidget links selected)
  68. "Activate link matching SELECTED in XWIDGET LINKS."
  69. (let* ((selected-id (car (rassoc selected links)))
  70. (script (--js "__xwidget_plus_follow_link_action('%s');" js-- selected-id)))
  71. (xwidget-webkit-execute-script xwidget script)))
  72. (defun xwidget-plus-follow-link-prepare-links (links)
  73. "Prepare the alist of LINKS."
  74. (setq links (seq-sort-by (lambda (v) (string-to-number (car v))) #'< links))
  75. (setq links (seq-map (lambda (v) (cons (intern (car v)) (xwidget-plus-follow-link-format-link (cdr v))))
  76. links))
  77. (seq-filter #'identity links))
  78. (defun xwidget-plus-follow-link-iv)
  79. (defun xwidget-plus-follow-link-callback (links)
  80. "Ask for a link belonging to the alist LINKS."
  81. (let* ((xwidget (xwidget-webkit-current-session))
  82. (links (xwidget-plus-follow-link-prepare-links links))
  83. (choice (seq-map #'cdr links))
  84. link)
  85. (unwind-protect
  86. (xwidget-plus-follow-link-action xwidget links
  87. (ivy-read "Link: " choice :update-fn (apply-partially #'xwidget-plus-follow-link-highlight xwidget links)))
  88. (xwidget-plus-follow-link-exit xwidget))))
  89. ;;;###autoload
  90. (defun xwidget-plus-follow-link (&optional xwidget)
  91. "Ask for a link in the XWIDGET session or the current one and follow it."
  92. (interactive)
  93. (let ((xwidget (or xwidget (xwidget-webkit-current-session)))
  94. (script (--js "__xwidget_plus_follow_link_links();" js--)))
  95. (xwidget-plus-inject-style xwidget "__xwidget_plus_follow_link_style" (xwidget-plus-follow-link-style-definition))
  96. (xwidget-plus-inject-script xwidget "__xwidget_plus_follow_link_script" xwidget-plus-follow-link-script)
  97. (xwidget-webkit-execute-script xwidget script #'xwidget-plus-follow-link-callback)))
  98. (provide 'xwidget-plus-follow-link)
  99. ;;; xwidget-plus-follow-link.el ends here
  100. ;; Local Variables:
  101. ;; eval: (mmm-mode)
  102. ;; eval: (mmm-add-classes '((elisp-js :submode js-mode :face mmm-code-submode-face :delimiter-mode nil :front "--js \"" :back "\" js--")))
  103. ;; mmm-classes: elisp-js
  104. ;; End: