Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-<id>-filtered.log`)

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |

---

Expand Down
32 changes: 29 additions & 3 deletions server/components/LogCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
defaultLevelFilters,
filterLogLines,
formatLineHash,
joinLinesForExport,
parseLineHash,
parseLogLines,
parseSearchQuery,
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -310,6 +321,21 @@ export function LogCanvas({
>
Download .log
</button>
<button
type="button"
onClick={downloadFiltered}
disabled={visibleLines.length === 0}
title={
isFiltered
? `Download ${visibleLines.length} visible line(s) after filters`
: "Download currently visible lines (same as full paste when unfiltered)"
}
className="w-full rounded border border-vscode-border bg-vscode-line px-2 py-1.5 text-xs hover:border-vscode-accent disabled:cursor-not-allowed disabled:opacity-40"
>
{isFiltered
? `Download filtered (${visibleLines.length})`
: "Download visible"}
</button>
{selection ? (
<button
type="button"
Expand Down
14 changes: 14 additions & 0 deletions server/lib/log-lines.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
filterLogLines,
formatLineHash,
isLineSelected,
joinLinesForExport,
parseLineHash,
parseLogLines,
parseSearchQuery,
Expand Down Expand Up @@ -65,6 +66,19 @@ describe("parseSearchQuery / filterLogLines", () => {
expect(q.regex).toBeNull();
expect(q.error).toBeTruthy();
});

it("joinLinesForExport joins visible raw lines", () => {
expect(joinLinesForExport([])).toBe("");
const filtered = filterLogLines(
lines,
defaultLevelFilters(),
parseSearchQuery("beta"),
);
expect(joinLinesForExport(filtered)).toBe("[ERROR] beta");
expect(joinLinesForExport(lines)).toBe(
["[INFO] alpha", "[ERROR] beta", "gamma noise"].join("\n"),
);
});
});

describe("line hash selection", () => {
Expand Down
9 changes: 9 additions & 0 deletions server/lib/log-lines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ export function filterLogLines(
);
}

/**
* Join visible lines for download/export.
* Uses original `raw` (keeps ANSI); no trailing newline when empty or single empty line set.
*/
export function joinLinesForExport(lines: readonly ParsedLogLine[]): string {
if (lines.length === 0) return "";
return lines.map((line) => line.raw).join("\n");
}

export interface LineSelection {
start: number; // 1-based inclusive
end: number; // 1-based inclusive
Expand Down
Loading