Skip to content

Commit 2bb29af

Browse files
committed
plan: support multiline mileage paste down consecutive days
1 parent 3511d20 commit 2bb29af

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

static/plan.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,82 @@
570570
return values;
571571
}
572572

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+
573649
function sessionDetailsFromRow(row) {
574650
const fromDetail = Array.isArray(row && row.planned_sessions_detail) ? row.planned_sessions_detail : [];
575651
const normalized = [];
@@ -1746,6 +1822,25 @@
17461822
}
17471823

17481824
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+
17491844
bodyEl.addEventListener("change", (event) => {
17501845
const target = event.target;
17511846
if (!(target instanceof Element)) return;

0 commit comments

Comments
 (0)