diff --git a/src/ui.js b/src/ui.js index 183eeaf..9c23d64 100644 --- a/src/ui.js +++ b/src/ui.js @@ -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, '''); +} + 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 onUp = () => { - if (!this.dragState) return; - const rect = this.root.getBoundingClientRect(); - this.dragState = null; - this.onPosChange && this.onPosChange({ x: rect.left, y: rect.top }); - }; - this.root.addEventListener('mousedown', onDown); - document.addEventListener('mousemove', onMove); - document.addEventListener('mouseup', onUp); + + 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 (!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); + }; + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }); } render(state) { @@ -169,7 +181,7 @@ export class Dialog { if (error) { this.root.innerHTML = `
⚙ Attribute Tracker
-
${error}
+
${esc(error)}
`; 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 { ⚙ Attribute Tracker -
Attribute${attr || '—'} · ${gym || '—'}
+
Attribute${esc(attr)} · ${esc(gym)}
Current${fmtFull(current)}
Target @@ -211,7 +223,7 @@ export class Dialog {
Trains to go≈ ${fmtFull(est.trainsToGo)}
ETA${est.days > 0 ? `~ ${fmtFull(est.days)} days (${fmtDate(est.eta)})` : '—'}
${modes}
- ${warn ? `
⚠ ${warn}
` : ''} + ${warn ? `
⚠ ${esc(warn)}
` : ''} `; this.root.querySelector('.tat-close').onclick = () => this.onClose && this.onClose();