feat(chat): save a single answer as PDF via the print dialog
Hides app chrome via a 'printing-answer' class on body, leaves only the .print-target visible. The class is removed in a finally so a cancelled print dialog can't leave the app invisible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
7d066f37dc
commit
e528589e9c
2 changed files with 50 additions and 1 deletions
|
|
@ -32,6 +32,21 @@ export function ChatView() {
|
|||
const bottomRef = React.useRef<HTMLDivElement>(null);
|
||||
const inputRef = React.useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
/** Print one answer. The class is removed in a finally so a cancelled
|
||||
print dialog cannot leave the app invisible. */
|
||||
const printAnswer = React.useCallback((index: number) => {
|
||||
const el = document.getElementById(`answer-${index}`);
|
||||
if (!el) return;
|
||||
el.classList.add("print-target");
|
||||
document.body.classList.add("printing-answer");
|
||||
try {
|
||||
window.print();
|
||||
} finally {
|
||||
document.body.classList.remove("printing-answer");
|
||||
el.classList.remove("print-target");
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Pending photos attached to the next user turn. Declared before any
|
||||
// handler that reads it; useCallback closes over this state at the
|
||||
// time the handler is created, so the state must be in scope by then.
|
||||
|
|
@ -337,7 +352,7 @@ export function ChatView() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div key={i} className="border-l-2 border-[#F2B165]/40 pl-4">
|
||||
<div key={i} id={`answer-${i}`} className="border-l-2 border-[#F2B165]/40 pl-4">
|
||||
{t.text ? (
|
||||
<AnswerMarkdown>{t.text}</AnswerMarkdown>
|
||||
) : (
|
||||
|
|
@ -364,6 +379,12 @@ export function ChatView() {
|
|||
{toolLabel(name)}
|
||||
</span>
|
||||
))}
|
||||
<button
|
||||
onClick={() => printAnswer(i)}
|
||||
className="print-hide ml-auto rounded-full border border-border px-2 py-0.5 text-[11px] text-muted-foreground transition-colors hover:border-[#F2B165]/50 hover:text-foreground"
|
||||
>
|
||||
Save as PDF
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -216,3 +216,31 @@
|
|||
background-size: 32px 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Printing an answer: hide the app chrome and let the answer fill the page. Triggered by adding .printing-answer to <body>. */
|
||||
@media print {
|
||||
body.printing-answer * {
|
||||
visibility: hidden;
|
||||
}
|
||||
body.printing-answer .print-target,
|
||||
body.printing-answer .print-target * {
|
||||
visibility: visible;
|
||||
}
|
||||
body.printing-answer .print-target {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
max-width: none;
|
||||
}
|
||||
body.printing-answer .print-hide {
|
||||
display: none !important;
|
||||
}
|
||||
/* Keep table rows intact across page breaks. */
|
||||
body.printing-answer table {
|
||||
page-break-inside: auto;
|
||||
}
|
||||
body.printing-answer tr {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue