feat(bundle): mirror source changes in embedded userscript

This commit is contained in:
dev
2026-06-07 12:17:29 -05:00
parent ca83996c6e
commit c6de810417
+38 -18
View File
@@ -276,6 +276,7 @@
// ===== 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 auto 12px; max-width: 720px; box-shadow: none; }
.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,10 +351,36 @@
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
@@ -439,20 +466,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"]',
];
@@ -516,20 +543,13 @@
if (prefs.mode === 'anchored') {
const el = findAnchorElement();
if (el) {
const rect = el.getBoundingClientRect();
dialog.setMode('anchored', { canAnchor: true, rect: 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;
}