fix(ui): escape user strings, lazy drag listeners, drop dead warn field
This commit is contained in:
@@ -65,6 +65,16 @@ function fmtDate(d) {
|
||||
return d.toLocaleDateString('en-US', { weekday: 'short', day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
if (s == null) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
export class Dialog {
|
||||
constructor({ onTargetChange, onModeChange, onPosChange, onClose } = {}) {
|
||||
this.onTargetChange = onTargetChange;
|
||||
@@ -74,7 +84,6 @@ export class Dialog {
|
||||
this.root = null;
|
||||
this.dragState = null;
|
||||
this.mode = 'free';
|
||||
this.warn = null;
|
||||
}
|
||||
|
||||
mount({ initialMode = 'free', initialPos = { x: 0, y: 0 } } = {}) {
|
||||
@@ -136,31 +145,34 @@ export class Dialog {
|
||||
}
|
||||
|
||||
_wireHeaderDrag() {
|
||||
const onDown = (e) => {
|
||||
if (this.mode !== 'free') return;
|
||||
const self = this;
|
||||
this.root.addEventListener('mousedown', (e) => {
|
||||
if (self.mode !== 'free') return;
|
||||
if (e.target.classList.contains('tat-close')) return;
|
||||
const rect = this.root.getBoundingClientRect();
|
||||
this.dragState = { dx: e.clientX - rect.left, dy: e.clientY - rect.top };
|
||||
const rect = self.root.getBoundingClientRect();
|
||||
self.dragState = { dx: e.clientX - rect.left, dy: e.clientY - rect.top };
|
||||
e.preventDefault();
|
||||
};
|
||||
const onMove = (e) => {
|
||||
if (!this.dragState) return;
|
||||
const x = e.clientX - this.dragState.dx;
|
||||
const y = e.clientY - this.dragState.dy;
|
||||
this.root.style.left = `${x}px`;
|
||||
this.root.style.top = `${y}px`;
|
||||
this.root.style.right = 'auto';
|
||||
this.root.style.bottom = 'auto';
|
||||
|
||||
const onMove = (ev) => {
|
||||
if (!self.dragState) return;
|
||||
const x = ev.clientX - self.dragState.dx;
|
||||
const y = ev.clientY - self.dragState.dy;
|
||||
self.root.style.left = x + 'px';
|
||||
self.root.style.top = y + 'px';
|
||||
self.root.style.right = 'auto';
|
||||
self.root.style.bottom = 'auto';
|
||||
};
|
||||
const onUp = () => {
|
||||
if (!this.dragState) return;
|
||||
const rect = this.root.getBoundingClientRect();
|
||||
this.dragState = null;
|
||||
this.onPosChange && this.onPosChange({ x: rect.left, y: rect.top });
|
||||
if (!self.dragState) return;
|
||||
const r = self.root.getBoundingClientRect();
|
||||
self.dragState = null;
|
||||
self.onPosChange && self.onPosChange({ x: r.left, y: r.top });
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
};
|
||||
this.root.addEventListener('mousedown', onDown);
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
}
|
||||
|
||||
render(state) {
|
||||
@@ -169,7 +181,7 @@ export class Dialog {
|
||||
if (error) {
|
||||
this.root.innerHTML = `
|
||||
<div class="tat-header"><strong>⚙ Attribute Tracker</strong><span class="tat-close">✕</span></div>
|
||||
<div class="tat-error">${error}<button data-action="reload">Reload</button></div>
|
||||
<div class="tat-error">${esc(error)}<button data-action="reload">Reload</button></div>
|
||||
`;
|
||||
this.root.querySelector('[data-action="reload"]').onclick = () => location.reload();
|
||||
this.root.querySelector('.tat-close').onclick = () => this.onClose && this.onClose();
|
||||
@@ -192,7 +204,7 @@ export class Dialog {
|
||||
<strong>⚙ Attribute Tracker</strong>
|
||||
<span class="tat-close" title="Hide for this session">✕</span>
|
||||
</div>
|
||||
<div class="tat-row"><span>Attribute</span><span><strong>${attr || '—'}</strong> · <em>${gym || '—'}</em></span></div>
|
||||
<div class="tat-row"><span>Attribute</span><span><strong>${esc(attr)}</strong> · <em>${esc(gym)}</em></span></div>
|
||||
<div class="tat-row"><span>Current</span><span>${fmtFull(current)}</span></div>
|
||||
<div class="tat-row tat-target">
|
||||
<span>Target</span>
|
||||
@@ -211,7 +223,7 @@ export class Dialog {
|
||||
<div class="tat-row"><span>Trains to go</span><span>≈ ${fmtFull(est.trainsToGo)}</span></div>
|
||||
<div class="tat-row"><span>ETA</span><span>${est.days > 0 ? `~ ${fmtFull(est.days)} days (${fmtDate(est.eta)})` : '—'}</span></div>
|
||||
<div class="tat-modes">${modes}</div>
|
||||
${warn ? `<div class="tat-warn">⚠ ${warn}</div>` : ''}
|
||||
${warn ? `<div class="tat-warn">⚠ ${esc(warn)}</div>` : ''}
|
||||
`;
|
||||
|
||||
this.root.querySelector('.tat-close').onclick = () => this.onClose && this.onClose();
|
||||
|
||||
Reference in New Issue
Block a user