-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathGarminAutoPaste.user.js
More file actions
115 lines (108 loc) · 4.6 KB
/
GarminAutoPaste.user.js
File metadata and controls
115 lines (108 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// ==UserScript==
// @name Garmin Connect - Auto-Paste on Right Click
// @namespace nick2bad4u.github.io
// @version 1.7
// @description Automatically pastes clipboard content into specific input fields on right-click. Clipboard access requires HTTPS and user permission. Right-clicking on target fields will auto-paste clipboard content. (Note: Clipboard API support may vary by browser.)
// @author Nick2bad4u
// @match *://connect.garmin.com/*
// @license UnLicense
// @tag garmin
// @icon https://www.google.com/s2/favicons?sz=64&domain=connect.garmin.com
// @grant none
// @run-at document-end
// @homepageURL https://github.com/Nick2bad4u/UserStyles
// @homepage https://github.com/Nick2bad4u/UserStyles
// @supportURL https://github.com/Nick2bad4u/UserStyles/issues
// @downloadURL https://update.greasyfork.org/scripts/536012/Garmin%20Connect%20-%20Auto-Paste%20on%20Right%20Click.user.js
// @updateURL https://update.greasyfork.org/scripts/536012/Garmin%20Connect%20-%20Auto-Paste%20on%20Right%20Click.meta.js
// ==/UserScript==
(function () {
'use strict';
// === Configurable Section ===
const FIELD_CONFIG = [
{
selector: 'input[name="elev-gain"]',
name: 'elev-gain',
sanitize: (text) => text.replace(/[^\d.-]/g, ''),
},
{
selector: 'input[name="elev-loss"]',
name: 'elev-loss',
sanitize: (text) => text.replace(/[^\d.-]/g, ''),
},
{
selector: 'input[name="youtubeUrl"]',
name: 'youtubeUrl',
// Allow more URL-safe characters
sanitize: (text) => text.replace(/[^\w\-.:/?=&%#~@!$*'(),]/g, ''),
},
{
selector: 'textarea#activity-description',
name: 'activity-description',
// Allow most printable characters, but sanitize HTML special chars
sanitize: (text) => text.replace(/[<>&"'`]/g, (char) => escapeMap[char] || char),
},
];
const TARGET_INPUT_SELECTOR = FIELD_CONFIG.map((f) => f.selector).join(', ');
// Define escapeMap outside the event handler for efficiency
const escapeMap = {
'<': '<',
'>': '>',
'&': '&',
'"': '"',
"'": ''',
'`': '`',
};
// Add a tooltip to target fields to inform users about right-click auto-paste
function addAutoPasteTooltip() {
document.querySelectorAll(TARGET_INPUT_SELECTOR).forEach((input) => {
if (!input.hasAttribute('data-autopaste-tooltip')) {
input.setAttribute('title', 'Right-click to auto-paste clipboard content. Clipboard access required.');
input.setAttribute('aria-label', 'Right-click to auto-paste clipboard content. Clipboard access required.');
input.setAttribute('data-autopaste-tooltip', '1');
}
});
}
// Debounce function for performance
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// Initial tooltip set and observe for dynamically added fields
addAutoPasteTooltip();
const observer = new MutationObserver(debounce(addAutoPasteTooltip, 200));
observer.observe(document.body, { childList: true, subtree: true });
document.body.addEventListener('contextmenu', function (event) {
const target = event.target;
// Type safety: ensure target is an input or textarea element
if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return;
// Check if right-clicked element is one of the target input fields
const field = FIELD_CONFIG.find((f) => target.matches(f.selector));
if (field) {
event.preventDefault(); // Prevent default right-click menu
void (async () => {
if (navigator.clipboard && navigator.clipboard.readText) {
try {
// Use clipboard API to read the text and paste it
const clipboardText = await navigator.clipboard.readText();
// Sanitize clipboard content to prevent injection of malicious content
let sanitizedText = clipboardText.replace(/[<>&"'`]/g, (char) => escapeMap[char] || char);
// Use field-specific sanitizer if available
if (field.sanitize) sanitizedText = field.sanitize(sanitizedText);
target.value = sanitizedText; // Paste sanitized clipboard content into the field
} catch (err) {
console.error(`Clipboard read error on input field "${target.name}" at URL "${window.location.href}" using browser "${navigator.userAgent}":`, err);
alert(
'Failed to access clipboard. Clipboard access may require HTTPS and browser permission. Please check your browser settings or use Ctrl+V manually.',
);
}
} else {
alert('Clipboard API is not supported in this browser or context. Please use Ctrl+V to paste manually.');
}
})();
}
});
})();