Compare commits
7 Commits
95c655c24c
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e44bf2b3c9 | |||
| b626fb7d41 | |||
| c6de810417 | |||
| ca83996c6e | |||
| a1e79ac801 | |||
| 7fddd84b6a | |||
| 2ed25c14de |
+10
-17
@@ -4,20 +4,20 @@ import { currentAttribute } from './dom.js';
|
||||
import { startRequestInterceptor } from './interceptor.js';
|
||||
|
||||
function findAnchorElement() {
|
||||
// Try several selectors in priority order. Torn's gym page renders the
|
||||
// training UI as <ul class="properties___HASH"> (the list of attribute
|
||||
// rows). Anchor the dialog above that list.
|
||||
// Return the element to insert the dialog BEFORE in the DOM.
|
||||
// The user wants the dialog between the notification wrapper and the
|
||||
// gym content wrapper; we insert before gymContentWrapper.
|
||||
const candidates = [
|
||||
'ul[class*="properties"]',
|
||||
'[class*="gymContent"]',
|
||||
'[class*="gymContentWrapper"]',
|
||||
// Legacy fallbacks (kept in case Torn ever wraps the list in a form):
|
||||
'[class*="gymContentWrapper"]', // outer wrapper — best insertion point
|
||||
'[class*="gymContent"]', // inner wrapper (fallback)
|
||||
'ul[class*="properties"]', // the list itself (last resort)
|
||||
// Legacy fallbacks (kept for robustness):
|
||||
'form[action*="train"]',
|
||||
'form.train-form',
|
||||
'form[class*="train"]',
|
||||
'[class*="train-button"]',
|
||||
'button[class*="train"]',
|
||||
'a[class*="train"]',
|
||||
'a[href*="train"]',
|
||||
'button[name="train"]',
|
||||
'a[href*="train"]',
|
||||
];
|
||||
@@ -99,20 +99,13 @@ function start() {
|
||||
if (prefs.mode === 'anchored') {
|
||||
const el = findAnchorElement();
|
||||
if (el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
dialog.setMode('anchored', { canAnchor: true });
|
||||
dialog._positionAnchored(rect);
|
||||
// observe
|
||||
const ro = new ResizeObserver(() => {
|
||||
if (prefs.mode === 'anchored') dialog._positionAnchored(el.getBoundingClientRect());
|
||||
});
|
||||
ro.observe(el);
|
||||
dialog.setMode('anchored', { canAnchor: true, insertBefore: 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.";
|
||||
anchorError = "Couldn't find the training UI on this page.";
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,17 @@ const STYLE = `
|
||||
font: 13px/1.4 Tahoma, Verdana, sans-serif;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
.tat-root.tat-anchored {
|
||||
position: static;
|
||||
margin: 0 0 12px 0;
|
||||
max-width: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
border: 1px solid #444;
|
||||
border-top: 2px solid #c00;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.tat-root.tat-anchored .tat-header { cursor: default; }
|
||||
.tat-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding-bottom: 8px; margin-bottom: 10px;
|
||||
@@ -117,17 +128,37 @@ export class Dialog {
|
||||
setMode(mode, anchorInfo) {
|
||||
this.mode = mode;
|
||||
if (!this.root) return;
|
||||
// Clear all position styles
|
||||
this.root.style.transform = '';
|
||||
this.root.style.top = '';
|
||||
this.root.style.bottom = '';
|
||||
this.root.style.left = '';
|
||||
this.root.style.right = '';
|
||||
this.root.classList.remove('tat-anchored');
|
||||
|
||||
if (mode === 'free') {
|
||||
// Floating mode: ensure dialog is in body and position at bottom-left.
|
||||
if (this.root.parentNode !== document.body) {
|
||||
document.body.appendChild(this.root);
|
||||
}
|
||||
this.root.style.bottom = '20px';
|
||||
this.root.style.left = '20px';
|
||||
} else if (anchorInfo && anchorInfo.canAnchor) {
|
||||
this._positionAnchored(anchorInfo.rect);
|
||||
if (anchorInfo.insertBefore) {
|
||||
// Docked mode: insert the dialog into the page flow before the
|
||||
// given element, and add the tat-anchored class to switch to
|
||||
// static positioning.
|
||||
anchorInfo.insertBefore.parentNode.insertBefore(this.root, anchorInfo.insertBefore);
|
||||
this.root.classList.add('tat-anchored');
|
||||
} else if (anchorInfo.rect) {
|
||||
// Fallback: position fixed above the rect (old behavior, used when
|
||||
// no insertion point is available but a rect was given).
|
||||
this._positionAnchored(anchorInfo.rect);
|
||||
}
|
||||
// If neither insertBefore nor rect, leave the dialog where it is
|
||||
// (the caller will show an anchorError note).
|
||||
} else {
|
||||
// Top-center fallback (used when mode is anchored but no anchor info).
|
||||
this.root.style.top = '20px';
|
||||
this.root.style.left = '50%';
|
||||
this.root.style.transform = 'translateX(-50%)';
|
||||
@@ -135,6 +166,7 @@ export class Dialog {
|
||||
}
|
||||
|
||||
_positionAnchored(rect) {
|
||||
if (!rect) return; // defensive: setMode may be called without a rect
|
||||
const dialogRect = this.root.getBoundingClientRect();
|
||||
let top = rect.top - dialogRect.height - 8;
|
||||
if (top < 8) top = 20;
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
const current = tatExtractValueFromLi(li);
|
||||
if (current == null) return null;
|
||||
const gym = tatFindGymName() || 'Unknown gym';
|
||||
return { attr: attr, current: current, gym: tatEsc(gym) };
|
||||
return { attr: attr, current: current, gym: gym };
|
||||
}
|
||||
function tatFindActiveAttributeLi() {
|
||||
// Priority 1: the <li> with the "success" class (just trained).
|
||||
@@ -276,6 +276,8 @@
|
||||
// ===== ui.js (embedded) =====
|
||||
const TAT_STYLE = `
|
||||
.tat-root { position: fixed; z-index: 99999; min-width: 320px; max-width: 420px; background: #2b2b2b; color: #ddd; border: 1px solid #444; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.4); font: 13px/1.4 Tahoma, Verdana, sans-serif; padding: 12px 14px; }
|
||||
.tat-root.tat-anchored { position: static; margin: 0 0 12px 0; max-width: none; box-shadow: none; border-radius: 0; border: 1px solid #444; border-top: 2px solid #c00; padding: 16px 20px; }
|
||||
.tat-root.tat-anchored .tat-header { cursor: default; }
|
||||
.tat-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 8px; margin-bottom: 10px; border-bottom: 1px solid #444; cursor: move; user-select: none; }
|
||||
.tat-header strong { color: #fff; }
|
||||
.tat-close { cursor: pointer; opacity: 0.6; padding: 0 4px; }
|
||||
@@ -350,12 +352,39 @@
|
||||
setMode(mode, anchorInfo) {
|
||||
this.mode = mode;
|
||||
if (!this.root) return;
|
||||
// Clear all position styles
|
||||
this.root.style.transform = ''; this.root.style.top = ''; this.root.style.bottom = ''; this.root.style.left = ''; this.root.style.right = '';
|
||||
if (mode === 'free') { this.root.style.bottom = '20px'; this.root.style.left = '20px'; }
|
||||
else if (anchorInfo && anchorInfo.canAnchor) { this._positionAnchored(anchorInfo.rect); }
|
||||
else { this.root.style.top = '20px'; this.root.style.left = '50%'; this.root.style.transform = 'translateX(-50%)'; }
|
||||
this.root.classList.remove('tat-anchored');
|
||||
if (mode === 'free') {
|
||||
// Floating mode: ensure dialog is in body and position at bottom-left.
|
||||
if (this.root.parentNode !== document.body) {
|
||||
document.body.appendChild(this.root);
|
||||
}
|
||||
this.root.style.bottom = '20px';
|
||||
this.root.style.left = '20px';
|
||||
} else if (anchorInfo && anchorInfo.canAnchor) {
|
||||
if (anchorInfo.insertBefore) {
|
||||
// Docked mode: insert the dialog into the page flow before the
|
||||
// given element, and add the tat-anchored class to switch to
|
||||
// static positioning.
|
||||
anchorInfo.insertBefore.parentNode.insertBefore(this.root, anchorInfo.insertBefore);
|
||||
this.root.classList.add('tat-anchored');
|
||||
} else if (anchorInfo.rect) {
|
||||
// Fallback: position fixed above the rect (old behavior, used when
|
||||
// no insertion point is available but a rect was given).
|
||||
this._positionAnchored(anchorInfo.rect);
|
||||
}
|
||||
// If neither insertBefore nor rect, leave the dialog where it is
|
||||
// (the caller will show an anchorError note).
|
||||
} else {
|
||||
// Top-center fallback (used when mode is anchored but no anchor info).
|
||||
this.root.style.top = '20px';
|
||||
this.root.style.left = '50%';
|
||||
this.root.style.transform = 'translateX(-50%)';
|
||||
}
|
||||
}
|
||||
_positionAnchored(rect) {
|
||||
if (!rect) return; // defensive: setMode may be called without a rect
|
||||
const dialogRect = this.root.getBoundingClientRect();
|
||||
let top = rect.top - dialogRect.height - 8;
|
||||
if (top < 8) top = 20;
|
||||
@@ -438,20 +467,20 @@
|
||||
|
||||
// ===== main.js (embedded) =====
|
||||
function findAnchorElement() {
|
||||
// Try several selectors in priority order. Torn's gym page renders the
|
||||
// training UI as <ul class="properties___HASH"> (the list of attribute
|
||||
// rows). Anchor the dialog above that list.
|
||||
// Return the element to insert the dialog BEFORE in the DOM.
|
||||
// The user wants the dialog between the notification wrapper and the
|
||||
// gym content wrapper; we insert before gymContentWrapper.
|
||||
const candidates = [
|
||||
'ul[class*="properties"]',
|
||||
'[class*="gymContent"]',
|
||||
'[class*="gymContentWrapper"]',
|
||||
'[class*="gymContent"]',
|
||||
'ul[class*="properties"]',
|
||||
// Legacy fallbacks (kept in case Torn ever wraps the list in a form):
|
||||
'form[action*="train"]',
|
||||
'form.train-form',
|
||||
'form[class*="train"]',
|
||||
'[class*="train-button"]',
|
||||
'button[class*="train"]',
|
||||
'a[class*="train"]',
|
||||
'a[href*="train"]',
|
||||
'button[name="train"]',
|
||||
'a[href*="train"]',
|
||||
];
|
||||
@@ -515,21 +544,13 @@
|
||||
if (prefs.mode === 'anchored') {
|
||||
const el = findAnchorElement();
|
||||
if (el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
dialog.setMode('anchored', { canAnchor: true });
|
||||
dialog._positionAnchored(rect);
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
const ro = new ResizeObserver(function () {
|
||||
if (prefs.mode === 'anchored') dialog._positionAnchored(el.getBoundingClientRect());
|
||||
});
|
||||
ro.observe(el);
|
||||
}
|
||||
dialog.setMode('anchored', { canAnchor: true, insertBefore: 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.";
|
||||
anchorError = "Couldn't find the training UI on this page.";
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user