hotfix pagebreak

This commit is contained in:
Mateusz Gruszczyński committed 2026-09-12 10:09:25 +02:00
1 parent f16a860f0a
commit 295761123f
5 files changed
+118 -4

No files matched your search

Generated
+1 -1
View File
@@ -2581,7 +2581,7 @@ dependencies = [
[[package]]
name = "rustpad"
version = "0.2.73"
version = "0.2.74"
dependencies = [
"argon2",
"aws-config",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "rustpad"
version = "0.2.73"
version = "0.2.74"
edition = "2024"
rust-version = "1.94"
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
+78
View File
@@ -0,0 +1,78 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/static/css/styles.css">
<link rel="stylesheet" href="/static/libs/rustpad-player/player.css">
<script>window.__RUSTPAD_CONFIG__={assetVersion:"test"};</script>
<script type="importmap">
{"imports":{
"@rustpad/i18n":"/static/js/i18n.js",
"@rustpad/toast":"/static/js/toast.js",
"@rustpad/vendor-libs":"/static/js/vendor-libs.js",
"@rustpad/markdown":"/static/js/markdown.js",
"@rustpad/emoji-data":"/static/js/emoji-data.js",
"@rustpad/image-alias":"/static/js/image-alias.js"
}}
</script>
</head>
<body>
<div id="toast"></div>
<button id="pdf">PDF</button>
<dialog id="dlg">
<form id="form" method="dialog">
<select id="pdf-page-format"><option value="screen-16-9" selected>screen</option></select>
<select id="pdf-page-margin"><option value="12" selected>12</option></select>
<input id="pdf-include-title" type="checkbox">
<input id="pdf-preserve-colors" type="checkbox">
<button data-pdf-cancel type="button">cancel</button>
<button type="submit">go</button>
</form>
</dialog>
<div id="preview" class="preview markdown-body"></div>
<script type="module">
import { renderMarkdown } from "@rustpad/markdown";
import { bindPdfExport } from "/static/js/pdf-export.js";
const source = await (await fetch('/TEST_NOTE_FEATURES.txt')).text();
const preview = document.querySelector('#preview');
preview.innerHTML = renderMarkdown(source);
// Replace raw Mermaid source blocks with representative SVGs so pagination
// exercises the same tall/wide atomic visuals as the real rendered preview.
[...preview.querySelectorAll('.mermaid')].forEach((node, index) => {
const tall = index === 2 || index === 3 || index === 4;
const height = tall ? 1500 : 520;
node.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 ${height}" width="900" height="${height}"><rect width="900" height="${height}" fill="#efeae2"/><rect x="90" y="60" width="720" height="180" rx="16" fill="#202323"/><text x="450" y="160" text-anchor="middle" font-size="56" fill="white">Mermaid ${index + 1}</text><line x1="450" y1="240" x2="450" y2="${height - 100}" stroke="#777" stroke-width="6"/></svg>`;
});
let popupFrame = null;
window.open = () => {
popupFrame = document.createElement('iframe');
popupFrame.id = 'print-frame';
popupFrame.style.cssText = 'position:absolute;left:-10000px;top:0;width:1400px;height:900px;border:0';
document.body.appendChild(popupFrame);
const win = popupFrame.contentWindow;
win.focus = () => {};
win.print = async () => {
const html = win.document.documentElement.outerHTML;
await fetch('/capture', { method: 'POST', headers: {'Content-Type':'text/html'}, body: html });
document.documentElement.dataset.captureDone = '1';
};
return win;
};
const dialog = document.querySelector('#dlg');
bindPdfExport({
button: document.querySelector('#pdf'),
dialog,
form: document.querySelector('#form'),
preview,
title: () => 'TEST_NOTE_FEATURES',
});
dialog.showModal();
document.querySelector('#form').dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
</script>
</body>
</html>
+24
View File
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8">
<link rel="stylesheet" href="file:///mnt/data/rustpad_pdf_fix/static/css/styles.css">
<link rel="stylesheet" href="file:///mnt/data/rustpad_pdf_fix/static/libs/rustpad-player/player.css">
<script>window.__RUSTPAD_CONFIG__={assetVersion:"test"};</script>
<script type="importmap">{"imports": {"@rustpad/i18n": "file:///mnt/data/rustpad_pdf_fix/static/js/i18n.js", "@rustpad/toast": "file:///mnt/data/rustpad_pdf_fix/static/js/toast.js", "@rustpad/vendor-libs": "file:///mnt/data/rustpad_pdf_fix/static/js/vendor-libs.js", "@rustpad/markdown": "file:///mnt/data/rustpad_pdf_fix/static/js/markdown.js", "@rustpad/emoji-data": "file:///mnt/data/rustpad_pdf_fix/static/js/emoji-data.js", "@rustpad/image-alias": "file:///mnt/data/rustpad_pdf_fix/static/js/image-alias.js"}}</script></head><body>
<div id="toast"></div><button id="pdf">PDF</button><dialog id="dlg"><form id="form" method="dialog">
<select id="pdf-page-format"><option value="screen-16-9" selected>screen</option></select>
<select id="pdf-page-margin"><option value="12" selected>12</option></select>
<input id="pdf-include-title" type="checkbox"><input id="pdf-preserve-colors" type="checkbox">
<button data-pdf-cancel type="button">cancel</button><button type="submit">go</button></form></dialog>
<div id="preview" class="preview markdown-body"></div>
<script type="module">
import { renderMarkdown } from "@rustpad/markdown";
import { bindPdfExport } from "file:///mnt/data/rustpad_pdf_fix/static/js/pdf-export.js";
const source="# RustPad Complete Feature Demonstration {#rustpad-complete-test}\n\nThis note is an extensive test document for the RustPad editor, Markdown renderer, preview editor, file handling, media players, collaboration tools, and mobile interface.\n\nUse **Markdown mode** and begin in **Split view**. Later, follow the manual interface checklist near the end of this note.\n\n## Preparation before testing\n\nUpload files with these exact names so every local attachment example can resolve:\n\n- `TEST_IMAGE.png` \u2014 any PNG or JPEG image renamed to this filename.\n- `TEST_VIDEO.mp4` \u2014 a short MP4 video.\n- `TEST_DOCUMENT.pdf` \u2014 any PDF document.\n- `TEST_ARCHIVE.zip` \u2014 any ZIP archive.\n\nUntil the files are uploaded, aliases such as `[image=TEST_IMAGE.png,...]` remain visible as plain text. This is expected.\n\nThe generated table of contents below should include only headings after the `[TOC]` marker, nest lower-level headings under their nearest parent, and avoid adding a second numeric sequence to headings that are already numbered.\n\n[TOC]\n\n---\n\n# 1. Headings and navigation {#headings-and-navigation}\n\n## Heading level 2\n\n### Heading level 3\n\n#### Heading level 4\n\n##### Heading level 5\n\n###### Heading level 6\n\nThe toolbar exposes H1\u2013H4, while the renderer also supports H5 and H6 entered manually.\n\n## Custom heading identifier {#custom-anchor-example}\n\nThis heading uses the explicit identifier `custom-anchor-example`.\n\n[Jump to the custom heading](#custom-anchor-example)\n\n[Return to the top](#rustpad-complete-test)\n\n## Repeated heading\n\nThe first repeated heading should receive the generated anchor `repeated-heading`.\n\n## Repeated heading\n\nThe second repeated heading should receive a unique generated anchor such as `repeated-heading-2`.\n\n---\n\n# 2. Paragraphs, wrapping, and plain text\n\nThis is a normal paragraph. It should wrap naturally when the editor or preview becomes narrow. Resize the browser window and verify that no horizontal page overflow is introduced by ordinary text.\n\nThis deliberately long sentence tests wrapping, compact mode, synchronized scrolling, font changes, mobile layout, and preview editing while preserving a readable paragraph across narrow and wide screens without forcing the entire interface beyond the available viewport width.\n\nA blank source line creates visible paragraph spacing.\n\nThe next line contains characters that should be escaped rather than executed:\n\n<script>alert(\"This must never execute\")</script>\n\nHTML other than the supported `<details>` and `<summary>` block should be displayed safely as text.\n\n---\n\n# 3. Inline formatting\n\n**Bold text**\n\n*Italic text*\n\n~~Strikethrough text~~\n\n==Highlighted text==\n\nH~2~O uses subscript.\n\nE = mc^2^ uses superscript.\n\nUse `cargo test`, `npm run build`, and `SELECT * FROM notes;` as inline code.\n\nFormatting can appear in one sentence: **bold**, *italic*, ~~deleted~~, ==important==, H~2~O, x^2^, and `inline code`.\n\nSelect one or more lines in the editor and press a formatting button. Multi-line selection should apply or remove the wrapper on each non-empty selected line.\n\n---\n\n# 4. Emoji support\n\nShortcodes should render as emoji:\n\n:smile: :rocket: :white_check_mark: :warning: :heart: :fire: :computer: :memo: :bug: :tada:\n\nThe **Emoji** toolbar menu should also:\n\n- search by emoji name or keyword,\n- switch between categories,\n- insert the selected emoji at the current cursor position,\n- close after insertion.\n\nDirect Unicode emoji should remain unchanged: \ud83d\ude00 \ud83d\ude80 \u2705 \u26a0\ufe0f \u2764\ufe0f \ud83d\udd25 \ud83e\uddea \ud83d\udcce \ud83c\udfac.\n\n---\n\n# 5. Links and automatic link detection\n\n[Example link](https://example.com)\n\n[Example link with a title](https://example.com \"Open example.com\")\n\n[Relative link to the RustPad home page](/)\n\n[Internal link to the media section](#media-and-attachments)\n\nA plain URL should become clickable automatically:\n\nhttps://example.com/documentation\n\nTrailing punctuation should not become part of the URL: https://example.com/testing.\n\nA mail address using the mailto scheme should become clickable:\n\nmailto:qa@example.com\n\nA deliberately unsafe URL should be neutralized instead of executed:\n\n[Blocked JavaScript URL](javascript:alert%281%29)\n\nA protocol-relative URL should also be rejected:\n\n[Blocked protocol-relative URL](//example.com/path)\n\n---\n\n# 6. Blockquotes\n\n> RustPad renders a quoted line as a blockquote.\n\n> This second source line is another quote and can be edited directly in Preview mode.\n\n> **Formatting**, `inline code`, links such as [RustPad home](/), and :memo: emoji work inside a quote.\n\n---\n\n# 7. Unordered, ordered, nested, and task lists\n\n## Unordered list\n\n- First unordered item\n- Second unordered item with **bold text**\n- Third unordered item with a [link](https://example.com)\n+ A plus sign is Line truncated
const preview=document.querySelector('#preview'); preview.innerHTML=renderMarkdown(source);
[...preview.querySelectorAll('.mermaid')].forEach((node,index)=>{
const tall=index===2||index===3||index===4; const height=tall?1500:520;
node.innerHTML=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 ${height}" width="900" height="${height}"><rect width="900" height="${height}" fill="#efeae2"/><rect x="90" y="60" width="720" height="180" rx="16" fill="#202323"/><text x="450" y="160" text-anchor="middle" font-size="56" fill="white">Mermaid ${index+1}</text><line x1="450" y1="240" x2="450" y2="${height-100}" stroke="#777" stroke-width="6"/></svg>`;
});
window.open=()=>{ const frame=document.createElement('iframe'); frame.style.cssText='position:absolute;left:-10000px;top:0;width:1400px;height:900px;border:0'; document.body.appendChild(frame); const win=frame.contentWindow; win.focus=()=>{}; win.print=()=>{ window.__CAPTURE_HTML__=win.document.documentElement.outerHTML; document.documentElement.dataset.captureDone='1'; }; return win; };
const dialog=document.querySelector('#dlg'); bindPdfExport({button:document.querySelector('#pdf'),dialog,form:document.querySelector('#form'),preview,title:()=> 'TEST_NOTE_FEATURES'});
dialog.showModal(); document.querySelector('#form').dispatchEvent(new Event('submit',{bubbles:true,cancelable:true}));
</script></body></html>
+14 -2
View File
@@ -427,15 +427,27 @@ function printStyles(cssSize, pageWidthMm, pageHeightMm, marginMm, preserveColor
box-sizing: border-box;
}
.pdf-content .preview-source-line::before { display: none !important; content: none !important; }
.pdf-content .preview-source-line { min-height: 0; }
.pdf-content .preview-source-line { min-height: 0; position: static !important; }
.pdf-content .preview-editable:hover,
.pdf-content .preview-editable:focus { background: transparent !important; box-shadow: none !important; }
.pdf-content a { color: inherit !important; text-decoration: underline; }
/* Book-like pagination: keep small semantic units together, not whole sections. */
/*
* Do not rely on break-after: avoid for the main Markdown headings.
* Chromium can first paint the heading at the end of the old page and
* then move it to the next page, leaving a clipped strip of glyphs behind.
* A small invisible reserve inside the heading keeps one following line
* with it without triggering that fragmentation bug.
*/
.pdf-content h1,
.pdf-content h2,
.pdf-content h3,
.pdf-content h3 {
break-after: auto;
page-break-after: auto;
padding-bottom: 1.15em;
margin-bottom: calc(.18em - 1.15em);
}
.pdf-content h4,
.pdf-content h5,
.pdf-content h6,