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.

目前为 2025-01-21 提交的版本。查看 最新版本

// ==UserScript==
// @name         Strava Text Auto-Selector
// @namespace    typpi.online
// @version      1.0.8
// @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',
		];
		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);
	}
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址