Files
edentext/src/styles/editor.css
T

441 lines
15 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.editor {
flex: 1;
padding: 2rem 1rem 3rem; /* extra bottom padding clears the fixed status bar */
overflow: auto;
/* Positioning context for the floating table toolbar (TableToolbar.svelte),
which is an absolutely-positioned child that scrolls with the content. */
position: relative;
}
/* Reserves the SCALED footprint of the transformed .paper (a CSS transform takes
no layout space of its own), so the scrollbars and horizontal centering reflect
the zoomed page. Its width/height are sized in JS (Editor.svelte). */
.paper-scaler {
margin: 0 auto;
}
/* A4 paper container (794px = 210mm at 96dpi by default; --user-page-width is
set from the Layout panel and swaps to 1123px in landscape — pageOrientation.ts) */
.paper {
width: var(--user-page-width, 794px);
/* Zoom is a `transform: scale()` set inline (Editor.svelte); pin the origin so the
page scales down/right from the top-left of its reserved footprint. */
transform-origin: top left;
/* Positioning context for the table page-break overlay (.band-layer in
Editor.svelte), which lives inside .paper so the same transform scales it
identically to the .tiptap page background. */
position: relative;
}
.paper .tiptap {
/* Driven by --user-page-height (pageOrientation.ts); fallback = A4 portrait. */
--page-height: var(--user-page-height, 1123px);
--page-gap: 20px;
/* Driven by --user-margin-* set on :root from the Layout panel (pageMargins.ts).
Fallbacks preserve the original hardcoded layout when unset. */
--page-margin-top: var(--user-margin-top, 96px);
--page-margin-bottom: var(--user-margin-bottom, 96px);
--page-margin-left: var(--user-margin-left, 80px);
--page-margin-right: var(--user-margin-right, 80px);
--page-cycle: calc(var(--page-height) + var(--page-gap));
position: relative;
width: 100%;
min-height: var(--page-height);
padding: var(--page-margin-top) var(--page-margin-right) var(--page-margin-bottom) var(--page-margin-left);
outline: none;
font-family: var(--font-serif);
/* 12pt matches odf-kit's Standard style and the toolbar's declared default. */
font-size: 12pt;
/* ODF/LibreOffice "single" spacing = the font's natural line height
(Liberation Serif ≈ 1.15× em), NOT 1.0× the font size like CSS unitless 1.
Keep this in sync with LINE_HEIGHT_RATIO in lineHeight.ts. */
line-height: 1.15;
color: var(--color-page-text);
/* Multi-page background: page surfaces separated by editor background */
background:
/* Left border per page */
repeating-linear-gradient(
to bottom,
var(--color-page-border) 0px,
var(--color-page-border) var(--page-height),
transparent var(--page-height),
transparent var(--page-cycle)
),
/* Right border per page */
repeating-linear-gradient(
to bottom,
var(--color-page-border) 0px,
var(--color-page-border) var(--page-height),
transparent var(--page-height),
transparent var(--page-cycle)
),
/* Top/bottom border lines of each page */
repeating-linear-gradient(
to bottom,
var(--color-page-border) 0px,
var(--color-page-border) 1px,
transparent 1px,
transparent calc(var(--page-height) - 1px),
var(--color-page-border) calc(var(--page-height) - 1px),
var(--color-page-border) var(--page-height),
transparent var(--page-height),
transparent var(--page-cycle)
),
/* Page surface (white) alternating with gap (transparent = editor bg shows) */
repeating-linear-gradient(
to bottom,
var(--color-page-bg) 0px,
var(--color-page-bg) var(--page-height),
transparent var(--page-height),
transparent var(--page-cycle)
);
background-size:
1px var(--page-cycle),
1px var(--page-cycle),
100% var(--page-cycle),
100% var(--page-cycle);
background-position:
left top,
right top,
left top,
left top;
background-repeat: repeat-y;
border: none;
/* Subtle overall shadow for depth */
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}
/* Page break spacer widgets (inserted by ProseMirror decorations).
line-height/font-size are zeroed so the spacer doesn't pick up the
inherited inline strut when it sits mid-paragraph between text nodes —
otherwise the browser adds a phantom line of height around the widget
and the continuation text on the next page lands too low.
Scoped to div so it never overrides the display of the table-row spacer below. */
.paper .tiptap div[data-page-break-spacer] {
display: block;
width: 100%;
margin: 0;
padding: 0;
border: none;
background: none;
line-height: 0;
font-size: 0;
overflow: hidden;
}
/* Table page-break spacer: a borderless, empty <tr>/<td> that bridges the page
gap so the following rows start on the next page (a table can't hold a block
spacer between its rows). Keeps default table-row/cell display. */
.paper .tiptap tr[data-page-break-spacer] {
background: none;
}
.paper .tiptap td[data-page-break-spacer-cell] {
padding: 0;
border: none;
line-height: 0;
font-size: 0;
}
.paper .tiptap p {
margin-top: 0;
/* matches odf-kit Standard style fo:margin-bottom (0.212cm ≈ 6pt) */
margin-bottom: 0.212cm;
}
.paper .tiptap h1,
.paper .tiptap h2,
.paper .tiptap h3 {
font-weight: 700;
line-height: 1.15;
margin-top: 1.5em;
margin-bottom: 0.5em;
}
.paper .tiptap h1 {
font-size: 20pt;
}
.paper .tiptap h2 {
font-size: 16pt;
}
.paper .tiptap h3 {
font-size: 14pt;
}
.paper .tiptap ul,
.paper .tiptap ol {
padding-left: 1.5rem;
margin-top: 0;
margin-bottom: 0.212cm;
}
/* Ordered-list numbering styles (Toolbar split-button dropdown → orderedList
`listStyleType` attr, rendered as data-list-style). The dot variants reuse the
built-in counter styles (which already render with a trailing "."); the bracket
variants override the suffix via @counter-style … system: extends. `decimal`
needs no rule (the <ol> default already renders "1."). Keep in sync with
src/lib/editor/orderedListTypes.ts and export/odt.ts. */
@counter-style oledit-decimal-paren { system: extends decimal; suffix: ") "; }
@counter-style oledit-lower-alpha-paren { system: extends lower-alpha; suffix: ") "; }
@counter-style oledit-upper-alpha-paren { system: extends upper-alpha; suffix: ") "; }
@counter-style oledit-lower-roman-paren { system: extends lower-roman; suffix: ") "; }
@counter-style oledit-upper-roman-paren { system: extends upper-roman; suffix: ") "; }
.paper .tiptap ol[data-list-style="decimal-paren"] { list-style-type: oledit-decimal-paren; }
.paper .tiptap ol[data-list-style="lower-alpha"] { list-style-type: lower-alpha; }
.paper .tiptap ol[data-list-style="lower-alpha-paren"] { list-style-type: oledit-lower-alpha-paren; }
.paper .tiptap ol[data-list-style="upper-alpha"] { list-style-type: upper-alpha; }
.paper .tiptap ol[data-list-style="upper-alpha-paren"] { list-style-type: oledit-upper-alpha-paren; }
.paper .tiptap ol[data-list-style="lower-roman"] { list-style-type: lower-roman; }
.paper .tiptap ol[data-list-style="lower-roman-paren"] { list-style-type: oledit-lower-roman-paren; }
.paper .tiptap ol[data-list-style="upper-roman"] { list-style-type: upper-roman; }
.paper .tiptap ol[data-list-style="upper-roman-paren"] { list-style-type: oledit-upper-roman-paren; }
.paper .tiptap li {
margin-top: 0;
margin-bottom: 0;
}
.paper .tiptap li p {
margin-bottom: 0;
}
/* Tables. The visual border/padding must match the ODT export so the editor
and the exported .odt look identical (see export/odt.ts CUST_TABLE handler):
• border → export emits 0.5pt solid #000000; 1px solid renders crisply
on screen and is the visual equivalent of a thin 0.5pt line.
• padding → export emits fo:padding 0.1cm per cell (CELL_PADDING).
Tables are wrapped in <div class="tableWrapper"> by TipTap's node view. */
.paper .tiptap .tableWrapper {
margin: 0 0 0.212cm;
overflow-x: auto;
}
.paper .tiptap table {
width: 100%;
/* fixed layout → the per-column <col> percentage widths from the custom node
view (tableView.ts) are authoritative; width:100% keeps the table the full
text width, matching the ODT export (export/odt.ts emits per-column widths
summing to the text width with table:align="margins"). */
table-layout: fixed;
border-collapse: collapse;
}
.paper .tiptap td,
.paper .tiptap th {
border: 1px solid #000;
padding: 0.1cm;
vertical-align: top;
/* Positioning context for the column-resize handle widget (a child div the
resize plugin places at the cell's right edge). */
position: relative;
/* Cells hold paragraphs; suppress their bottom margin so a single-line cell
isn't taller than its text (matches the list-item paragraph treatment). */
}
.paper .tiptap td > p,
.paper .tiptap th > p {
margin-bottom: 0;
}
/* Selected-cell highlight while editing (ProseMirror tables add .selectedCell).
A translucent overlay via the ::after avoids disturbing the cell border. */
.paper .tiptap .selectedCell {
position: relative;
}
.paper .tiptap .selectedCell::after {
content: '';
position: absolute;
inset: 0;
background: var(--color-primary);
opacity: 0.12;
pointer-events: none;
}
/* Column resize (tableColumnResize.ts). The handle is a widget the plugin places
at a cell's right edge when the pointer is near an internal column border; the
plugin reads the pointer position itself, so the handle ignores pointer events.
`.resize-cursor` is toggled on the whole editor while a border is active. */
.paper .tiptap.resize-cursor {
cursor: col-resize;
}
.paper .tiptap .column-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: 0;
width: 4px;
z-index: 20;
background: var(--color-primary);
pointer-events: none;
}
.paper .tiptap .column-resize-dragging {
/* Suppress text-selection flicker while dragging a column border. */
user-select: none;
}
/* Row resize (tableRowResize.ts). Mirrors the column handle but sits at a cell's
lower edge; one handle per cell forms a continuous line across the active row.
`.row-resize-cursor` is toggled on the whole editor while a row border is active.
Column resize takes precedence at a corner, so the two never fight. */
.paper .tiptap.row-resize-cursor {
cursor: row-resize;
}
.paper .tiptap .row-resize-handle {
position: absolute;
left: 0;
right: 0;
bottom: -2px;
height: 4px;
z-index: 20;
background: var(--color-primary);
pointer-events: none;
}
.paper .tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: var(--color-text-muted);
pointer-events: none;
height: 0;
}
/* Formatting marks (Toggle in extended toolbar).
Zero-width inline-block with visible overflow: the ¶ glyph renders just
past the last character without consuming line width — so right/center/
justify alignment of the actual text is preserved. The leading NBSP in
the content gives a small visual gap from the text. */
.paper.show-formatting-marks .tiptap p::after,
.paper.show-formatting-marks .tiptap h1::after,
.paper.show-formatting-marks .tiptap h2::after,
.paper.show-formatting-marks .tiptap h3::after {
content: '\00A0¶';
display: inline-block;
width: 0;
overflow: visible;
white-space: pre;
color: var(--color-primary);
opacity: 0.5;
font-family: var(--font-sans);
font-weight: 400;
user-select: none;
pointer-events: none;
}
.paper.show-formatting-marks .tiptap li > p::after {
content: '\00A0¶';
}
/* Empty block fix: when a paragraph/heading/li-paragraph contains only the
ProseMirror trailing-break BR, the inline ::after would otherwise render
on a second line below the BR, doubling the block's height and shifting
the page-break calculation. Pull ::after out of flow via absolute positioning. */
.paper.show-formatting-marks .tiptap p:has(> br.ProseMirror-trailingBreak:only-child),
.paper.show-formatting-marks .tiptap h1:has(> br.ProseMirror-trailingBreak:only-child),
.paper.show-formatting-marks .tiptap h2:has(> br.ProseMirror-trailingBreak:only-child),
.paper.show-formatting-marks .tiptap h3:has(> br.ProseMirror-trailingBreak:only-child),
.paper.show-formatting-marks .tiptap li > p:has(> br.ProseMirror-trailingBreak:only-child) {
position: relative;
}
.paper.show-formatting-marks .tiptap p:has(> br.ProseMirror-trailingBreak:only-child)::after,
.paper.show-formatting-marks .tiptap h1:has(> br.ProseMirror-trailingBreak:only-child)::after,
.paper.show-formatting-marks .tiptap h2:has(> br.ProseMirror-trailingBreak:only-child)::after,
.paper.show-formatting-marks .tiptap h3:has(> br.ProseMirror-trailingBreak:only-child)::after,
.paper.show-formatting-marks .tiptap li > p:has(> br.ProseMirror-trailingBreak:only-child)::after {
position: absolute;
top: 0;
left: 0;
margin-left: 0;
}
.paper.show-formatting-marks .tiptap .pm-space-mark {
position: relative;
}
.paper.show-formatting-marks .tiptap .pm-space-mark::before {
content: '·';
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-primary);
opacity: 0.55;
font-weight: 700;
pointer-events: none;
user-select: none;
}
/* Print styles: proper A4 pagination */
@media print {
.editor {
padding: 0;
overflow: visible;
}
/* Never scale for print — the real page size comes from @page below. */
.paper-scaler {
width: auto;
height: auto;
}
.paper {
width: 100%;
max-width: none;
transform: none;
}
.paper .tiptap {
background: white !important;
box-shadow: none;
border: none;
padding: 0;
min-height: auto;
}
.paper .tiptap > *[data-page-break] {
margin-top: 0 !important;
page-break-before: always;
}
@page {
size: A4;
margin: 25.4mm;
}
.paper.show-formatting-marks .tiptap p::after,
.paper.show-formatting-marks .tiptap h1::after,
.paper.show-formatting-marks .tiptap h2::after,
.paper.show-formatting-marks .tiptap h3::after,
.paper.show-formatting-marks .tiptap li > p::after,
.paper.show-formatting-marks .tiptap .pm-space-mark::before {
content: none;
}
}
/* Fallback channel: drive color from the --font-color custom property so the
visible color survives even if ProseMirror's incremental DOM update leaves
a reused span with a stale `style` attribute that no longer contains
`color:`. The inline `color:` declaration still wins when present. */
.tiptap [data-color] {
color: var(--font-color, currentColor);
}
/* allBlack theme: force all explicitly-colored text to the page text color.
The color is still stored on the textStyle mark, so .odt exports remain faithful. */
[data-theme="allBlack"] .tiptap [data-color] {
color: var(--color-page-text) !important;
}