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.9
  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. '.activity-description p:first-child',
  39. ];
  40. const summarySelector = '.summaryGridDataContainer';
  41.  
  42. // Function to add the event listener to target elements
  43. function addContextMenuListener(element) {
  44. console.log('Adding right-click event listener to element:', element);
  45.  
  46. element.addEventListener('contextmenu', function (event) {
  47. console.log('Right-click detected on element:', element);
  48.  
  49. event.preventDefault();
  50. console.log('Default right-click menu prevented.');
  51.  
  52. const range = document.createRange();
  53. if (element.classList.contains('summaryGridDataContainer')) {
  54. const textNode = element.childNodes[0];
  55. range.selectNodeContents(textNode);
  56. console.log('Text range selected:', textNode.textContent);
  57. } else {
  58. range.selectNodeContents(element);
  59. console.log('Text range selected:', element.textContent);
  60. }
  61.  
  62. const selection = window.getSelection();
  63. selection.removeAllRanges();
  64. selection.addRange(range);
  65. console.log('Text added to selection.');
  66.  
  67. const copiedText = selection.toString();
  68. console.log('Text copied to clipboard:', copiedText);
  69.  
  70. navigator.clipboard
  71. .writeText(copiedText)
  72. .then(() => {
  73. console.log('Clipboard write successful.');
  74. showNotification(event.clientX, event.clientY, 'Text Copied!');
  75. })
  76. .catch(() => {
  77. console.log('Clipboard write failed.');
  78. showNotification(event.clientX, event.clientY, 'Failed to Copy!');
  79. });
  80. });
  81. }
  82.  
  83. // Query elements and add event listeners initially for the first three selectors
  84. selectors.forEach((selector) => {
  85. const elements = document.querySelectorAll(selector);
  86. console.log(`Found ${elements.length} elements for selector: ${selector}`);
  87. elements.forEach(addContextMenuListener);
  88. });
  89.  
  90. // Function to handle the summaryGridDataContainer elements separately
  91. function handleSummaryGridDataContainer() {
  92. const elements = document.querySelectorAll(summarySelector);
  93. console.log(`Found ${elements.length} elements for selector: ${summarySelector}`);
  94. elements.forEach(addContextMenuListener);
  95. }
  96.  
  97. // MutationObserver to detect changes in the DOM and add event listeners to new summaryGridDataContainer elements
  98. const observer = new MutationObserver((mutations) => {
  99. mutations.forEach((mutation) => {
  100. mutation.addedNodes.forEach((node) => {
  101. if (node.nodeType === Node.ELEMENT_NODE) {
  102. if (node.matches(summarySelector)) {
  103. addContextMenuListener(node);
  104. }
  105. node.querySelectorAll(summarySelector).forEach(addContextMenuListener);
  106. }
  107. });
  108. });
  109. });
  110.  
  111. observer.observe(document.body, {
  112. childList: true,
  113. subtree: true,
  114. });
  115. console.log('MutationObserver set up to monitor the DOM for summaryGridDataContainer.');
  116.  
  117. // Handle existing summaryGridDataContainer elements initially
  118. handleSummaryGridDataContainer();
  119. }
  120.  
  121. function showNotification(x, y, message) {
  122. console.log('Displaying notification:', message);
  123.  
  124. const notification = document.createElement('div');
  125. notification.textContent = message;
  126. notification.style.position = 'absolute';
  127. notification.style.left = `${x + 10}px`;
  128. notification.style.top = `${y + 10}px`;
  129. notification.style.background = 'rgba(0, 0, 0, 0.8)';
  130. notification.style.color = 'white';
  131. notification.style.padding = '5px 10px';
  132. notification.style.borderRadius = '5px';
  133. notification.style.fontSize = '12px';
  134. notification.style.zIndex = '1000';
  135. notification.style.pointerEvents = 'none';
  136.  
  137. document.body.appendChild(notification);
  138. console.log('Notification added to DOM.');
  139.  
  140. setTimeout(() => {
  141. notification.remove();
  142. console.log('Notification removed from DOM.');
  143. }, 2000);
  144. }
  145. })();