RSS: FreshRSS Gestures and Auto-Scroll

Gesture controls (swipe/double-tap) for FreshRSS: double-tap to close articles, edge swipe to jump to article end, and auto-scroll

目前為 2025-02-17 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name RSS: FreshRSS Gestures and Auto-Scroll
  3. // @namespace http://tampermonkey.net/
  4. // @version 3.1
  5. // @description Gesture controls (swipe/double-tap) for FreshRSS: double-tap to close articles, edge swipe to jump to article end, and auto-scroll
  6. // @author Your Name
  7. // @homepage https://greasyfork.org/en/scripts/525912
  8. // @match http://192.168.1.2:1030/*
  9. // @grant none
  10. // ==/UserScript==
  11. (function() {
  12. 'use strict';
  13. // Debug mode
  14. const DEBUG = false;
  15. // Swipe detection configuration
  16. const EDGE_THRESHOLD = 50; // Distance from edge to start swipe
  17. const SWIPE_THRESHOLD = 50; // Minimum distance for a swipe
  18. let touchStartX = 0;
  19. let touchStartY = 0;
  20. function debugLog(message) {
  21. if (DEBUG) {
  22. console.log(`[FreshRSS Script]: ${message}`);
  23. }
  24. }
  25. debugLog('Script loaded');
  26.  
  27. // Function to scroll to element
  28. function scrollToElement(element) {
  29. if (element) {
  30. const header = document.querySelector('header');
  31. const headerHeight = header ? header.offsetHeight : 0;
  32. const elementPosition = element.getBoundingClientRect().top + window.pageYOffset;
  33. const scrollTarget = elementPosition - headerHeight - 20; // 20px padding from header
  34. window.scrollTo({
  35. top: scrollTarget,
  36. behavior: 'smooth'
  37. });
  38. debugLog('Scrolling element near top: ' + element.id);
  39. }
  40. }
  41.  
  42. // Function to scroll to next element with peek
  43. function scrollToNextElement(element) {
  44. if (element) {
  45. const nextElement = element.nextElementSibling;
  46. if (nextElement) {
  47. const header = document.querySelector('header');
  48. const headerHeight = header ? header.offsetHeight : 0;
  49. const nextElementPosition = nextElement.getBoundingClientRect().top + window.pageYOffset;
  50. const scrollTarget = nextElementPosition - headerHeight - 25; // px padding from header
  51. window.scrollTo({
  52. top: scrollTarget,
  53. behavior: 'smooth'
  54. });
  55. debugLog('Scrolled to show next element near top');
  56. }
  57. }
  58. }
  59.  
  60. // Function to close active article
  61. function closeActiveArticle(element) {
  62. if (element) {
  63. element.classList.remove('active');
  64. debugLog('Closed article');
  65. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  66. }
  67. }
  68.  
  69. // Handle double-tap to close
  70. document.addEventListener('dblclick', function(event) {
  71. const interactiveElements = ['A', 'BUTTON', 'INPUT', 'TEXTAREA', 'SELECT', 'LABEL'];
  72. if (interactiveElements.includes(event.target.tagName)) {
  73. debugLog('Ignored double-tap on interactive element');
  74. return;
  75. }
  76. const activeElement = event.target.closest('.flux.active');
  77. if (activeElement) {
  78. closeActiveArticle(activeElement);
  79. }
  80. });
  81.  
  82. // Touch event handlers for swipe detection
  83. document.addEventListener('touchstart', function(event) {
  84. touchStartX = event.touches[0].clientX;
  85. touchStartY = event.touches[0].clientY;
  86. // If touch starts from near the left edge, always prevent default
  87. if (touchStartX <= EDGE_THRESHOLD) {
  88. event.preventDefault();
  89. debugLog('Touch started near left edge');
  90. }
  91. }, { passive: false });
  92.  
  93. document.addEventListener('touchmove', function(event) {
  94. const currentX = event.touches[0].clientX;
  95. const deltaX = currentX - touchStartX;
  96. // If started from left edge, prevent default during the swipe
  97. if (touchStartX <= EDGE_THRESHOLD && deltaX > 0) {
  98. event.preventDefault();
  99. debugLog('Preventing default during edge swipe');
  100. }
  101. }, { passive: false });
  102.  
  103. document.addEventListener('touchend', function(event) {
  104. if (!touchStartX) return;
  105.  
  106. const touchEndX = event.changedTouches[0].clientX;
  107. const deltaX = touchEndX - touchStartX;
  108.  
  109. // If it was a left-to-right swipe starting from the edge
  110. if (touchStartX <= EDGE_THRESHOLD && deltaX >= SWIPE_THRESHOLD) {
  111. const activeElement = document.querySelector('.flux.active');
  112. if (activeElement) {
  113. event.preventDefault();
  114. scrollToNextElement(activeElement);
  115. }
  116. }
  117.  
  118. // Reset touch tracking
  119. touchStartX = 0;
  120. touchStartY = 0;
  121. }, { passive: false });
  122.  
  123. // Mutation observer to catch programmatic changes
  124. const observer = new MutationObserver((mutations) => {
  125. mutations.forEach((mutation) => {
  126. if (mutation.target.classList && mutation.target.classList.contains('flux')) {
  127. if (mutation.target.classList.contains('active')) {
  128. debugLog('Article became active via mutation');
  129. scrollToElement(mutation.target);
  130. }
  131. }
  132. });
  133. });
  134.  
  135. // Start observing the document with the configured parameters
  136. observer.observe(document.body, {
  137. attributes: true,
  138. attributeFilter: ['class'],
  139. subtree: true
  140. });
  141. })();