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.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Strava Text Auto-Selector
// @namespace    typpi.online
// @version      1.0.9
// @description  Automatically selects text in specific Strava elements and displays a notification near the cursor. Also allows right-click to copy text.
// @author       Nick2bad4u
// @license      UnLicense
// @homepageURL  https://github.com/Nick2bad4u/UserScripts
// @grant        none
// @include      *://*.strava.com/activities/*
// @include      *://*.strava.com/athlete/training
// @icon         https://www.google.com/s2/favicons?sz=64&domain=strava.com
// ==/UserScript==

(function () {
	'use strict';

	// Log when the script starts
	console.log('Strava Text Auto-Selector script loaded.');

	// Wait for the page to fully load
	window.addEventListener('load', function () {
		console.log('Page fully loaded, initializing script.');

		// Delay script execution for 500 ms
		setTimeout(initializeScript, 500);
	});

	function initializeScript() {
		console.log('Initializing script after delay.');

		const selectors = [
			'#search-results > tbody > tr:nth-child(n) > td.view-col.col-title > a',
			'.summaryGrid .summaryGridDataContainer, .inline-stats strong, .inline-stats b',
			'#heading > div > div.row.no-margins.activity-summary-container > div.spans8.activity-summary.mt-md.mb-md > div.details-container > div > h1',
			'.ride .segment-effort-detail .effort-details table, .swim .segment-effort-detail .effort-details table',
			'.activity-description p:only-child',
			'.activity-description p:first-child',
		];
		const summarySelector = '.summaryGridDataContainer';

		// Function to add the event listener to target elements
		function addContextMenuListener(element) {
			console.log('Adding right-click event listener to element:', element);

			element.addEventListener('contextmenu', function (event) {
				console.log('Right-click detected on element:', element);

				event.preventDefault();
				console.log('Default right-click menu prevented.');

				const range = document.createRange();
				if (element.classList.contains('summaryGridDataContainer')) {
					const textNode = element.childNodes[0];
					range.selectNodeContents(textNode);
					console.log('Text range selected:', textNode.textContent);
				} else {
					range.selectNodeContents(element);
					console.log('Text range selected:', element.textContent);
				}

				const selection = window.getSelection();
				selection.removeAllRanges();
				selection.addRange(range);
				console.log('Text added to selection.');

				const copiedText = selection.toString();
				console.log('Text copied to clipboard:', copiedText);

				navigator.clipboard
					.writeText(copiedText)
					.then(() => {
						console.log('Clipboard write successful.');
						showNotification(event.clientX, event.clientY, 'Text Copied!');
					})
					.catch(() => {
						console.log('Clipboard write failed.');
						showNotification(event.clientX, event.clientY, 'Failed to Copy!');
					});
			});
		}

		// Query elements and add event listeners initially for the first three selectors
		selectors.forEach((selector) => {
			const elements = document.querySelectorAll(selector);
			console.log(`Found ${elements.length} elements for selector: ${selector}`);
			elements.forEach(addContextMenuListener);
		});

		// Function to handle the summaryGridDataContainer elements separately
		function handleSummaryGridDataContainer() {
			const elements = document.querySelectorAll(summarySelector);
			console.log(`Found ${elements.length} elements for selector: ${summarySelector}`);
			elements.forEach(addContextMenuListener);
		}

		// MutationObserver to detect changes in the DOM and add event listeners to new summaryGridDataContainer elements
		const observer = new MutationObserver((mutations) => {
			mutations.forEach((mutation) => {
				mutation.addedNodes.forEach((node) => {
					if (node.nodeType === Node.ELEMENT_NODE) {
						if (node.matches(summarySelector)) {
							addContextMenuListener(node);
						}
						node.querySelectorAll(summarySelector).forEach(addContextMenuListener);
					}
				});
			});
		});

		observer.observe(document.body, {
			childList: true,
			subtree: true,
		});
		console.log('MutationObserver set up to monitor the DOM for summaryGridDataContainer.');

		// Handle existing summaryGridDataContainer elements initially
		handleSummaryGridDataContainer();
	}

	function showNotification(x, y, message) {
		console.log('Displaying notification:', message);

		const notification = document.createElement('div');
		notification.textContent = message;
		notification.style.position = 'absolute';
		notification.style.left = `${x + 10}px`;
		notification.style.top = `${y + 10}px`;
		notification.style.background = 'rgba(0, 0, 0, 0.8)';
		notification.style.color = 'white';
		notification.style.padding = '5px 10px';
		notification.style.borderRadius = '5px';
		notification.style.fontSize = '12px';
		notification.style.zIndex = '1000';
		notification.style.pointerEvents = 'none';

		document.body.appendChild(notification);
		console.log('Notification added to DOM.');

		setTimeout(() => {
			notification.remove();
			console.log('Notification removed from DOM.');
		}, 2000);
	}
})();