fix(richtext): blank lines survive into PDFs and read-only previews
Quill 2's semantic HTML saves a blank line as a truly empty <p></p> (no <br> placeholder) — verified in the DB (invoice_sections hold <p></p>, zero <p><br>) — which collapses to nothing outside the editor. cleanQuillHtml (PDF path) and the new restoreQuillBlankLines (order/ invoice read-only previews) restore the placeholder at render time, so existing stored content is covered retroactively. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,7 @@ import {
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
numberOr,
|
||||
restoreQuillBlankLines,
|
||||
todayLocalStr,
|
||||
} from "../utils/formatters";
|
||||
import { normalizeDateStr } from "../utils/attendanceHelpers";
|
||||
@@ -1670,7 +1671,9 @@ export default function InvoiceDetail() {
|
||||
{(s.content || "").replace(/<[^>]*>/g, "").trim() && (
|
||||
<RichTextView
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(s.content || ""),
|
||||
__html: DOMPurify.sanitize(
|
||||
restoreQuillBlankLines(s.content),
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -1690,7 +1693,9 @@ export default function InvoiceDetail() {
|
||||
invoice.internal_notes !== "<p><br></p>" ? (
|
||||
<RichTextView
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(invoice.internal_notes),
|
||||
__html: DOMPurify.sanitize(
|
||||
restoreQuillBlankLines(invoice.internal_notes),
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -18,7 +18,11 @@ import Typography from "@mui/material/Typography";
|
||||
import OrderConfirmationModal from "../components/OrderConfirmationModal";
|
||||
import Forbidden from "../components/Forbidden";
|
||||
import apiFetch from "../utils/api";
|
||||
import { formatCurrency, formatDate } from "../utils/formatters";
|
||||
import {
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
restoreQuillBlankLines,
|
||||
} from "../utils/formatters";
|
||||
import { ORDER_STATUS, statusLabel, statusColor } from "../lib/documentStatus";
|
||||
import {
|
||||
Button,
|
||||
@@ -713,7 +717,9 @@ export default function OrderDetail() {
|
||||
<RichTextView
|
||||
sx={{ p: 2 }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(section.content),
|
||||
__html: DOMPurify.sanitize(
|
||||
restoreQuillBlankLines(section.content),
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -76,3 +76,15 @@ export function czechPlural(
|
||||
if (n >= 2 && n <= 4) return few;
|
||||
return many;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quill 2's semantic HTML saves blank lines as truly empty <p></p>, which
|
||||
* collapse to zero height outside the editor — restore the <br> placeholder
|
||||
* before rendering stored rich text read-only. (The PDF path does the same
|
||||
* in cleanQuillHtml.)
|
||||
*/
|
||||
export function restoreQuillBlankLines(
|
||||
html: string | null | undefined,
|
||||
): string {
|
||||
return (html || "").replace(/<p([^>]*)>\s*<\/p>/gi, "<p$1><br></p>");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user