Strava Text Auto-Selector

Automatically selects text in specific Strava elements and displays a notification near the cursor. Also allows right-click to copy text.

  1. // ==UserScript==
  2. // @name Strava Text Auto-Selector
  3. // @namespace typpi.online
  4. // @version 1.0.8
  5. // @description Automatically selects text in specific Strava elements and displays a notification near the cursor. Also allows right-click to copy text.
  6. // @author Nick2bad4u
  7. // @license UnLicense
  8. // @homepageURL https://github.com/Nick2bad4u/UserScripts
  9. // @grant none
  10. // @include *://*.strava.com/activities/*
  11. // @include *://*.strava.com/athlete/training
  12. // @icon https://www.google.com/s2/favicons?sz=64&domain=strava.com
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. // Log when the script starts
  19. console.log('Strava Text Auto-Selector script loaded.');
  20.  
  21. // Wait for the page to fully load
  22. window.addEventListener('load', function () {
  23. console.log('Page fully loaded, initializing script.');
  24.  
  25. // Delay script execution for 500 ms
  26. setTimeout(initializeScript, 500);
  27. });
  28.  
  29. function initializeScript() {
  30. console.log('Initializing script after delay.');
  31.  
  32. const selectors = [
  33. '#search-results > tbody > tr:nth-child(n) > td.view-col.col-title > a',
  34. '.summaryGrid .summaryGridDataContainer, .inline-stats strong, .inline-stats b',
  35. '#heading > div > div.row.no-margins.activity-summary-container > div.spans8.activity-summary.mt-md.mb-md > div.details-container > div > h1',
  36. '.ride .segment-effort-detail .effort-details table, .swim .segment-effort-detail .effort-details table',
  37. '.activity-description p:only-child',
  38. ];
  39. const summarySelector = '.summaryGridDataContainer';
  40.  
  41. // Function to add the event listener to target elements
  42. function addContextMenuListener(element) {
  43. console.log('Adding right-click event listener to element:', element);
  44.  
  45. element.addEventListener('contextmenu', function (event) {
  46. console.log('Right-click detected on element:', element);
  47.  
  48. event.preventDefault();
  49. console.log('Default right-click menu prevented.');
  50.  
  51. const range = document.createRange();
  52. if (element.classList.contains('summaryGridDataContainer')) {
  53. const textNode = element.childNodes[0];
  54. range.selectNodeContents(textNode);
  55. console.log('Text range selected:', textNode.textContent);
  56. } else {
  57. range.selectNodeContents(element);
  58. console.log('Text range selected:', element.textContent);
  59. }
  60.  
  61. const selection = window.getSelection();
  62. selection.removeAllRanges();
  63. selection.addRange(range);
  64. console.log('Text added to selection.');
  65.  
  66. const copiedText = selection.toString();
  67. console.log('Text copied to clipboard:', copiedText);
  68.  
  69. navigator.clipboard
  70. .writeText(copiedText)
  71. .then(() => {
  72. console.log('Clipboard write successful.');
  73. showNotification(event.clientX, event.clientY, 'Text Copied!');
  74. })
  75. .catch(() => {
  76. console.log('Clipboard write failed.');
  77. showNotification(event.clientX, event.clientY, 'Failed to Copy!');
  78. });
  79. });
  80. }
  81.  
  82. // Query elements and add event listeners initially for the first three selectors
  83. selectors.forEach((selector) => {
  84. const elements = document.querySelectorAll(selector);
  85. console.log(
  86. `Found ${elements.length} elements for selector: ${selector}`,
  87. );
  88. elements.forEach(addContextMenuListener);
  89. });
  90.  
  91. // Function to handle the summaryGridDataContainer elements separately
  92. function handleSummaryGridDataContainer() {
  93. const elements = document.querySelectorAll(summarySelector);
  94. console.log(
  95. `Found ${elements.length} elements for selector: ${summarySelector}`,
  96. );
  97. elements.forEach(addContextMenuListener);
  98. }
  99.  
  100. // MutationObserver to detect changes in the DOM and add event listeners to new summaryGridDataContainer elements
  101. const observer = new MutationObserver((mutations) => {
  102. mutations.forEach((mutation) => {
  103. mutation.addedNodes.forEach((node) => {
  104. if (node.nodeType === Node.ELEMENT_NODE) {
  105. if (node.matches(summarySelector)) {
  106. addContextMenuListener(node);
  107. }
  108. node
  109. .querySelectorAll(summarySelector)
  110. .forEach(addContextMenuListener);
  111. }
  112. });
  113. });
  114. });
  115.  
  116. observer.observe(document.body, {
  117. childList: true,
  118. subtree: true,
  119. });
  120. console.log(
  121. 'MutationObserver set up to monitor the DOM for summaryGridDataContainer.',
  122. );
  123.  
  124. // Handle existing summaryGridDataContainer elements initially
  125. handleSummaryGridDataContainer();
  126. }
  127.  
  128. function showNotification(x, y, message) {
  129. console.log('Displaying notification:', message);
  130.  
  131. const notification = document.createElement('div');
  132. notification.textContent = message;
  133. notification.style.position = 'absolute';
  134. notification.style.left = `${x + 10}px`;
  135. notification.style.top = `${y + 10}px`;
  136. notification.style.background = 'rgba(0, 0, 0, 0.8)';
  137. notification.style.color = 'white';
  138. notification.style.padding = '5px 10px';
  139. notification.style.borderRadius = '5px';
  140. notification.style.fontSize = '12px';
  141. notification.style.zIndex = '1000';
  142. notification.style.pointerEvents = 'none';
  143.  
  144. document.body.appendChild(notification);
  145. console.log('Notification added to DOM.');
  146.  
  147. setTimeout(() => {
  148. notification.remove();
  149. console.log('Notification removed from DOM.');
  150. }, 2000);
  151. }
  152. })();