Render QWord print pages as separate sheets
This commit is contained in:
@@ -60,6 +60,47 @@ describe("QWord", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("показывает отдельные листы печатной разметки по расчетному числу страниц", async () => {
|
||||
const originalResizeObserver = globalThis.ResizeObserver;
|
||||
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
||||
|
||||
globalThis.ResizeObserver = class {
|
||||
observe() {}
|
||||
disconnect() {}
|
||||
} as unknown as typeof ResizeObserver;
|
||||
window.requestAnimationFrame = ((callback: FrameRequestCallback) => {
|
||||
callback(0);
|
||||
return 1;
|
||||
}) as typeof window.requestAnimationFrame;
|
||||
|
||||
const document: WordDocument = {
|
||||
id: "word-print-pages-test",
|
||||
title: "Страницы.docx",
|
||||
updatedAt: "2026-06-02T00:00:00.000Z",
|
||||
html: "<p>Первая страница</p><p>Вторая страница</p>"
|
||||
};
|
||||
|
||||
try {
|
||||
const view = render(<QWord document={document} onChange={vi.fn()} />);
|
||||
const stage = view.getByLabelText("Редактор QWord");
|
||||
const paper = view.getByLabelText("Содержимое документа");
|
||||
const frame = view.container.querySelector(".paper-frame") as HTMLElement;
|
||||
|
||||
paper.style.setProperty("--qword-page-height", "1123px");
|
||||
Object.defineProperty(paper, "scrollHeight", { configurable: true, value: 2400 });
|
||||
fireEvent.scroll(stage);
|
||||
|
||||
await waitFor(() => expect(view.container.querySelectorAll(".page-sheet")).toHaveLength(3));
|
||||
expect(frame.style.getPropertyValue("--qword-page-count")).toBe("3");
|
||||
expect(frame.style.minHeight).toBe("3425px");
|
||||
expect(frame.style.paddingBottom).toBe("56px");
|
||||
expect(view.container.querySelectorAll(".page-break-guides span")).toHaveLength(2);
|
||||
} finally {
|
||||
globalThis.ResizeObserver = originalResizeObserver;
|
||||
window.requestAnimationFrame = originalRequestAnimationFrame;
|
||||
}
|
||||
});
|
||||
|
||||
it("переключает вкладки ленты и показывает команды выбранной вкладки", () => {
|
||||
const originalResizeObserver = globalThis.ResizeObserver;
|
||||
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
||||
|
||||
@@ -180,6 +180,7 @@ function findInPage(query: string) {
|
||||
}
|
||||
|
||||
const defaultPageViewHeight = 1180;
|
||||
const defaultPageGap = 28;
|
||||
|
||||
function pageHeightFromStyles(element: HTMLElement) {
|
||||
const pageHeight = Number.parseFloat(window.getComputedStyle(element).getPropertyValue("--qword-page-height"));
|
||||
@@ -270,6 +271,12 @@ export function QWord({ document, zoom = 100, onChange, onStatusChange }: QWordP
|
||||
const [pageView, setPageView] = useState({ count: 1, current: 1, height: defaultPageViewHeight });
|
||||
const wordCount = useMemo(() => countWords(document.html), [document.html]);
|
||||
const stageStyle = { "--qword-zoom": String(zoom / 100) } as CSSProperties;
|
||||
const pageFrameStyle = {
|
||||
"--qword-page-count": String(pageView.count),
|
||||
"--qword-page-gap": `${defaultPageGap}px`,
|
||||
minHeight: `${pageView.count * pageView.height + Math.max(0, pageView.count - 1) * defaultPageGap}px`,
|
||||
paddingBottom: pageView.count > 1 ? `${(pageView.count - 1) * defaultPageGap}px` : undefined
|
||||
} as CSSProperties;
|
||||
const activeRibbonTabLabel = wordRibbonTabs.find((tab) => tab.id === activeRibbonTab)?.label ?? "Главная";
|
||||
const stageClassName = ["document-stage", `qword-view-${viewMode}`].join(" ");
|
||||
|
||||
@@ -998,7 +1005,12 @@ export function QWord({ document, zoom = 100, onChange, onStatusChange }: QWordP
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="paper-frame">
|
||||
<div className="paper-frame" style={pageFrameStyle}>
|
||||
<div className="page-sheets" aria-hidden="true">
|
||||
{Array.from({ length: pageView.count }, (_, index) => (
|
||||
<span key={index} className="page-sheet" />
|
||||
))}
|
||||
</div>
|
||||
<article
|
||||
ref={editorRef}
|
||||
className="paper document-content"
|
||||
@@ -1015,9 +1027,7 @@ export function QWord({ document, zoom = 100, onChange, onStatusChange }: QWordP
|
||||
{pageView.count > 1 ? (
|
||||
<div className="page-break-guides" aria-hidden="true">
|
||||
{Array.from({ length: pageView.count - 1 }, (_, index) => (
|
||||
<span key={index} style={{ top: `${(index + 1) * pageView.height}px` }}>
|
||||
Страница {index + 2}
|
||||
</span>
|
||||
<span key={index} style={{ top: `${(index + 1) * pageView.height + index * defaultPageGap}px` }} />
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
+39
-30
@@ -1053,28 +1053,54 @@ button {
|
||||
}
|
||||
|
||||
.paper-frame {
|
||||
--qword-page-gap: 28px;
|
||||
--qword-page-height: 1123px;
|
||||
position: relative;
|
||||
width: var(--qword-page-width, 794px);
|
||||
zoom: var(--qword-zoom, 1);
|
||||
margin: 0 auto 38px;
|
||||
}
|
||||
|
||||
.paper {
|
||||
--qword-page-height: 1123px;
|
||||
min-height: var(--qword-page-height);
|
||||
.page-sheets {
|
||||
position: absolute;
|
||||
inset: 0 0 auto;
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--qword-page-gap);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.page-sheet {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 72px 86px;
|
||||
height: var(--qword-page-height);
|
||||
border: 1px solid #c7d1dd;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.paper {
|
||||
min-height: var(--qword-page-height);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 72px 86px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.qword-view-read .page-ruler,
|
||||
.qword-view-read .vertical-page-ruler,
|
||||
.qword-view-read .page-sheets,
|
||||
.qword-view-read .page-break-guides,
|
||||
.qword-view-web .page-ruler,
|
||||
.qword-view-web .vertical-page-ruler {
|
||||
.qword-view-web .vertical-page-ruler,
|
||||
.qword-view-web .page-sheets,
|
||||
.qword-view-web .page-break-guides {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1086,6 +1112,9 @@ button {
|
||||
.qword-view-read .paper {
|
||||
min-height: auto;
|
||||
padding: 56px 72px;
|
||||
border: 1px solid #c7d1dd;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.qword-view-web .paper-frame {
|
||||
@@ -1096,6 +1125,7 @@ button {
|
||||
.qword-view-web .paper {
|
||||
min-height: auto;
|
||||
padding: 36px 48px;
|
||||
background: #ffffff;
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
box-shadow: none;
|
||||
@@ -1104,6 +1134,7 @@ button {
|
||||
.page-break-guides {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -1111,30 +1142,8 @@ button {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: 24px;
|
||||
border-top: 1px dashed #94a3b8;
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
|
||||
.page-break-guides span::before,
|
||||
.page-break-guides span::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
width: calc(50% - 58px);
|
||||
border-top: 1px dashed #94a3b8;
|
||||
}
|
||||
|
||||
.page-break-guides span::before {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.page-break-guides span::after {
|
||||
right: 0;
|
||||
height: var(--qword-page-gap);
|
||||
background: #dedede;
|
||||
}
|
||||
|
||||
.document-content h1 {
|
||||
|
||||
Reference in New Issue
Block a user