From df58598fbd71147c2c8f950d19b17da8390f70d2 Mon Sep 17 00:00:00 2001 From: ArianAr Date: Thu, 16 Jul 2026 00:40:52 +0300 Subject: [PATCH] feat(ui): download filtered / visible canvas lines Export currently visible lines (after level + search filters) as paste--filtered.log. Full raw download remains available. Closes #29 --- CHANGELOG.md | 1 + ROADMAP.md | 2 +- server/components/LogCanvas.tsx | 32 +++++++++++++++++++++++++++++--- server/lib/log-lines.test.ts | 14 ++++++++++++++ server/lib/log-lines.ts | 9 +++++++++ 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0328a0c..a265cc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Opt-in process metrics (`PAPERCUT_METRICS=1`) — `GET /api/metrics` counters only (`pastes_created`, `unlocks_ok`, `rate_limited`); disabled by default; no content/IP logging - Log canvas **wrap / no-wrap** toggle (dense horizontal scroll), sticky line-number gutter, preference in `localStorage` - Log canvas **line pins / bookmarks** (per-paste `localStorage`, gutter star + sidebar jump list) +- Log canvas **download filtered / visible lines** (`paste--filtered.log`) ### Fixed diff --git a/ROADMAP.md b/ROADMAP.md index f94f825..e8155c6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -81,7 +81,7 @@ Focus: log analysis depth. | 1.2.3 | **Multi-file or multi-paste compare** (side-by-side) | ui, api | Optional second paste id | | 1.2.4 | **Custom highlight rules** (user regex → color) | ui | Per-browser config | | 1.2.5 | **Timeline scrubber** for timestamped logs | ui | Detect common timestamp formats | -| 1.2.6 | **Download filtered view** (not only raw) | ui | Export what you see | +| 1.2.6 | **Download filtered view** (not only raw) | ui | ✅ Done (export visible lines) | --- diff --git a/server/components/LogCanvas.tsx b/server/components/LogCanvas.tsx index c80a19e..e35ddbb 100644 --- a/server/components/LogCanvas.tsx +++ b/server/components/LogCanvas.tsx @@ -8,6 +8,7 @@ import { defaultLevelFilters, filterLogLines, formatLineHash, + joinLinesForExport, parseLineHash, parseLogLines, parseSearchQuery, @@ -186,16 +187,26 @@ export function LogCanvas({ } } - function downloadRaw() { - const blob = new Blob([rawContent], { type: "text/plain;charset=utf-8" }); + function downloadText(content: string, filename: string) { + const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; - a.download = `paste-${id}.log`; + a.download = filename; a.click(); URL.revokeObjectURL(url); } + function downloadRaw() { + downloadText(rawContent, `paste-${id}.log`); + } + + function downloadFiltered() { + downloadText(joinLinesForExport(visibleLines), `paste-${id}-filtered.log`); + } + + const isFiltered = visibleLines.length !== allLines.length; + const wrapLabel = wrapMode === "wrap" ? "No wrap" : "Wrap"; const wrapTitle = @@ -310,6 +321,21 @@ export function LogCanvas({ > Download .log + {selection ? (