From 76eff3c351b475694f7179512c9dc1135050c181 Mon Sep 17 00:00:00 2001 From: R3D347HR4Y Date: Mon, 15 Jun 2026 17:28:02 +0200 Subject: [PATCH] feat(drive): enhance document layout with new page separators and margin masks - Introduced `DocsPageSeparators` component to manage visual gaps between pages in the document editor. - Updated `DocsBodyMarginMasks` to include dark mode support for improved styling. - Refactored `DocsPageViewInner` to integrate new separators and margin masks, enhancing layout consistency. - Adjusted layout constants to increase the gap between stacked pages for better visual separation. - Improved test coverage for page flow calculations and layout metrics. --- .../drive/richtext/docs-body-margin-masks.tsx | 4 +- components/drive/richtext/docs-page-rims.tsx | 49 ++++ .../drive/richtext/docs-page-separators.tsx | 66 +++++ components/drive/richtext/docs-page-view.tsx | 112 ++++---- lib/drive/docs-page-flow.test.ts | 72 ++++- lib/drive/docs-page-layout-constants.ts | 2 +- lib/drive/docs-page-metrics.test.ts | 2 +- .../extensions/docs-page-flow-decoration.ts | 267 ++++++++++++++++-- next-env.d.ts | 2 +- styles/docs-print.css | 6 + styles/richtext-editor.css | 32 ++- 11 files changed, 530 insertions(+), 84 deletions(-) create mode 100644 components/drive/richtext/docs-page-rims.tsx create mode 100644 components/drive/richtext/docs-page-separators.tsx diff --git a/components/drive/richtext/docs-body-margin-masks.tsx b/components/drive/richtext/docs-body-margin-masks.tsx index adae8de..e557a12 100644 --- a/components/drive/richtext/docs-body-margin-masks.tsx +++ b/components/drive/richtext/docs-body-margin-masks.tsx @@ -38,7 +38,7 @@ export function DocsBodyMarginMasks({ return (
+ {Array.from({ length: pageCount }, (_, index) => { + const pageTop = index * (pageHeight + DOCS_PAGE_GAP_PX) + return ( +
+ ) + })} + + ) +} diff --git a/components/drive/richtext/docs-page-separators.tsx b/components/drive/richtext/docs-page-separators.tsx new file mode 100644 index 0000000..a6a2a26 --- /dev/null +++ b/components/drive/richtext/docs-page-separators.tsx @@ -0,0 +1,66 @@ +"use client" + +import { DOCS_PAGE_GAP_PX } from "@/lib/drive/docs-page-layout-constants" + +/** Canvas-colored gap + side margin gutters between stacked pages. */ +export function DocsPageSeparators({ + pageCount, + pageHeight, + pageWidth, + margins, + pageColor, + interPageSpacer, +}: { + pageCount: number + pageHeight: number + pageWidth: number + margins: { top: number; right: number; bottom: number; left: number } + pageColor: string + interPageSpacer: number +}) { + if (pageCount <= 1) return null + + return ( + <> + {Array.from({ length: pageCount - 1 }, (_, index) => { + const pageTop = index * (pageHeight + DOCS_PAGE_GAP_PX) + const gapTop = pageTop + pageHeight + const zoneTop = gapTop - margins.bottom + + return ( +
+
+
+
+
+ ) + })} + + ) +} diff --git a/components/drive/richtext/docs-page-view.tsx b/components/drive/richtext/docs-page-view.tsx index de55793..afe8f77 100644 --- a/components/drive/richtext/docs-page-view.tsx +++ b/components/drive/richtext/docs-page-view.tsx @@ -9,6 +9,8 @@ import { type DocsHeaderFooterRegion, } from "@/components/drive/richtext/docs-header-footer-region" import { DocsBodyMarginMasks } from "@/components/drive/richtext/docs-body-margin-masks" +import { DocsPageSeparators } from "@/components/drive/richtext/docs-page-separators" +import { DocsPageRims } from "@/components/drive/richtext/docs-page-rims" import { DOCS_REGION_EDIT_EVENT, type DocsRegionEditDetail, @@ -31,40 +33,12 @@ import { docsPageLengthToScreen, docsZoomToScale } from "@/lib/drive/docs-ruler- import { cn } from "@/lib/utils" import { DocsGraphicSnapGuides } from "@/components/drive/richtext/docs-graphic-snap-guides" import { DocsTableContextMenu } from "@/components/drive/richtext/docs-table-context-menu" -import { applyPageFlowLayout, computeSimulatedLayoutHeight, readPageFlowMetrics } from "@/lib/drive/extensions/docs-page-flow-decoration" +import { + applyPageFlowLayout, + measureFlowContentHeight, +} from "@/lib/drive/extensions/docs-page-flow-decoration" import { focusEditorAtPointer } from "@/lib/drive/focus-editor-at-pointer" -/** Total layout height inside ProseMirror (blocks + flow spacers). */ -function measureProseContentHeight(prose: HTMLElement): number { - const metrics = readPageFlowMetrics(prose) - if (!metrics) return 0 - - const blocks: Array<{ height: number }> = [] - for (const child of prose.children) { - const el = child as HTMLElement - if (el.classList.contains("docs-page-flow-spacer")) continue - const style = getComputedStyle(el) - const marginBottom = parseFloat(style.marginBottom) || 0 - blocks.push({ height: el.offsetHeight + marginBottom }) - } - - if (blocks.length === 0) return 0 - - const simulated = computeSimulatedLayoutHeight( - blocks, - metrics.bodyAreaH, - metrics.interPageSpacer - ) - - let maxBottom = 0 - for (const child of prose.children) { - const el = child as HTMLElement - maxBottom = Math.max(maxBottom, el.offsetTop + el.offsetHeight) - } - - return Math.max(simulated, maxBottom) -} - function DocsPageViewInner({ editor, pageLayout, @@ -227,24 +201,29 @@ function DocsPageViewInner({ let cancelled = false const measurePageCount = () => { - const prose = surface.querySelector(".ProseMirror") as HTMLElement | null - if (!prose) return - const contentHeight = measureProseContentHeight(prose) + if (editor.isDestroyed) return + const contentHeight = measureFlowContentHeight(editor.view) const count = computePageCount(contentHeight, metrics) setPageCount((prev) => (prev === count ? prev : count)) } - const runLayoutPasses = (passesLeft: number) => { + const runLayoutPasses = () => { if (cancelled || editor.isDestroyed) return - requestAnimationFrame(() => { + let passesLeft = 12 + const step = () => { if (cancelled || editor.isDestroyed) return - const changed = applyPageFlowLayout(editor) - if (changed && passesLeft > 1) { - runLayoutPasses(passesLeft - 1) - return - } - measurePageCount() - }) + requestAnimationFrame(() => { + if (cancelled || editor.isDestroyed) return + const changed = applyPageFlowLayout(editor) + passesLeft -= 1 + if (changed && passesLeft > 0) { + step() + return + } + measurePageCount() + }) + } + step() } let flushPending = false @@ -253,13 +232,13 @@ function DocsPageViewInner({ flushPending = true requestAnimationFrame(() => { flushPending = false - runLayoutPasses(2) + runLayoutPasses() }) } if (debounceId) clearTimeout(debounceId) debounceId = setTimeout(() => { debounceId = null - runLayoutPasses(2) + runLayoutPasses() }, 32) } @@ -282,6 +261,15 @@ function DocsPageViewInner({ onPageCountChangeRef.current?.(pageCount) }, [pageCount]) + useEffect(() => { + if (!showLayout || pageCount <= 1) return + const id = requestAnimationFrame(() => { + if (editor.isDestroyed) return + applyPageFlowLayout(editor) + }) + return () => cancelAnimationFrame(id) + }, [editor, pageCount, showLayout]) + const stackHeight = computeStackHeight(pageCount, pageHeight) const proseMinHeight = computeProseMinHeight(pageCount, metrics) @@ -394,7 +382,7 @@ function DocsPageViewInner({
@@ -452,6 +430,26 @@ function DocsPageViewInner({ /> ) : null} + {showLayout ? ( + + ) : null} + + {showLayout ? ( + + ) : null} + {showLayout ? Array.from({ length: pageCount }, (_, index) => { const pageTop = index * (pageHeight + DOCS_PAGE_GAP_PX) diff --git a/lib/drive/docs-page-flow.test.ts b/lib/drive/docs-page-flow.test.ts index d1eb5d6..65a15e7 100644 --- a/lib/drive/docs-page-flow.test.ts +++ b/lib/drive/docs-page-flow.test.ts @@ -1,6 +1,11 @@ import assert from "node:assert/strict" import { describe, it } from "node:test" -import { countPageFlowSpacers } from "./extensions/docs-page-flow-decoration.ts" +import { + computePageFlowPushes, + computeSimulatedLayoutHeight, + countPageFlowSpacers, +} from "./extensions/docs-page-flow-decoration.ts" +import { computePageCount } from "./docs-page-metrics.ts" describe("docs-page-flow spacers", () => { const bodyAreaH = 900 @@ -30,4 +35,69 @@ describe("docs-page-flow spacers", () => { 1 ) }) + + it("inserts spacers at each page boundary for many blocks", () => { + const blocks = Array.from({ length: 10 }, () => ({ height: 400 })) + const pushes = computePageFlowPushes(blocks, bodyAreaH, interPageSpacer) + assert.equal(pushes.length, 4) + assert.deepEqual( + pushes.map((p) => p.breakY), + [900, 2000, 3100, 4200] + ) + }) + + it("pushes content that starts in the inter-page gap", () => { + const pushes = computePageFlowPushes( + [{ height: 910 }, { height: 100 }], + bodyAreaH, + interPageSpacer + ) + assert.equal(pushes.length, 2) + assert.equal(pushes[0].blockIndex, 0) + assert.equal(pushes[1].blockIndex, 1) + assert.equal(pushes[1].pushPx, 190) + }) + + it("does not double-count nested blocks (list model)", () => { + const listHeight = 300 + const topLevelOnly = [{ height: 850 }, { height: listHeight }, { height: 100 }] + const nestedOvercount = [ + { height: 850 }, + { height: listHeight }, + ...Array.from({ length: 10 }, () => ({ height: listHeight })), + { height: 100 }, + ] + + const topLevelPushes = countPageFlowSpacers(topLevelOnly, bodyAreaH, interPageSpacer) + const nestedPushes = countPageFlowSpacers(nestedOvercount, bodyAreaH, interPageSpacer) + assert.equal(topLevelPushes, 1) + assert.ok(nestedPushes > topLevelPushes) + }) + + it("simulated height matches page count across five pages", () => { + const blocks = Array.from({ length: 10 }, () => ({ height: 400 })) + const simH = computeSimulatedLayoutHeight(blocks, bodyAreaH, interPageSpacer) + const pages = computePageCount(simH, { + bodyAreaHeight: bodyAreaH, + interPageSpacer, + pageWidth: 0, + pageHeight: 0, + margins: { top: 0, right: 0, bottom: 0, left: 0 }, + headerMarginPx: 0, + footerMarginPx: 0, + }) + assert.equal(simH, 5200) + assert.equal(pages, 5) + }) + + it("inserts multiple spacers for tall content split into lines", () => { + const lineHeight = 24 + const totalH = 2500 + const lines = Math.ceil(totalH / lineHeight) + const units = Array.from({ length: lines }, (_, i) => ({ + height: i === lines - 1 ? totalH - lineHeight * (lines - 1) : lineHeight, + })) + const pushes = computePageFlowPushes(units, bodyAreaH, interPageSpacer) + assert.ok(pushes.length >= 2) + }) }) diff --git a/lib/drive/docs-page-layout-constants.ts b/lib/drive/docs-page-layout-constants.ts index a71ffc7..60cf734 100644 --- a/lib/drive/docs-page-layout-constants.ts +++ b/lib/drive/docs-page-layout-constants.ts @@ -1,5 +1,5 @@ /** Gap between stacked pages in print layout (px, unscaled). */ -export const DOCS_PAGE_GAP_PX = 12 +export const DOCS_PAGE_GAP_PX = 24 export const DOCS_CANVAS_PADDING_Y_PX = 32 export const DOCS_CANVAS_PADDING_TOP_NARROW_PX = 0 diff --git a/lib/drive/docs-page-metrics.test.ts b/lib/drive/docs-page-metrics.test.ts index 4612584..2843471 100644 --- a/lib/drive/docs-page-metrics.test.ts +++ b/lib/drive/docs-page-metrics.test.ts @@ -26,7 +26,7 @@ describe("docs-page-metrics", () => { }) it("computes stack height with page gaps", () => { - assert.equal(computeStackHeight(2, 1122), 1122 * 2 + 12) + assert.equal(computeStackHeight(2, 1122), 1122 * 2 + 24) }) it("computes prose min height with inter-page spacers", () => { diff --git a/lib/drive/extensions/docs-page-flow-decoration.ts b/lib/drive/extensions/docs-page-flow-decoration.ts index cfe1953..534b73a 100644 --- a/lib/drive/extensions/docs-page-flow-decoration.ts +++ b/lib/drive/extensions/docs-page-flow-decoration.ts @@ -1,10 +1,15 @@ import { Extension } from "@tiptap/core" import type { Editor } from "@tiptap/react" +import type { Node as PMNode } from "@tiptap/pm/model" import { Plugin, PluginKey } from "@tiptap/pm/state" import { Decoration, DecorationSet, type EditorView } from "@tiptap/pm/view" export const PAGE_FLOW_PLUGIN_KEY = new PluginKey("docsPageFlowDecoration") +type FlowUnit = { height: number; offset: number } + +const LIST_TYPES = new Set(["bulletList", "orderedList", "taskList"]) + function decorationSetsEqual( current: DecorationSet | undefined, next: DecorationSet @@ -49,6 +54,13 @@ function blockDomAtOffset(view: EditorView, offset: number): HTMLElement | null return null } +function nestedDomAt(view: EditorView, offset: number, selector: string): HTMLElement | null { + const dom = view.nodeDOM(offset) + if (!(dom instanceof HTMLElement)) return null + if (dom.matches(selector)) return dom + return dom.closest(selector) +} + /** Block height in prose px (offsetHeight + bottom margin; immune to canvas scale). */ function measureBlockFlowHeight(dom: HTMLElement): number { const style = getComputedStyle(dom) @@ -56,6 +68,191 @@ function measureBlockFlowHeight(dom: HTMLElement): number { return dom.offsetHeight + marginBottom } +function measureVisualLineHeights(dom: HTMLElement): Array<{ top: number; height: number }> { + const blockTop = dom.getBoundingClientRect().top + const range = document.createRange() + const lines: Array<{ top: number; height: number }> = [] + + const walker = document.createTreeWalker(dom, NodeFilter.SHOW_TEXT) + while (walker.nextNode()) { + const text = walker.currentNode as Text + if (!text.nodeValue?.length) continue + range.selectNodeContents(text) + for (const rect of range.getClientRects()) { + if (rect.width <= 0 || rect.height <= 0) continue + const relTop = rect.top - blockTop + const last = lines[lines.length - 1] + if (!last || Math.abs(last.top - relTop) > 2) { + lines.push({ top: relTop, height: rect.height }) + } else { + last.height = Math.max(last.height, rect.height) + } + } + } + + if (lines.length === 0) { + const h = dom.offsetHeight + if (h > 0) lines.push({ top: 0, height: h }) + } + + return lines +} + +function findPosAtVisualLine( + view: EditorView, + dom: HTMLElement, + blockRect: DOMRect, + relTop: number +): number | null { + const coords = view.posAtCoords({ + left: blockRect.left + Math.min(Math.max(1, blockRect.width / 2), blockRect.width - 1), + top: blockRect.top + relTop + 1, + }) + return coords?.pos ?? null +} + +function estimatedLineUnits( + view: EditorView, + node: PMNode, + offset: number, + dom: HTMLElement, + totalH: number +): FlowUnit[] { + const style = getComputedStyle(dom) + let lineHeight = parseFloat(style.lineHeight) + if (!Number.isFinite(lineHeight) || style.lineHeight === "normal") { + lineHeight = (parseFloat(style.fontSize) || 16) * 1.25 + } + const lineCount = Math.max(1, Math.ceil(totalH / lineHeight)) + const textLen = Math.max(1, node.content.size) + const units: FlowUnit[] = [] + + for (let i = 0; i < lineCount; i++) { + const h = + i === lineCount - 1 + ? Math.max(1, totalH - lineHeight * (lineCount - 1)) + : lineHeight + const pos = + i === 0 + ? offset + : offset + Math.min(textLen - 1, Math.floor((textLen * i) / lineCount)) + units.push({ height: h, offset: pos }) + } + + return units +} + +function expandTallLeafUnits( + view: EditorView, + node: PMNode, + offset: number, + dom: HTMLElement, + totalH: number, + bodyAreaH: number +): FlowUnit[] { + if (totalH <= bodyAreaH) { + return [{ height: totalH, offset }] + } + + const lineSegments = measureVisualLineHeights(dom) + if (lineSegments.length <= 1) { + return estimatedLineUnits(view, node, offset, dom, totalH) + } + + const blockRect = dom.getBoundingClientRect() + return lineSegments.map((segment, lineIndex) => ({ + height: segment.height, + offset: + lineIndex === 0 + ? offset + : (findPosAtVisualLine(view, dom, blockRect, segment.top) ?? offset), + })) +} + +function expandLeafBlockUnits( + view: EditorView, + node: PMNode, + offset: number, + units: FlowUnit[], + bodyAreaH: number +): void { + const dom = blockDomAtOffset(view, offset) + if (!dom) return + const totalH = measureBlockFlowHeight(dom) + if (totalH <= 0) return + units.push(...expandTallLeafUnits(view, node, offset, dom, totalH, bodyAreaH)) +} + +function expandNodeUnits( + view: EditorView, + node: PMNode, + offset: number, + units: FlowUnit[], + bodyAreaH: number +): void { + if (!node.isBlock) return + + if (LIST_TYPES.has(node.type.name)) { + let itemOffset = offset + 1 + for (let j = 0; j < node.childCount; j++) { + const item = node.child(j) + const liDom = nestedDomAt(view, itemOffset, "li") + const h = liDom ? measureBlockFlowHeight(liDom) : 0 + if (h > 0) { + units.push(...expandTallLeafUnits(view, item, itemOffset, liDom, h, bodyAreaH)) + } + itemOffset += item.nodeSize + } + return + } + + if (node.type.name === "table") { + let rowOffset = offset + 1 + for (let j = 0; j < node.childCount; j++) { + const row = node.child(j) + if (row.type.name !== "tableRow") { + rowOffset += row.nodeSize + continue + } + const rowDom = nestedDomAt(view, rowOffset, "tr") + const h = rowDom ? measureBlockFlowHeight(rowDom) : 0 + if (h > 0) { + units.push(...expandTallLeafUnits(view, row, rowOffset, rowDom, h, bodyAreaH)) + } + rowOffset += row.nodeSize + } + return + } + + if (node.type.name === "blockquote") { + let innerOffset = offset + 1 + for (let j = 0; j < node.childCount; j++) { + const child = node.child(j) + if (child.isBlock) { + expandLeafBlockUnits(view, child, innerOffset, units, bodyAreaH) + } + innerOffset += child.nodeSize + } + return + } + + expandLeafBlockUnits(view, node, offset, units, bodyAreaH) +} + +function collectFlowUnits(view: EditorView, bodyAreaH: number): FlowUnit[] { + const units: FlowUnit[] = [] + const doc = view.state.doc + let offset = 0 + + for (let i = 0; i < doc.childCount; i++) { + const node = doc.child(i) + expandNodeUnits(view, node, offset, units, bodyAreaH) + offset += node.nodeSize + } + + return units +} + function createSpacerElement(height: number): HTMLElement { const el = document.createElement("div") el.className = "docs-page-flow-spacer" @@ -80,13 +277,24 @@ export function computePageFlowPushes( if (blockH <= 0) return while (simulatedY + blockH > nextBreakY) { + const nextBodyStart = nextBreakY + interPageSpacer + if (simulatedY < nextBreakY) { const pushPx = nextBreakY - simulatedY + interPageSpacer pushes.push({ blockIndex, pushPx, breakY: nextBreakY }) - simulatedY = nextBreakY + interPageSpacer + simulatedY = nextBodyStart nextBreakY += pageStep break } + + if (simulatedY < nextBodyStart) { + const pushPx = nextBodyStart - simulatedY + pushes.push({ blockIndex, pushPx, breakY: nextBreakY }) + simulatedY = nextBodyStart + nextBreakY += pageStep + break + } + nextBreakY += pageStep } @@ -108,11 +316,20 @@ export function computeSimulatedLayoutHeight( for (const { height: blockH } of blocks) { if (blockH <= 0) continue while (simulatedY + blockH > nextBreakY) { + const nextBodyStart = nextBreakY + interPageSpacer + if (simulatedY < nextBreakY) { - simulatedY = nextBreakY + interPageSpacer + simulatedY = nextBodyStart nextBreakY += pageStep break } + + if (simulatedY < nextBodyStart) { + simulatedY = nextBodyStart + nextBreakY += pageStep + break + } + nextBreakY += pageStep } simulatedY += blockH @@ -121,45 +338,55 @@ export function computeSimulatedLayoutHeight( return simulatedY } -function collectFlowBlocks(view: EditorView): Array<{ height: number; offset: number }> { - const blocks: Array<{ height: number; offset: number }> = [] - view.state.doc.forEach((node, offset) => { - if (!node.isBlock || node.type.name === "doc") return - const dom = blockDomAtOffset(view, offset) - if (!dom) return - const blockH = measureBlockFlowHeight(dom) - if (blockH <= 0) return - blocks.push({ height: blockH, offset }) - }) - return blocks -} - /** Build page-flow spacer widgets for blocks that cross a page body boundary. */ export function buildPageFlowDecorations(view: EditorView): DecorationSet { const metrics = readPageFlowMetrics(view.dom) if (!metrics) return DecorationSet.empty const { bodyAreaH, interPageSpacer } = metrics - const blocks = collectFlowBlocks(view) + const units = collectFlowUnits(view, bodyAreaH) const pushes = computePageFlowPushes( - blocks.map((b) => ({ height: b.height })), + units.map((u) => ({ height: u.height })), bodyAreaH, interPageSpacer ) const decorations = pushes.map(({ blockIndex, pushPx, breakY }) => { - const block = blocks[blockIndex] + const unit = units[blockIndex] return Decoration.widget( - block.offset, + unit.offset, () => createSpacerElement(pushPx), - { side: -1, key: `page-flow-spacer-${block.offset}-${breakY}`, pushPx } + { side: -1, key: `page-flow-spacer-${unit.offset}-${breakY}`, pushPx } ) }) return DecorationSet.create(view.state.doc, decorations) } +/** Measure paginated prose height using the same flow units as layout. */ +export function measureFlowContentHeight(view: EditorView): number { + const metrics = readPageFlowMetrics(view.dom) + if (!metrics) return 0 + + const units = collectFlowUnits(view, metrics.bodyAreaH) + if (units.length === 0) return 0 + + const simulated = computeSimulatedLayoutHeight( + units.map((u) => ({ height: u.height })), + metrics.bodyAreaH, + metrics.interPageSpacer + ) + + let maxBottom = 0 + for (const child of view.dom.children) { + const el = child as HTMLElement + maxBottom = Math.max(maxBottom, el.offsetTop + el.offsetHeight) + } + + return Math.max(simulated, maxBottom) +} + /** Apply page-flow layout once. Returns true if decorations changed. */ export function applyPageFlowLayout(editor: Editor): boolean { if (editor.isDestroyed || !editor.isInitialized) return false diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/styles/docs-print.css b/styles/docs-print.css index bbc2d55..e3427e1 100644 --- a/styles/docs-print.css +++ b/styles/docs-print.css @@ -58,6 +58,12 @@ border-color: transparent !important; } +.docs-printing.docs-pdf-capture .docs-page-gap-band, +.docs-printing.docs-pdf-capture .docs-page-inter-margin-gutter, +.docs-printing.docs-pdf-capture .docs-page-rim { + display: none !important; +} + .docs-printing.docs-pdf-capture .ultidrive-docs-editor-surface--dimmed .ProseMirror { opacity: 1 !important; } diff --git a/styles/richtext-editor.css b/styles/richtext-editor.css index e8b052e..b80a543 100644 --- a/styles/richtext-editor.css +++ b/styles/richtext-editor.css @@ -597,12 +597,36 @@ html.dark .ultidrive-richtext-region-editor table th { .ultidrive-docs-editor-surface--paginated { overflow: visible; + background: transparent; } .ultidrive-docs-editor-surface--paginated .ProseMirror { min-height: var(--docs-prose-min-height); - max-height: var(--docs-prose-min-height); overflow: visible; + background: transparent; +} + +.docs-page-gap-band { + background: #f9fbfd; +} + +html.dark .docs-page-gap-band { + background: #202124; +} + +.docs-page-rim { + border: 1px solid #dadce0; + box-shadow: 0 1px 3px 1px rgba(60, 64, 67, 0.15), 0 1px 2px 0 rgba(60, 64, 67, 0.3); + background: transparent; +} + +html.dark .docs-page-rim:not(.docs-page-rim--imported) { + border-color: #5f6368; + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.35), 0 1px 2px 0 rgba(0, 0, 0, 0.45); +} + +.docs-page-rim--imported { + box-shadow: none; } .docs-hf-chrome__checkbox { @@ -666,6 +690,7 @@ html.dark .docs-hf-chrome__options:hover { flex-shrink: 0; pointer-events: none; user-select: none; + background: transparent; } .ultidrive-docs-editor-surface .ProseMirror .docs-page-flow-spacer { @@ -1262,6 +1287,11 @@ html.dark .docs-menu-badge { border: 1px solid #dadce0; } +.ultidrive-docs-page--sheet { + border: none; + box-shadow: none; +} + .ultidrive-docs-page--imported-border { border: none; }