|
570 | 570 | return values; |
571 | 571 | } |
572 | 572 |
|
| 573 | + function parseMileagePasteValues(rawText) { |
| 574 | + const normalized = String(rawText || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n"); |
| 575 | + if (!normalized.trim()) return { values: [] }; |
| 576 | + const lines = normalized.split("\n"); |
| 577 | + while (lines.length > 0 && !String(lines[lines.length - 1] || "").trim()) { |
| 578 | + lines.pop(); |
| 579 | + } |
| 580 | + const values = []; |
| 581 | + for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) { |
| 582 | + const cells = String(lines[lineIndex] || "").split("\t"); |
| 583 | + for (let cellIndex = 0; cellIndex < cells.length; cellIndex += 1) { |
| 584 | + const rawCell = String(cells[cellIndex] || "").trim(); |
| 585 | + if (!rawCell) { |
| 586 | + values.push(""); |
| 587 | + continue; |
| 588 | + } |
| 589 | + const parsed = Number.parseFloat(rawCell.replace(/,/g, "")); |
| 590 | + if (!Number.isFinite(parsed) || parsed < 0) { |
| 591 | + return { |
| 592 | + error: `Invalid mileage "${rawCell}" at line ${lineIndex + 1}.`, |
| 593 | + values: [], |
| 594 | + }; |
| 595 | + } |
| 596 | + values.push(parsed > 0 ? formatSessionValue(parsed) : ""); |
| 597 | + } |
| 598 | + } |
| 599 | + return { values }; |
| 600 | + } |
| 601 | + |
| 602 | + async function applyDistancePaste(targetInput, rawText) { |
| 603 | + const parsed = parseMileagePasteValues(rawText); |
| 604 | + if (parsed.error) { |
| 605 | + if (metaEl) metaEl.textContent = parsed.error; |
| 606 | + return false; |
| 607 | + } |
| 608 | + const values = Array.isArray(parsed.values) ? parsed.values : []; |
| 609 | + if (values.length <= 1) { |
| 610 | + return false; |
| 611 | + } |
| 612 | + |
| 613 | + const startDate = String(targetInput && targetInput.dataset ? targetInput.dataset.date || "" : ""); |
| 614 | + const startIndex = rowIndexByDate(startDate); |
| 615 | + if (!isIsoDateString(startDate) || startIndex < 0) { |
| 616 | + return false; |
| 617 | + } |
| 618 | + |
| 619 | + const lastNeededDate = addDaysIso(startDate, values.length - 1); |
| 620 | + if (isIsoDateString(lastNeededDate)) { |
| 621 | + await ensureDateLoadedForCenter(lastNeededDate); |
| 622 | + } |
| 623 | + |
| 624 | + let appliedCount = 0; |
| 625 | + for (let offset = 0; offset < values.length; offset += 1) { |
| 626 | + const row = rowAt(renderedRows, startIndex + offset); |
| 627 | + if (!row || !isIsoDateString(row.date)) break; |
| 628 | + const rowIndex = rowIndexByDate(row.date); |
| 629 | + if (rowIndex < 0) continue; |
| 630 | + const inputEl = bodyEl.querySelector(distanceSelectorForDate(row.date)); |
| 631 | + if (!(inputEl instanceof HTMLInputElement)) continue; |
| 632 | + inputEl.value = String(values[offset] || ""); |
| 633 | + const nextRow = rowAt(renderedRows, rowIndex + 1); |
| 634 | + saveSessionPayload( |
| 635 | + row, |
| 636 | + rowIndex, |
| 637 | + renderedRows, |
| 638 | + nextRow ? nextRow.date : addDaysIso(row.date, 1), |
| 639 | + ); |
| 640 | + appliedCount += 1; |
| 641 | + } |
| 642 | + |
| 643 | + if (appliedCount > 0 && metaEl) { |
| 644 | + metaEl.textContent = `Pasted ${appliedCount} day(s) starting ${startDate}.`; |
| 645 | + } |
| 646 | + return appliedCount > 0; |
| 647 | + } |
| 648 | + |
573 | 649 | function sessionDetailsFromRow(row) { |
574 | 650 | const fromDetail = Array.isArray(row && row.planned_sessions_detail) ? row.planned_sessions_detail : []; |
575 | 651 | const normalized = []; |
|
1746 | 1822 | } |
1747 | 1823 |
|
1748 | 1824 | if (bodyEl) { |
| 1825 | + bodyEl.addEventListener("paste", (event) => { |
| 1826 | + const target = event.target; |
| 1827 | + if (!(target instanceof HTMLInputElement)) return; |
| 1828 | + if (!target.matches(".plan-session-distance")) return; |
| 1829 | + const rawText = event.clipboardData ? event.clipboardData.getData("text/plain") : ""; |
| 1830 | + if (!rawText) return; |
| 1831 | + const parsed = parseMileagePasteValues(rawText); |
| 1832 | + if (parsed.error) { |
| 1833 | + event.preventDefault(); |
| 1834 | + if (metaEl) metaEl.textContent = parsed.error; |
| 1835 | + return; |
| 1836 | + } |
| 1837 | + if (!Array.isArray(parsed.values) || parsed.values.length <= 1) { |
| 1838 | + return; |
| 1839 | + } |
| 1840 | + event.preventDefault(); |
| 1841 | + void applyDistancePaste(target, rawText); |
| 1842 | + }); |
| 1843 | + |
1749 | 1844 | bodyEl.addEventListener("change", (event) => { |
1750 | 1845 | const target = event.target; |
1751 | 1846 | if (!(target instanceof Element)) return; |
|
0 commit comments