fix(print): default to portrait, opt-in landscape for wide surfaces (t-paliad-233)
The smart-timeline-chart block in global.css declared @page { size: A4
landscape } inside @media print. @page rules are global even when nested
in selectors, so this leaked landscape onto every printed surface in
paliad — not just the chart.
Switch to named-page strategy:
- Default @page { size: A4 portrait; margin: 1.5cm 1.2cm }
- @page paliad-landscape { size: A4 landscape; margin: 1.5cm }
- @media print: body.<surface> { page: paliad-landscape } opts surfaces
that need width into landscape via per-page body classes
Landscape opt-ins:
- body.page-kostenrechner — wide fee-tier tables
- body.page-projects-chart — horizontal Smart Timeline chart
- body.events-view-calendar — /events Kalender tab (month grid)
- body.views-shape-active-calendar / -timeline — Custom Views shapes
- body.verfahrensablauf-view-timeline — horizontal procedure timeline
Body classes:
- kostenrechner.tsx, projects-chart.tsx, verfahrensablauf.tsx now set
page-<slug> on body
- verfahrensablauf.ts toggles verfahrensablauf-view-(timeline|columns)
in initViewToggle
- views.ts toggles views-shape-active-<shape> in setActiveShape (mirrors
the existing events.ts events-view-* pattern)
General print polish in the universal block (the catch-all at the bottom
of global.css):
- Hide .fab / .fab-button / .edit-mode-handle / .paliadin-widget /
[data-print-hide] in print
- thead { display: table-header-group } so headers repeat across pages
- tr/th/td page-break-inside: avoid so rows don't split mid-cell
- h1-h6 page-break-after: avoid, orphans/widows: 3 for p/h*/li
- print-color-adjust: exact on brand-coloured headers + status pills
- a[href^="http"]::after content: " (" attr(href) ")" prints external
URLs after their link text (opt-out via data-print-url="hide")
- body font-size: 11pt for print readability
Verified via Playwright on static dist build that:
- Default surfaces (dashboard, projects, fristenrechner, agenda, admin)
match no page: rule → portrait
- kostenrechner, projects-chart match the landscape rule
- verfahrensablauf-view-columns → portrait, -view-timeline → landscape
- views-shape-active-list/-cards → portrait, -calendar/-timeline →
landscape
- /events default (events-view-cards) → portrait, calendar toggle →
landscape
go build ./... + go test ./internal/... + bun test (99 pass) + bun
run build all clean.
This commit is contained in:
@@ -283,18 +283,30 @@ function selectProceeding(btn: HTMLButtonElement) {
|
||||
scheduleCalc(0);
|
||||
}
|
||||
|
||||
function applyVerfahrensablaufViewBodyClass(view: ProcedureView) {
|
||||
// Mirrors the events.ts pattern (body.events-view-*). The print
|
||||
// stylesheet keys `body.verfahrensablauf-view-timeline` to
|
||||
// `@page paliad-landscape`, so flipping this class is what lets a
|
||||
// user print the horizontal timeline in landscape without affecting
|
||||
// the columns view (which stays portrait).
|
||||
document.body.classList.toggle("verfahrensablauf-view-timeline", view === "timeline");
|
||||
document.body.classList.toggle("verfahrensablauf-view-columns", view === "columns");
|
||||
}
|
||||
|
||||
function initViewToggle() {
|
||||
const toggle = document.getElementById("fristen-view-toggle");
|
||||
if (!toggle) return;
|
||||
|
||||
const initial = new URLSearchParams(window.location.search).get("view");
|
||||
if (initial === "timeline") procedureView = "timeline";
|
||||
applyVerfahrensablaufViewBodyClass(procedureView);
|
||||
|
||||
toggle.querySelectorAll<HTMLInputElement>("input[name=fristen-view]").forEach((input) => {
|
||||
input.checked = input.value === procedureView;
|
||||
input.addEventListener("change", () => {
|
||||
if (!input.checked) return;
|
||||
procedureView = input.value === "columns" ? "columns" : "timeline";
|
||||
applyVerfahrensablaufViewBodyClass(procedureView);
|
||||
const url = new URL(window.location.href);
|
||||
if (procedureView === "columns") {
|
||||
url.searchParams.delete("view");
|
||||
|
||||
@@ -204,6 +204,12 @@ function setActiveShape(shape: RenderShape | null): void {
|
||||
document.querySelectorAll<HTMLButtonElement>("#views-shape-chips [data-shape]").forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.dataset.shape === shape);
|
||||
});
|
||||
// Mirror the active shape on <body> so the print stylesheet can opt
|
||||
// calendar / timeline into landscape (`@page paliad-landscape`) while
|
||||
// list / cards stay portrait — t-paliad-233.
|
||||
for (const s of ["list", "cards", "calendar", "timeline"]) {
|
||||
document.body.classList.toggle(`views-shape-active-${s}`, shape === s);
|
||||
}
|
||||
}
|
||||
|
||||
let timelineHandle: ChartHandle | null = null;
|
||||
|
||||
@@ -104,7 +104,7 @@ export function renderKostenrechner(): string {
|
||||
<title data-i18n="kosten.title">Prozesskostenrechner — Paliad</title>
|
||||
<link rel="stylesheet" href="/assets/global.css" />
|
||||
</head>
|
||||
<body className="has-sidebar">
|
||||
<body className="has-sidebar page-kostenrechner">
|
||||
<Sidebar currentPath="/tools/kostenrechner" />
|
||||
<BottomNav currentPath="/tools/kostenrechner" />
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export function renderProjectsChart(): string {
|
||||
<title data-i18n="projects.chart.title">Projekt-Chart — Paliad</title>
|
||||
<link rel="stylesheet" href="/assets/global.css" />
|
||||
</head>
|
||||
<body className="has-sidebar">
|
||||
<body className="has-sidebar page-projects-chart">
|
||||
<Sidebar currentPath="/projects" />
|
||||
<BottomNav currentPath="/projects" />
|
||||
|
||||
|
||||
@@ -4902,11 +4902,6 @@ dialog.modal::backdrop {
|
||||
font-size: 11pt;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 2cm;
|
||||
size: A4;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Geb\u00fchrentabellen --- */
|
||||
@@ -12185,8 +12180,38 @@ dialog.quick-add-sheet::backdrop {
|
||||
gericht cards, entity tables, glossar entries, checklist items) print as-is.
|
||||
Per-page print rules above this block handle their specific tweaks; this
|
||||
block is the catch-all for chrome that those rules miss.
|
||||
|
||||
Orientation strategy (t-paliad-233):
|
||||
- Default `@page` is A4 portrait. The CSS `@page` rule is *global*
|
||||
even when nested inside `@media print` — so prior to t-paliad-233
|
||||
a stray `@page { size: A4 landscape }` in the smart-timeline-chart
|
||||
block was leaking landscape onto every printed surface.
|
||||
- Surfaces that genuinely need width opt into the named
|
||||
`paliad-landscape` page via a `page: paliad-landscape` declaration
|
||||
on a per-page body class. Wired below: Kostenrechner, projects
|
||||
chart, Custom Views calendar / timeline, /events Kalender,
|
||||
Verfahrensablauf timeline view.
|
||||
============================================================================ */
|
||||
@page {
|
||||
size: A4 portrait;
|
||||
margin: 1.5cm 1.2cm;
|
||||
}
|
||||
|
||||
@page paliad-landscape {
|
||||
size: A4 landscape;
|
||||
margin: 1.5cm;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body.page-kostenrechner,
|
||||
body.page-projects-chart,
|
||||
body.events-view-calendar,
|
||||
body.views-shape-active-calendar,
|
||||
body.views-shape-active-timeline,
|
||||
body.verfahrensablauf-view-timeline {
|
||||
page: paliad-landscape;
|
||||
}
|
||||
|
||||
.header,
|
||||
.footer,
|
||||
.sidebar,
|
||||
@@ -12222,6 +12247,11 @@ dialog.quick-add-sheet::backdrop {
|
||||
.event-picker-row,
|
||||
.date-input-group,
|
||||
.wizard-step-hint,
|
||||
.fab,
|
||||
.fab-button,
|
||||
.edit-mode-handle,
|
||||
.paliadin-widget,
|
||||
[data-print-hide],
|
||||
#step-1,
|
||||
#step-2,
|
||||
#event-step-1,
|
||||
@@ -12274,14 +12304,64 @@ dialog.quick-add-sheet::backdrop {
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Tables: repeat headers on each printed page, keep rows intact. */
|
||||
thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
|
||||
tfoot {
|
||||
display: table-footer-group;
|
||||
}
|
||||
|
||||
tr,
|
||||
th,
|
||||
td {
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Orphans / widows defensive defaults. */
|
||||
p, h1, h2, h3, h4, h5, h6, li {
|
||||
orphans: 3;
|
||||
widows: 3;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 1.5cm;
|
||||
/* Brand-coloured headers and status pills keep their fill in print
|
||||
instead of losing background colour to the default print bleach. */
|
||||
.print-header,
|
||||
.checklist-regime,
|
||||
.gebuehren-table th,
|
||||
.entity-status-pill,
|
||||
.fr-col-header,
|
||||
[data-print-color="exact"] {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
|
||||
/* External hyperlinks: print the URL after the link text so the
|
||||
printed page remains traceable. Skip same-page fragment anchors
|
||||
and javascript: pseudo-links; skip links whose text already *is*
|
||||
the URL (avoids duplicates like "https://… (https://…)"). */
|
||||
a[href^="http"]:not([href*="#"])::after {
|
||||
content: " (" attr(href) ")";
|
||||
font-size: 9pt;
|
||||
color: #555;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a[href^="http"][data-print-url="hide"]::after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16066,13 +16146,10 @@ dialog.quick-add-sheet::backdrop {
|
||||
- Force the print palette regardless of the user's screen choice
|
||||
(B&W shows nothing the user didn't intend, redactable).
|
||||
- Hide chrome (sidebar, footer, header, bottom-nav, control chips).
|
||||
- Let the chart fill landscape A4 width.
|
||||
- Let the chart fill landscape A4 width via the named `paliad-landscape`
|
||||
page declared in the universal print block (t-paliad-233).
|
||||
- Add a printed header with project meta on the chart page. */
|
||||
@media print {
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 1.5cm;
|
||||
}
|
||||
body.has-sidebar > aside.sidebar,
|
||||
body.has-sidebar > .bottom-nav,
|
||||
body.has-sidebar > footer,
|
||||
|
||||
@@ -84,7 +84,7 @@ export function renderVerfahrensablauf(): string {
|
||||
<title data-i18n="tools.verfahrensablauf.title">Verfahrensablauf — Paliad</title>
|
||||
<link rel="stylesheet" href="/assets/global.css" />
|
||||
</head>
|
||||
<body className="has-sidebar">
|
||||
<body className="has-sidebar page-verfahrensablauf">
|
||||
<Sidebar currentPath="/tools/verfahrensablauf" />
|
||||
<BottomNav currentPath="/tools/verfahrensablauf" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user