Respect QWord page breaks in page layout
This commit is contained in:
@@ -3,7 +3,7 @@ import { useState } from "react";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { vi } from "vitest";
|
||||
import type { WordDocument } from "../../types";
|
||||
import { QWord, qwordCurrentPageFromViewport } from "./QWord";
|
||||
import { QWord, qwordCurrentPageFromViewport, qwordPageCountFromContent } from "./QWord";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
@@ -20,6 +20,21 @@ describe("qwordCurrentPageFromViewport", () => {
|
||||
expect(qwordCurrentPageFromViewport({ stageTop: 34, paperTop: -1200, pageHeight: 1123, pageCount: 3, zoom: 1 })).toBe(2);
|
||||
expect(qwordCurrentPageFromViewport({ stageTop: 34, paperTop: -1200, pageHeight: 1123, pageCount: 3, zoom: 1.2 })).toBe(1);
|
||||
});
|
||||
|
||||
it("does not advance the current page while the viewport is inside the inter-page gap", () => {
|
||||
expect(qwordCurrentPageFromViewport({ stageTop: 0, paperTop: -110, pageHeight: 100, pageGap: 20, pageCount: 3, zoom: 1 })).toBe(1);
|
||||
expect(qwordCurrentPageFromViewport({ stageTop: 0, paperTop: -125, pageHeight: 100, pageGap: 20, pageCount: 3, zoom: 1 })).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("qwordPageCountFromContent", () => {
|
||||
it("keeps manual page breaks as a minimum page count even when content height is short", () => {
|
||||
expect(qwordPageCountFromContent({ scrollHeight: 300, pageHeight: 1123, manualPageBreakCount: 2 })).toBe(3);
|
||||
});
|
||||
|
||||
it("keeps content height page count when it exceeds manual page breaks", () => {
|
||||
expect(qwordPageCountFromContent({ scrollHeight: 2400, pageHeight: 1123, manualPageBreakCount: 1 })).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("QWord", () => {
|
||||
@@ -101,6 +116,46 @@ 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-manual-break-pages-test",
|
||||
title: "Разрыв.docx",
|
||||
updatedAt: "2026-06-04T00:00:00.000Z",
|
||||
html: '<p>Первая страница</p><span data-qoffice-page-break="true" contenteditable="false"></span><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: 300 });
|
||||
fireEvent.scroll(stage);
|
||||
|
||||
await waitFor(() => expect(view.container.querySelectorAll(".page-sheet")).toHaveLength(2));
|
||||
expect(frame.style.getPropertyValue("--qword-page-count")).toBe("2");
|
||||
expect(frame.style.minHeight).toBe("2274px");
|
||||
expect(view.container.querySelectorAll(".page-break-guides span")).toHaveLength(1);
|
||||
} finally {
|
||||
globalThis.ResizeObserver = originalResizeObserver;
|
||||
window.requestAnimationFrame = originalRequestAnimationFrame;
|
||||
}
|
||||
});
|
||||
|
||||
it("переключает вкладки ленты и показывает команды выбранной вкладки", () => {
|
||||
const originalResizeObserver = globalThis.ResizeObserver;
|
||||
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
||||
|
||||
@@ -250,12 +250,21 @@ function keepEditorSelection(event: MouseEvent<HTMLButtonElement>) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
export function qwordCurrentPageFromViewport(input: { stageTop: number; paperTop: number; pageHeight: number; pageCount: number; zoom: number }) {
|
||||
export function qwordCurrentPageFromViewport(input: { stageTop: number; paperTop: number; pageHeight: number; pageCount: number; zoom: number; pageGap?: number }) {
|
||||
const pageCount = Math.max(1, input.pageCount);
|
||||
const zoom = Number.isFinite(input.zoom) && input.zoom > 0 ? input.zoom : 1;
|
||||
const visualPageHeight = Math.max(1, input.pageHeight * zoom);
|
||||
const pageGap = Number.isFinite(input.pageGap) && input.pageGap ? Math.max(0, input.pageGap) : 0;
|
||||
const visualPageStride = Math.max(1, (input.pageHeight + pageGap) * zoom);
|
||||
const visiblePaperOffset = Math.max(0, input.stageTop - input.paperTop);
|
||||
return Math.min(pageCount, Math.max(1, Math.floor(visiblePaperOffset / visualPageHeight) + 1));
|
||||
return Math.min(pageCount, Math.max(1, Math.floor(visiblePaperOffset / visualPageStride) + 1));
|
||||
}
|
||||
|
||||
export function qwordPageCountFromContent(input: { scrollHeight: number; pageHeight: number; manualPageBreakCount?: number }) {
|
||||
const pageHeight = Number.isFinite(input.pageHeight) && input.pageHeight > 0 ? input.pageHeight : defaultPageViewHeight;
|
||||
const scrollHeight = Number.isFinite(input.scrollHeight) && input.scrollHeight > 0 ? input.scrollHeight : 0;
|
||||
const manualPageBreakCount =
|
||||
Number.isFinite(input.manualPageBreakCount) && input.manualPageBreakCount ? Math.max(0, Math.trunc(input.manualPageBreakCount)) : 0;
|
||||
return Math.max(1, Math.ceil(scrollHeight / pageHeight), manualPageBreakCount + 1);
|
||||
}
|
||||
|
||||
export function QWord({ document, zoom = 100, viewMode, onChange, onStatusChange, onViewModeChange }: QWordProps) {
|
||||
@@ -315,7 +324,8 @@ export function QWord({ document, zoom = 100, viewMode, onChange, onStatusChange
|
||||
paperTop: paperRect.top,
|
||||
pageHeight,
|
||||
pageCount,
|
||||
zoom: Number.isFinite(currentZoom) ? currentZoom : 1
|
||||
zoom: Number.isFinite(currentZoom) ? currentZoom : 1,
|
||||
pageGap: defaultPageGap
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,7 +337,8 @@ export function QWord({ document, zoom = 100, viewMode, onChange, onStatusChange
|
||||
}
|
||||
|
||||
const height = pageHeightFromStyles(editor);
|
||||
const count = Math.max(1, Math.ceil(editor.scrollHeight / height));
|
||||
const manualPageBreakCount = editor.querySelectorAll('[data-qoffice-page-break="true"]').length;
|
||||
const count = qwordPageCountFromContent({ scrollHeight: editor.scrollHeight, pageHeight: height, manualPageBreakCount });
|
||||
const currentPage = currentPageFromStage(count, height);
|
||||
setPageView((current) =>
|
||||
current.count === count && current.height === height && current.current === currentPage ? current : { count, current: currentPage, height }
|
||||
|
||||
Reference in New Issue
Block a user