Skip to content

Instantly share code, notes, and snippets.

@scottj
Last active April 9, 2026 15:48
Show Gist options
  • Select an option

  • Save scottj/2b54464c7d5cbd6df7f2e38d4a04156e to your computer and use it in GitHub Desktop.

Select an option

Save scottj/2b54464c7d5cbd6df7f2e38d4a04156e to your computer and use it in GitHub Desktop.
Keyboard refresh for Fidelity portfolio
// ==UserScript==
// @name Keyboard refresh for Fidelity portfolio
// @namespace https://scottj.info/
// @version 1.2
// @description Press "u" or "r" to trigger a click action on configured pages.
// @author Scott Johnson
// @match https://digital.fidelity.com/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function () {
"use strict";
/*** ------------------ CONFIG ------------------ ***/
// Map URL paths to the CSS selector of the element to click.
// All paths are on digital.fidelity.com.
const PATH_SELECTORS = {
"/ftgw/digital/portfolio/activity": ".ao-refresh button",
"/ftgw/digital/portfolio/positions": "div[data-testid=refresh-section] button",
// Add more paths here:
// "/ftgw/digital/portfolio/activity": "button.refresh",
};
// Whether to click the FIRST matching button, or ALL
const CLICK_FIRST_ONLY = true;
// Keys that should trigger the action (lowercase single chars)
const TRIGGER_KEYS = new Set(["u", "r"]);
// Optional: add a small debounce to avoid rapid repeats
const DEBOUNCE_MS = 200;
/*** -------------------------------------------- ***/
let lastRun = 0;
// Utility: don’t trigger while the user is typing in inputs/textareas
function isTypingTarget(el) {
if (!el) return false;
const tag = el.tagName?.toLowerCase();
const editable = el.isContentEditable;
return (
editable ||
tag === "input" ||
tag === "textarea" ||
tag === "select"
);
}
// Resolve the CSS selector for the current page path
function getSelectorForPath() {
const path = window.location.pathname;
for (const [prefix, selector] of Object.entries(PATH_SELECTORS)) {
if (path === prefix || path.startsWith(prefix + "/") || path.startsWith(prefix + "?")) {
return selector;
}
}
return null;
}
// Find the buttons for the current page
function getButtons() {
const selector = getSelectorForPath();
if (!selector) return [];
return Array.from(document.querySelectorAll(selector));
}
// The action to run when hotkey is pressed
function runAction(triggerKey) {
const now = Date.now();
if (now - lastRun < DEBOUNCE_MS) return;
lastRun = now;
const buttons = getButtons();
if (buttons.length === 0) {
const selector = getSelectorForPath();
console.debug(`[Userscript] No buttons found for path "${window.location.pathname}"${selector ? ` with selector "${selector}"` : " (no selector configured)"}`);
return;
}
if (CLICK_FIRST_ONLY) {
const btn = buttons[0];
safeClick(btn, triggerKey);
} else {
buttons.forEach((btn) => safeClick(btn, triggerKey));
}
}
// Safely click: bring into view, focus, then click
function safeClick(btn, triggerKey) {
try {
//btn.scrollIntoView({ behavior: "smooth", block: "center" });
btn.focus({ preventScroll: true });
// If the site uses data attributes or special handlers, add custom behavior here:
// Example: btn.dispatchEvent(new Event('click', { bubbles: true }));
btn.click();
console.debug(`[Userscript] Triggered "${triggerKey}" -> clicked`, btn);
} catch (e) {
console.warn("[Userscript] Failed to click button:", e);
}
}
// Key listener
window.addEventListener(
"keydown",
(e) => {
// Respect IME and modifiers; only plain key presses
if (e.isComposing || e.ctrlKey || e.metaKey || e.altKey) return;
const key = e.key?.toLowerCase();
if (!TRIGGER_KEYS.has(key)) return;
// Avoid firing while typing
if (isTypingTarget(e.target)) return;
// Optional: prevent default if the page has its own hotkeys for the same keys
// e.preventDefault();
runAction(key);
},
{ passive: true }
);
// Optional: watch for dynamic DOM changes (SPAs)
//const mo = new MutationObserver(() => {
// You can hook logic here if you need to ensure content is present
// e.g., pre-cache button references if performance matters
//});
//mo.observe(document.documentElement, { subtree: true, childList: true });
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment