diff --git a/src/main.js b/src/main.js index c868bf2..6f88918 100644 --- a/src/main.js +++ b/src/main.js @@ -4,11 +4,34 @@ import { currentAttribute } from './dom.js'; import { startRequestInterceptor } from './interceptor.js'; function findAnchorElement() { - // Torn's training form is the element containing the Train button. - // Selector is best-effort; the Dialog will fall back if missing. - const btn = document.querySelector('button[name="train"], a[href*="train"]'); - if (!btn) return null; - return btn.closest('form') || btn.parentElement; + // Try several selectors in priority order. Torn's gym page structure + // varies; we cast a wide net and return the first match. + const candidates = [ + 'form[action*="train"]', + 'form.train-form', + 'form[class*="train"]', + '[class*="train-button"]', + 'button[class*="train"]', + 'a[class*="train"]', + 'button[name="train"]', + 'a[href*="train"]', + ]; + for (const sel of candidates) { + const el = document.querySelector(sel); + if (el) { + // Prefer the form ancestor if the match is a button/link, since we + // want to anchor above the whole form, not just the button. + return el.closest('form') || el; + } + } + // Last-ditch: any element inside the gym panel that looks like the + // training form. + const panel = document.querySelector('.gym, #gym, [class*="gym-"], [class*="Gym"]'); + if (panel) { + const form = panel.querySelector('form'); + if (form) return form; + } + return null; } function start() { @@ -40,6 +63,7 @@ function start() { let lastSnapshot = null; let lastAttr = null; let lastDelta = 0; + let anchorError = null; function snapshot() { const a = currentAttribute(); @@ -56,6 +80,7 @@ function start() { perTrain: lastDelta, summary, warn: store._saveDisabled ? 'saving disabled this session' : null, + anchorError: anchorError, }; } @@ -76,9 +101,16 @@ function start() { if (prefs.mode === 'anchored') dialog._positionAnchored(el.getBoundingClientRect()); }); ro.observe(el); + anchorError = null; return; } + // Anchor selector missed — don't snap to default, just keep current + // position and show a note. + anchorError = "Couldn't find the training form on this page."; + render(); + return; } + anchorError = null; dialog.setMode('free'); } diff --git a/src/ui.js b/src/ui.js index 610d691..d5b6599 100644 --- a/src/ui.js +++ b/src/ui.js @@ -30,6 +30,7 @@ const STYLE = ` } .tat-modes button.active { background: #444; border-color: #888; } .tat-warn { color: #c90; margin-top: 6px; font-size: 11px; } +.tat-anchor-err { color: #c90; margin-top: 6px; font-size: 11px; } .tat-error { padding: 8px 0; color: #f88; } .tat-error button { margin-left: 8px; } `; @@ -227,6 +228,7 @@ export class Dialog {
ETA${est.days > 0 ? `~ ${fmtFull(est.days)} days (${fmtDate(est.eta)})` : '—'}
${modes}
${warn ? `
⚠ ${esc(warn)}
` : ''} + ${state.anchorError ? `
⚠ ${esc(state.anchorError)}
` : ''} `; this.root.querySelector('.tat-close').onclick = () => this.onClose && this.onClose();