@open-press/cli 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/dist/cli.js +298 -79
- package/package.json +9 -7
- package/template/core/AGENTS.md +0 -130
- package/template/core/CHANGELOG.md +0 -218
- package/template/core/README.md +0 -43
- package/template/core/engine/cli.mjs +0 -96
- package/template/core/engine/commands/_shared.mjs +0 -199
- package/template/core/engine/commands/deploy.mjs +0 -31
- package/template/core/engine/commands/dev.mjs +0 -49
- package/template/core/engine/commands/doctor.mjs +0 -229
- package/template/core/engine/commands/export.mjs +0 -8
- package/template/core/engine/commands/image.mjs +0 -29
- package/template/core/engine/commands/inspect.mjs +0 -35
- package/template/core/engine/commands/pdf.mjs +0 -26
- package/template/core/engine/commands/preview.mjs +0 -26
- package/template/core/engine/commands/render.mjs +0 -17
- package/template/core/engine/commands/replace.mjs +0 -41
- package/template/core/engine/commands/search.mjs +0 -33
- package/template/core/engine/commands/skills-sync.mjs +0 -71
- package/template/core/engine/commands/typecheck.mjs +0 -67
- package/template/core/engine/commands/upgrade.mjs +0 -159
- package/template/core/engine/commands/validate.mjs +0 -17
- package/template/core/engine/document-export.mjs +0 -15
- package/template/core/engine/output/chrome-pdf.d.mts +0 -34
- package/template/core/engine/output/chrome-pdf.mjs +0 -450
- package/template/core/engine/output/deploy-sync.mjs +0 -15
- package/template/core/engine/output/fonts.mjs +0 -62
- package/template/core/engine/output/katex-assets.mjs +0 -45
- package/template/core/engine/output/page-block.mjs +0 -30
- package/template/core/engine/output/pdf-media.mjs +0 -45
- package/template/core/engine/output/public-assets.mjs +0 -19
- package/template/core/engine/output/static-server.mjs +0 -571
- package/template/core/engine/react/caption-numbering.mjs +0 -73
- package/template/core/engine/react/comment-endpoint.d.mts +0 -11
- package/template/core/engine/react/comment-endpoint.mjs +0 -102
- package/template/core/engine/react/comment-marker.mjs +0 -374
- package/template/core/engine/react/document-entry.mjs +0 -331
- package/template/core/engine/react/document-export.mjs +0 -512
- package/template/core/engine/react/http-json.mjs +0 -24
- package/template/core/engine/react/mdx-compile.mjs +0 -629
- package/template/core/engine/react/measurement-css.mjs +0 -157
- package/template/core/engine/react/object-entities.mjs +0 -204
- package/template/core/engine/react/pagination/allocator.mjs +0 -167
- package/template/core/engine/react/pagination/regions.mjs +0 -81
- package/template/core/engine/react/pagination-constants.mjs +0 -3
- package/template/core/engine/react/pagination.mjs +0 -9
- package/template/core/engine/react/pipeline/allocate.mjs +0 -217
- package/template/core/engine/react/pipeline/final-render.mjs +0 -94
- package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -306
- package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
- package/template/core/engine/react/press-tree-inspection.mjs +0 -172
- package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
- package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
- package/template/core/engine/react/section-css.mjs +0 -56
- package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
- package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
- package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
- package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
- package/template/core/engine/react/style-discovery.mjs +0 -160
- package/template/core/engine/runtime/config.d.mts +0 -48
- package/template/core/engine/runtime/config.mjs +0 -172
- package/template/core/engine/runtime/file-utils.mjs +0 -114
- package/template/core/engine/runtime/file-walk.mjs +0 -22
- package/template/core/engine/runtime/inspection.mjs +0 -328
- package/template/core/engine/runtime/issue-report.mjs +0 -44
- package/template/core/engine/runtime/page-geometry.mjs +0 -131
- package/template/core/engine/runtime/path-utils.mjs +0 -20
- package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
- package/template/core/engine/runtime/source-text-tools.mjs +0 -832
- package/template/core/engine/runtime/source-workspace.mjs +0 -168
- package/template/core/engine/runtime/validation.mjs +0 -183
- package/template/core/index.html +0 -13
- package/template/core/openpress.config.mjs +0 -8
- package/template/core/package.json +0 -89
- package/template/core/src/main.tsx +0 -16
- package/template/core/src/openpress/app/OpenPressApp.tsx +0 -296
- package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -102
- package/template/core/src/openpress/app/WorkspaceGalleryPage.tsx +0 -219
- package/template/core/src/openpress/app/index.ts +0 -2
- package/template/core/src/openpress/core/Frame.tsx +0 -91
- package/template/core/src/openpress/core/FrameContext.tsx +0 -26
- package/template/core/src/openpress/core/MdxArea.tsx +0 -34
- package/template/core/src/openpress/core/Press.tsx +0 -55
- package/template/core/src/openpress/core/Workspace.tsx +0 -36
- package/template/core/src/openpress/core/cn.ts +0 -4
- package/template/core/src/openpress/core/index.tsx +0 -47
- package/template/core/src/openpress/core/primitives.tsx +0 -91
- package/template/core/src/openpress/core/types.ts +0 -236
- package/template/core/src/openpress/core/useSource.ts +0 -28
- package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
- package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
- package/template/core/src/openpress/document-model/documentTypes.ts +0 -147
- package/template/core/src/openpress/document-model/index.ts +0 -7
- package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -55
- package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
- package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
- package/template/core/src/openpress/document-model/workspaceManifestModel.ts +0 -57
- package/template/core/src/openpress/manuscript/index.tsx +0 -238
- package/template/core/src/openpress/mdx/index.ts +0 -96
- package/template/core/src/openpress/numbering/index.ts +0 -294
- package/template/core/src/openpress/reader/PageThumbnailsPanel.tsx +0 -168
- package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
- package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
- package/template/core/src/openpress/reader/index.ts +0 -11
- package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
- package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
- package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
- package/template/core/src/openpress/reader/readerScroll.ts +0 -92
- package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
- package/template/core/src/openpress/reader/readerTypes.ts +0 -4
- package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
- package/template/core/src/openpress/reader/usePanelState.ts +0 -56
- package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
- package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
- package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
- package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
- package/template/core/src/openpress/shared/Panel.tsx +0 -77
- package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
- package/template/core/src/openpress/shared/index.ts +0 -4
- package/template/core/src/openpress/shared/numberUtils.ts +0 -3
- package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
- package/template/core/src/openpress/workbench/Workbench.tsx +0 -506
- package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
- package/template/core/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
- package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
- package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
- package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
- package/template/core/src/openpress/workbench/actions/index.ts +0 -6
- package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
- package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
- package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
- package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
- package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
- package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
- package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
- package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
- package/template/core/src/openpress/workbench/document/index.ts +0 -10
- package/template/core/src/openpress/workbench/index.ts +0 -2
- package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
- package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
- package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
- package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
- package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
- package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -254
- package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
- package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
- package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
- package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
- package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -80
- package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
- package/template/core/src/openpress/workbench/panels/index.ts +0 -3
- package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -525
- package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
- package/template/core/src/openpress/workbench/project/index.ts +0 -2
- package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
- package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
- package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
- package/template/core/src/openpress/workbench/shell/index.ts +0 -1
- package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
- package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
- package/template/core/src/styles/openpress/app-shell.css +0 -251
- package/template/core/src/styles/openpress/media-workspace.css +0 -230
- package/template/core/src/styles/openpress/print-route.css +0 -184
- package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
- package/template/core/src/styles/openpress/public-viewer.css +0 -688
- package/template/core/src/styles/openpress/reader-runtime.css +0 -989
- package/template/core/src/styles/openpress/responsive.css +0 -245
- package/template/core/src/styles/openpress/workbench-panels.css +0 -707
- package/template/core/src/styles/openpress/workbench.css +0 -1255
- package/template/core/src/styles/openpress/workspace-gallery.css +0 -300
- package/template/core/src/styles/openpress.css +0 -15
- package/template/core/src/vite-env.d.ts +0 -9
- package/template/core/tsconfig.json +0 -40
- package/template/core/vite.config.ts +0 -584
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState, type CSSProperties } from "react";
|
|
2
|
-
import type { HtmlPageBlock, Theme } from "../document-model";
|
|
3
|
-
import { Panel } from "../shared";
|
|
4
|
-
|
|
5
|
-
// Used by canvas-style Press (slides, social posts) that don't have an
|
|
6
|
-
// MDX-derived TOC. Renders each page as a clickable miniature so the user
|
|
7
|
-
// can navigate without bookmarks. The miniature embeds the same HTML
|
|
8
|
-
// that the main reader renders, scaled to fit the panel width.
|
|
9
|
-
|
|
10
|
-
const FALLBACK_PAGE_WIDTH_PX = 794; // A4 portrait at 96dpi — matches reader default.
|
|
11
|
-
|
|
12
|
-
export function PageThumbnails({
|
|
13
|
-
pages,
|
|
14
|
-
currentPageIndex,
|
|
15
|
-
onSelectPage,
|
|
16
|
-
theme,
|
|
17
|
-
}: {
|
|
18
|
-
pages: HtmlPageBlock[];
|
|
19
|
-
currentPageIndex: number;
|
|
20
|
-
onSelectPage: (pageIndex: number, options?: { behavior?: ScrollBehavior }) => void;
|
|
21
|
-
theme?: Theme;
|
|
22
|
-
}) {
|
|
23
|
-
const pageWidthPx = parsePxLength(theme?.pageWidth) ?? FALLBACK_PAGE_WIDTH_PX;
|
|
24
|
-
const pageHeightPx = parsePxLength(theme?.pageHeight) ?? pageWidthPx;
|
|
25
|
-
// Compute aspect from the parsed dimensions so it always matches the
|
|
26
|
-
// page render. theme.pageAspectRatio may be missing on per-Press
|
|
27
|
-
// documents in multi-Press workspaces, which is why we don't read it
|
|
28
|
-
// here.
|
|
29
|
-
const aspectRatio = `${pageWidthPx} / ${pageHeightPx}`;
|
|
30
|
-
|
|
31
|
-
if (pages.length === 0) {
|
|
32
|
-
return <Panel.Empty className="openpress-asset-empty" role="status">尚無頁面</Panel.Empty>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<ul className="openpress-thumb-list" aria-label="頁面縮圖">
|
|
37
|
-
{pages.map((page, index) => (
|
|
38
|
-
<li key={page.id}>
|
|
39
|
-
<ThumbnailCard
|
|
40
|
-
page={page}
|
|
41
|
-
index={index}
|
|
42
|
-
active={index === currentPageIndex}
|
|
43
|
-
onClick={() => onSelectPage(index, { behavior: "smooth" })}
|
|
44
|
-
pageWidthPx={pageWidthPx}
|
|
45
|
-
pageHeightPx={pageHeightPx}
|
|
46
|
-
aspectRatio={aspectRatio}
|
|
47
|
-
/>
|
|
48
|
-
</li>
|
|
49
|
-
))}
|
|
50
|
-
</ul>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function ThumbnailCard({
|
|
55
|
-
page,
|
|
56
|
-
index,
|
|
57
|
-
active,
|
|
58
|
-
onClick,
|
|
59
|
-
pageWidthPx,
|
|
60
|
-
pageHeightPx,
|
|
61
|
-
aspectRatio,
|
|
62
|
-
}: {
|
|
63
|
-
page: HtmlPageBlock;
|
|
64
|
-
index: number;
|
|
65
|
-
active: boolean;
|
|
66
|
-
onClick: () => void;
|
|
67
|
-
pageWidthPx: number;
|
|
68
|
-
pageHeightPx: number;
|
|
69
|
-
aspectRatio: string;
|
|
70
|
-
}) {
|
|
71
|
-
const surfaceRef = useRef<HTMLDivElement>(null);
|
|
72
|
-
const [scale, setScale] = useState<number | null>(null);
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
const el = surfaceRef.current;
|
|
76
|
-
if (!el) return;
|
|
77
|
-
const update = () => {
|
|
78
|
-
const w = el.clientWidth;
|
|
79
|
-
const h = el.clientHeight;
|
|
80
|
-
if (w > 0 && h > 0) setScale(Math.min(w / pageWidthPx, h / pageHeightPx));
|
|
81
|
-
};
|
|
82
|
-
update();
|
|
83
|
-
if (typeof ResizeObserver === "undefined") return;
|
|
84
|
-
const ro = new ResizeObserver(update);
|
|
85
|
-
ro.observe(el);
|
|
86
|
-
return () => ro.disconnect();
|
|
87
|
-
}, [pageWidthPx, pageHeightPx]);
|
|
88
|
-
|
|
89
|
-
const className = `openpress-thumb-card${active ? " is-active" : ""}`;
|
|
90
|
-
// Wrap the page HTML using the same class structure as the main
|
|
91
|
-
// reader (`.openpress-html-page > .openpress-html-page__html`) so
|
|
92
|
-
// section-scoped CSS that targets those classes still applies in
|
|
93
|
-
// the miniature.
|
|
94
|
-
const pageClass = page.className
|
|
95
|
-
? `openpress-html-page ${page.className}`
|
|
96
|
-
: "openpress-html-page";
|
|
97
|
-
const scaledWidth = scale ? pageWidthPx * scale : 0;
|
|
98
|
-
const scaledHeight = scale ? pageHeightPx * scale : 0;
|
|
99
|
-
const frameStyle: CSSProperties = {
|
|
100
|
-
width: `${scaledWidth}px`,
|
|
101
|
-
height: `${scaledHeight}px`,
|
|
102
|
-
position: "relative",
|
|
103
|
-
visibility: scale ? "visible" : "hidden",
|
|
104
|
-
};
|
|
105
|
-
const pageStyle: CSSProperties = {
|
|
106
|
-
"--openpress-page-width": `${pageWidthPx}px`,
|
|
107
|
-
"--openpress-page-height": `${pageHeightPx}px`,
|
|
108
|
-
width: `${pageWidthPx}px`,
|
|
109
|
-
height: `${pageHeightPx}px`,
|
|
110
|
-
transform: scale ? `scale(${scale})` : undefined,
|
|
111
|
-
transformOrigin: "top left",
|
|
112
|
-
position: "absolute",
|
|
113
|
-
top: 0,
|
|
114
|
-
left: 0,
|
|
115
|
-
} as CSSProperties;
|
|
116
|
-
const pageTitle = page.title || `Page ${index + 1}`;
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<div
|
|
120
|
-
role="button"
|
|
121
|
-
tabIndex={0}
|
|
122
|
-
className={className}
|
|
123
|
-
data-openpress-thumb-index={index}
|
|
124
|
-
aria-label={`前往第 ${index + 1} 頁:${pageTitle}`}
|
|
125
|
-
aria-current={active ? "page" : undefined}
|
|
126
|
-
onClick={onClick}
|
|
127
|
-
onKeyDown={(event) => {
|
|
128
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
129
|
-
event.preventDefault();
|
|
130
|
-
onClick();
|
|
131
|
-
}
|
|
132
|
-
}}
|
|
133
|
-
>
|
|
134
|
-
<div className="openpress-thumb-card__surface" ref={surfaceRef} style={{ aspectRatio }}>
|
|
135
|
-
<div className="openpress-thumb-card__frame" style={frameStyle}>
|
|
136
|
-
<div className={pageClass} style={pageStyle} data-openpress-thumb-page="true">
|
|
137
|
-
<div
|
|
138
|
-
className="openpress-html-page__html"
|
|
139
|
-
// Page HTML comes from the trusted build pipeline (same source
|
|
140
|
-
// as the main reader).
|
|
141
|
-
dangerouslySetInnerHTML={{ __html: page.html }}
|
|
142
|
-
/>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
<div className="openpress-thumb-card__meta">
|
|
147
|
-
<span className="openpress-thumb-card__index">{String(index + 1).padStart(2, "0")}</span>
|
|
148
|
-
<span className="openpress-thumb-card__title">{pageTitle}</span>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function parsePxLength(value: string | undefined): number | null {
|
|
155
|
-
if (!value) return null;
|
|
156
|
-
const match = value.trim().match(/^([\d.]+)\s*(px|mm|cm|in)$/i);
|
|
157
|
-
if (!match) return null;
|
|
158
|
-
const n = Number(match[1]);
|
|
159
|
-
if (!Number.isFinite(n) || n <= 0) return null;
|
|
160
|
-
const unit = match[2].toLowerCase();
|
|
161
|
-
switch (unit) {
|
|
162
|
-
case "px": return n;
|
|
163
|
-
case "mm": return n * (96 / 25.4);
|
|
164
|
-
case "cm": return n * (96 / 2.54);
|
|
165
|
-
case "in": return n * 96;
|
|
166
|
-
default: return null;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useMemo,
|
|
3
|
-
useRef,
|
|
4
|
-
type CSSProperties,
|
|
5
|
-
type MouseEvent as ReactMouseEvent,
|
|
6
|
-
type RefCallback,
|
|
7
|
-
type RefObject,
|
|
8
|
-
} from "react";
|
|
9
|
-
import { BookOpen, ExternalLink, X } from "lucide-react";
|
|
10
|
-
import {
|
|
11
|
-
collectBookmarkIndex,
|
|
12
|
-
createAnchorPageMap,
|
|
13
|
-
createPageObjectEntityId,
|
|
14
|
-
getProjectIdentity,
|
|
15
|
-
resolveAnchorPageIndex,
|
|
16
|
-
type DeploymentInfo,
|
|
17
|
-
type HtmlPageBlock,
|
|
18
|
-
type ReaderDocument,
|
|
19
|
-
} from "../document-model";
|
|
20
|
-
import type { InspectorState } from "../workbench/inspector";
|
|
21
|
-
import { useReaderRuntime } from "./useReaderRuntime";
|
|
22
|
-
import { Bookmarks, CurrentPagePanel } from "./ReaderNavigationPanel";
|
|
23
|
-
import type { DisplayPage } from "./readerTypes";
|
|
24
|
-
import { usePageViewportScale } from "./usePageViewportScale";
|
|
25
|
-
import type { PageLayoutMode } from "./pageViewportScaleModel";
|
|
26
|
-
|
|
27
|
-
export const PUBLIC_DRAWER_BREAKPOINT = 1185;
|
|
28
|
-
export type ViewMode = "paged";
|
|
29
|
-
export type PageInspector = Pick<InspectorState, "enabled" | "handleClick">;
|
|
30
|
-
|
|
31
|
-
export function PublicViewer({
|
|
32
|
-
document,
|
|
33
|
-
pages,
|
|
34
|
-
style,
|
|
35
|
-
deploymentInfo = { online: false },
|
|
36
|
-
}: {
|
|
37
|
-
document: ReaderDocument;
|
|
38
|
-
pages: Array<HtmlPageBlock>;
|
|
39
|
-
style: CSSProperties;
|
|
40
|
-
deploymentInfo?: DeploymentInfo;
|
|
41
|
-
}) {
|
|
42
|
-
const sourceContainerRef = useRef<HTMLDivElement | null>(null);
|
|
43
|
-
const displayPages = pages;
|
|
44
|
-
const viewModeState = useViewMode();
|
|
45
|
-
const { viewMode } = viewModeState;
|
|
46
|
-
const bookmarks = collectBookmarkIndex(displayPages);
|
|
47
|
-
const anchorPageMap = useMemo(() => createAnchorPageMap(displayPages), [displayPages]);
|
|
48
|
-
const reader = useReaderRuntime({
|
|
49
|
-
pageCount: displayPages.length,
|
|
50
|
-
rightPanelBreakpoint: PUBLIC_DRAWER_BREAKPOINT,
|
|
51
|
-
});
|
|
52
|
-
usePageViewportScale({
|
|
53
|
-
stageRef: reader.stageRef,
|
|
54
|
-
pageContainerRef: sourceContainerRef,
|
|
55
|
-
pageCount: displayPages.length,
|
|
56
|
-
layoutMode: "single",
|
|
57
|
-
});
|
|
58
|
-
const currentPage = displayPages[reader.currentPageIndex];
|
|
59
|
-
const staticPdfHref = deploymentInfo.pdf;
|
|
60
|
-
const projectIdentity = getProjectIdentity(document.meta);
|
|
61
|
-
|
|
62
|
-
const drawerOpen = reader.rightPanelOpen;
|
|
63
|
-
|
|
64
|
-
const selectPublicPage = (pageIndex: number, options?: { behavior?: ScrollBehavior }) => {
|
|
65
|
-
reader.setPage(pageIndex, options);
|
|
66
|
-
if (window.innerWidth < PUBLIC_DRAWER_BREAKPOINT && drawerOpen) reader.toggleRightPanel();
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const selectPublicAnchor = (anchorId: string, pageIndex?: number) => {
|
|
70
|
-
const targetPageIndex = resolveAnchorPageIndex(anchorPageMap, displayPages.length, anchorId, pageIndex);
|
|
71
|
-
if (targetPageIndex === null) return false;
|
|
72
|
-
selectPublicPage(targetPageIndex, { behavior: "smooth" });
|
|
73
|
-
return true;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const appClassName = [
|
|
77
|
-
"reader-app openpress-reader-app openpress-public-viewer is-ready",
|
|
78
|
-
drawerOpen ? "" : "is-closed-right",
|
|
79
|
-
].filter(Boolean).join(" ");
|
|
80
|
-
|
|
81
|
-
const handleOpenStaticPdf = () => {
|
|
82
|
-
if (!staticPdfHref) return;
|
|
83
|
-
window.open(staticPdfHref, "_blank", "noopener,noreferrer");
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<main className="openpress-workbench openpress-public-shell" style={style} data-openpress-public-viewer="true" aria-label={`${document.meta.title} 公開頁`}>
|
|
88
|
-
<div
|
|
89
|
-
className={appClassName}
|
|
90
|
-
data-openpress-react-runtime="true"
|
|
91
|
-
data-openpress-view-mode={viewMode}
|
|
92
|
-
>
|
|
93
|
-
{drawerOpen && (
|
|
94
|
-
<div className="openpress-public-scrim" aria-hidden="true" onClick={reader.toggleRightPanel} />
|
|
95
|
-
)}
|
|
96
|
-
<button type="button" className="openpress-public-fab" aria-label="開啟目錄" onClick={reader.toggleRightPanel}>
|
|
97
|
-
<BookOpen size={20} aria-hidden="true" />
|
|
98
|
-
</button>
|
|
99
|
-
|
|
100
|
-
<section className="openpress-workbench__stage openpress-public-viewer__stage" aria-label="公開文件頁面">
|
|
101
|
-
<main className="reader-stage" tabIndex={-1} ref={reader.stageRef}>
|
|
102
|
-
<PublicPage
|
|
103
|
-
pages={displayPages}
|
|
104
|
-
currentPageIndex={reader.currentPageIndex}
|
|
105
|
-
devMode={false}
|
|
106
|
-
sourceContainerRef={sourceContainerRef}
|
|
107
|
-
registerPage={reader.registerPage}
|
|
108
|
-
onInternalAnchorNavigate={selectPublicAnchor}
|
|
109
|
-
/>
|
|
110
|
-
</main>
|
|
111
|
-
</section>
|
|
112
|
-
|
|
113
|
-
<aside className="reader-side-nav openpress-workspace-panel openpress-public-navigation" aria-label="文件導覽">
|
|
114
|
-
<button type="button" className="openpress-public-drawer-close" aria-label="關閉目錄" onClick={reader.toggleRightPanel}>
|
|
115
|
-
<X size={16} aria-hidden="true" />
|
|
116
|
-
</button>
|
|
117
|
-
<section className="openpress-public-identity" aria-label="文件資訊">
|
|
118
|
-
<strong>
|
|
119
|
-
<span className="openpress-public-title-main">{projectIdentity.name}</span>
|
|
120
|
-
{projectIdentity.subtitle ? <span className="openpress-public-title-sub">{projectIdentity.subtitle}</span> : null}
|
|
121
|
-
</strong>
|
|
122
|
-
{projectIdentity.label ? <span>{projectIdentity.label}</span> : null}
|
|
123
|
-
</section>
|
|
124
|
-
<div className="openpress-public-actions" aria-label="文件操作">
|
|
125
|
-
<button
|
|
126
|
-
type="button"
|
|
127
|
-
className="openpress-public-export-button"
|
|
128
|
-
data-openpress-public-export
|
|
129
|
-
disabled={!staticPdfHref}
|
|
130
|
-
onClick={handleOpenStaticPdf}
|
|
131
|
-
>
|
|
132
|
-
<ExternalLink aria-hidden="true" />
|
|
133
|
-
{!staticPdfHref ? "PDF 未部署" : "開啟 PDF"}
|
|
134
|
-
</button>
|
|
135
|
-
</div>
|
|
136
|
-
<section id="openpress-bookmarks" className="openpress-panel-section openpress-panel-section--bookmarks" aria-label="章節書籤">
|
|
137
|
-
<nav className="reader-bookmarks" aria-label="章節導覽" data-openpress-react-bookmarks="true">
|
|
138
|
-
<div className="reader-bookmarks-rail" aria-hidden="true" />
|
|
139
|
-
<Bookmarks items={bookmarks} currentPageIndex={reader.currentPageIndex} onSelectPage={selectPublicPage} />
|
|
140
|
-
</nav>
|
|
141
|
-
</section>
|
|
142
|
-
<CurrentPagePanel
|
|
143
|
-
currentPageLabel={reader.currentPageLabel}
|
|
144
|
-
totalPageLabel={reader.totalPageLabel}
|
|
145
|
-
progressPercent={reader.progressPercent}
|
|
146
|
-
title={currentPage?.title || document.meta.title}
|
|
147
|
-
pageLabelPrefix="頁"
|
|
148
|
-
showHeading={false}
|
|
149
|
-
showTitle={false}
|
|
150
|
-
/>
|
|
151
|
-
</aside>
|
|
152
|
-
</div>
|
|
153
|
-
</main>
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function useViewMode(): { viewMode: ViewMode } {
|
|
158
|
-
return { viewMode: "paged" };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function PrintDocument({
|
|
162
|
-
document,
|
|
163
|
-
pages,
|
|
164
|
-
style,
|
|
165
|
-
}: {
|
|
166
|
-
document: ReaderDocument;
|
|
167
|
-
pages: Array<HtmlPageBlock>;
|
|
168
|
-
style: CSSProperties;
|
|
169
|
-
}) {
|
|
170
|
-
const sourceContainerRef = useRef<HTMLDivElement | null>(null);
|
|
171
|
-
const displayPages = pages;
|
|
172
|
-
const registerPage = () => () => undefined;
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<main
|
|
176
|
-
className="openpress-print-document"
|
|
177
|
-
style={style}
|
|
178
|
-
data-openpress-print-document="true"
|
|
179
|
-
aria-label={`${document.meta.title} PDF 輸出`}
|
|
180
|
-
>
|
|
181
|
-
<PublicPage
|
|
182
|
-
pages={displayPages}
|
|
183
|
-
currentPageIndex={0}
|
|
184
|
-
devMode={false}
|
|
185
|
-
sourceContainerRef={sourceContainerRef}
|
|
186
|
-
registerPage={registerPage}
|
|
187
|
-
exposeSourceData
|
|
188
|
-
/>
|
|
189
|
-
</main>
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export function PublicPage({
|
|
194
|
-
pages,
|
|
195
|
-
currentPageIndex,
|
|
196
|
-
devMode,
|
|
197
|
-
sourceContainerRef,
|
|
198
|
-
registerPage,
|
|
199
|
-
exposeSourceData = false,
|
|
200
|
-
inspector,
|
|
201
|
-
onInternalAnchorNavigate,
|
|
202
|
-
pageLayoutMode = "single",
|
|
203
|
-
}: {
|
|
204
|
-
pages: DisplayPage[];
|
|
205
|
-
currentPageIndex: number;
|
|
206
|
-
devMode: boolean;
|
|
207
|
-
sourceContainerRef: RefObject<HTMLDivElement | null>;
|
|
208
|
-
registerPage: (pageIndex: number) => RefCallback<HTMLElement>;
|
|
209
|
-
exposeSourceData?: boolean;
|
|
210
|
-
inspector?: PageInspector;
|
|
211
|
-
onInternalAnchorNavigate?: (anchorId: string, pageIndex?: number) => boolean;
|
|
212
|
-
pageLayoutMode?: PageLayoutMode;
|
|
213
|
-
}) {
|
|
214
|
-
const handlePageClick = (event: ReactMouseEvent<HTMLDivElement>) => {
|
|
215
|
-
if (inspector?.enabled && inspector.handleClick(event)) return;
|
|
216
|
-
if (!onInternalAnchorNavigate || event.defaultPrevented || event.button !== 0) return;
|
|
217
|
-
if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;
|
|
218
|
-
if (!(event.target instanceof Element)) return;
|
|
219
|
-
|
|
220
|
-
const link = event.target.closest<HTMLAnchorElement>('a[href^="#"]');
|
|
221
|
-
if (!link) return;
|
|
222
|
-
|
|
223
|
-
const href = link.getAttribute("href") ?? "";
|
|
224
|
-
const anchorId = link.dataset.openpressAnchor || safeDecodeAnchor(href.slice(1));
|
|
225
|
-
if (!anchorId) return;
|
|
226
|
-
|
|
227
|
-
const pageIndex = Number.parseInt(link.dataset.openpressTargetPageIndex ?? "", 10);
|
|
228
|
-
const handled = onInternalAnchorNavigate(anchorId, Number.isFinite(pageIndex) ? pageIndex : undefined);
|
|
229
|
-
if (handled) event.preventDefault();
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
return (
|
|
233
|
-
<div
|
|
234
|
-
className="reader-pages openpress-public-page"
|
|
235
|
-
ref={sourceContainerRef}
|
|
236
|
-
data-openpress-public-page="true"
|
|
237
|
-
data-openpress-page-layout={pageLayoutMode}
|
|
238
|
-
onClick={handlePageClick}
|
|
239
|
-
>
|
|
240
|
-
{pages.map((page) => (
|
|
241
|
-
<div
|
|
242
|
-
key={page.id}
|
|
243
|
-
ref={registerPage(page.pageNumber - 1)}
|
|
244
|
-
id={`page-${String(page.pageNumber).padStart(2, "0")}`}
|
|
245
|
-
className="openpress-html-page"
|
|
246
|
-
data-openpress-object-id={page.frameKey ? createPageObjectEntityId(page.frameKey) : undefined}
|
|
247
|
-
data-openpress-page-index={page.pageNumber - 1}
|
|
248
|
-
data-openpress-page-spread-side={pageLayoutMode === "spread" ? ((page.pageNumber - 1) % 2 === 0 ? "left" : "right") : undefined}
|
|
249
|
-
data-openpress-active={currentPageIndex === page.pageNumber - 1 ? "true" : "false"}
|
|
250
|
-
data-source-path={exposeSourceData ? page.source?.path : undefined}
|
|
251
|
-
data-source-file={exposeSourceData ? page.source?.file : undefined}
|
|
252
|
-
>
|
|
253
|
-
<div className="openpress-html-page__html" dangerouslySetInnerHTML={{ __html: page.html }} />
|
|
254
|
-
</div>
|
|
255
|
-
))}
|
|
256
|
-
</div>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function safeDecodeAnchor(value: string) {
|
|
261
|
-
if (!value) return "";
|
|
262
|
-
try {
|
|
263
|
-
return decodeURIComponent(value);
|
|
264
|
-
} catch {
|
|
265
|
-
return value;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { type CSSProperties, type MouseEvent as ReactMouseEvent } from "react";
|
|
2
|
-
import type { BookmarkItem } from "../document-model";
|
|
3
|
-
import { Panel } from "../shared";
|
|
4
|
-
|
|
5
|
-
type BookmarkSelectOptions = {
|
|
6
|
-
behavior?: ScrollBehavior;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export function Bookmarks({
|
|
10
|
-
items,
|
|
11
|
-
currentPageIndex,
|
|
12
|
-
onSelectPage,
|
|
13
|
-
}: {
|
|
14
|
-
items: BookmarkItem[];
|
|
15
|
-
currentPageIndex: number;
|
|
16
|
-
onSelectPage: (pageIndex: number, options?: BookmarkSelectOptions) => void;
|
|
17
|
-
}) {
|
|
18
|
-
const goToPage = (event: ReactMouseEvent<HTMLButtonElement>, pageIndex: number) => {
|
|
19
|
-
event.preventDefault();
|
|
20
|
-
onSelectPage(pageIndex, { behavior: "smooth" });
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
if (items.length === 0) {
|
|
24
|
-
return <Panel.Empty className="openpress-asset-empty" role="status">尚無書籤</Panel.Empty>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<>
|
|
29
|
-
{items.map((item, index) => {
|
|
30
|
-
const groupActive = currentPageIndex >= item.pageIndex && currentPageIndex <= item.endPageIndex;
|
|
31
|
-
const activeSub = item.subs.find((sub) => currentPageIndex >= sub.pageIndex && currentPageIndex <= sub.endPageIndex);
|
|
32
|
-
const h2SelfActive = groupActive && !activeSub;
|
|
33
|
-
const itemLabel = item.label ?? String(index + 1).padStart(2, "0");
|
|
34
|
-
return (
|
|
35
|
-
<div className={`bookmark-group${groupActive ? " is-open" : ""}`} key={item.id}>
|
|
36
|
-
<button
|
|
37
|
-
type="button"
|
|
38
|
-
className={`bookmark-item bookmark-h2${h2SelfActive ? " is-active" : ""}`}
|
|
39
|
-
data-openpress-page-index={item.pageIndex}
|
|
40
|
-
onClick={(event) => goToPage(event, item.pageIndex)}
|
|
41
|
-
>
|
|
42
|
-
<span className="bookmark-index">{itemLabel}</span>
|
|
43
|
-
<span className="bookmark-title">{item.title}</span>
|
|
44
|
-
</button>
|
|
45
|
-
<div className="bookmark-subs">
|
|
46
|
-
{item.subs.map((sub, subIndex) => {
|
|
47
|
-
const subActive = currentPageIndex >= sub.pageIndex && currentPageIndex <= sub.endPageIndex;
|
|
48
|
-
const activeTopic = sub.subs.find((topic) => currentPageIndex >= topic.pageIndex && currentPageIndex <= topic.endPageIndex);
|
|
49
|
-
const subSelfActive = subActive && !activeTopic;
|
|
50
|
-
const subLabel = sub.label ?? `${itemLabel}.${subIndex + 1}`;
|
|
51
|
-
return (
|
|
52
|
-
<div className="bookmark-subgroup" key={sub.id}>
|
|
53
|
-
<button
|
|
54
|
-
type="button"
|
|
55
|
-
className={`bookmark-item bookmark-h3${subSelfActive ? " is-active" : ""}`}
|
|
56
|
-
data-openpress-page-index={sub.pageIndex}
|
|
57
|
-
onClick={(event) => goToPage(event, sub.pageIndex)}
|
|
58
|
-
>
|
|
59
|
-
<span className="bookmark-index">{subLabel}</span>
|
|
60
|
-
<span className="bookmark-title">{sub.title}</span>
|
|
61
|
-
</button>
|
|
62
|
-
{sub.subs.map((topic, topicIndex) => {
|
|
63
|
-
const topicActive = currentPageIndex >= topic.pageIndex && currentPageIndex <= topic.endPageIndex;
|
|
64
|
-
const topicLabel = topic.label ?? `${subLabel}.${topicIndex + 1}`;
|
|
65
|
-
return (
|
|
66
|
-
<button
|
|
67
|
-
type="button"
|
|
68
|
-
className={`bookmark-item bookmark-h4${topicActive ? " is-active" : ""}`}
|
|
69
|
-
data-openpress-page-index={topic.pageIndex}
|
|
70
|
-
onClick={(event) => goToPage(event, topic.pageIndex)}
|
|
71
|
-
key={topic.id}
|
|
72
|
-
>
|
|
73
|
-
<span className="bookmark-index">{topicLabel}</span>
|
|
74
|
-
<span className="bookmark-title">{topic.title}</span>
|
|
75
|
-
</button>
|
|
76
|
-
);
|
|
77
|
-
})}
|
|
78
|
-
</div>
|
|
79
|
-
);
|
|
80
|
-
})}
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
);
|
|
84
|
-
})}
|
|
85
|
-
</>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function CurrentPagePanel({
|
|
90
|
-
currentPageLabel,
|
|
91
|
-
totalPageLabel,
|
|
92
|
-
progressPercent,
|
|
93
|
-
title,
|
|
94
|
-
pageLabelPrefix,
|
|
95
|
-
showHeading = true,
|
|
96
|
-
showTitle = true,
|
|
97
|
-
}: {
|
|
98
|
-
currentPageLabel: string;
|
|
99
|
-
totalPageLabel: string;
|
|
100
|
-
progressPercent: number;
|
|
101
|
-
title: string;
|
|
102
|
-
pageLabelPrefix?: string;
|
|
103
|
-
showHeading?: boolean;
|
|
104
|
-
showTitle?: boolean;
|
|
105
|
-
}) {
|
|
106
|
-
return (
|
|
107
|
-
<Panel.Section className="openpress-panel-section--current" aria-label="目前頁面">
|
|
108
|
-
{showHeading ? <Panel.SectionTitle className="openpress-panel-heading">目前頁面</Panel.SectionTitle> : null}
|
|
109
|
-
<div className="openpress-current-page-card">
|
|
110
|
-
<div className="openpress-current-page-card__number" aria-label="目前頁數">
|
|
111
|
-
{pageLabelPrefix ? <span className="openpress-current-page-card__prefix">{pageLabelPrefix}</span> : null}
|
|
112
|
-
<span data-openpress-current-page>{currentPageLabel}</span>
|
|
113
|
-
<span className="sep">/</span>
|
|
114
|
-
<span data-openpress-total-pages>{totalPageLabel}</span>
|
|
115
|
-
</div>
|
|
116
|
-
{showTitle ? <div className="openpress-current-page-card__title">{title}</div> : null}
|
|
117
|
-
<div className="openpress-current-page-card__progress" aria-hidden="true">
|
|
118
|
-
<span style={{ "--progress": `${progressPercent}%` } as CSSProperties} />
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
</Panel.Section>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export * from "./PageThumbnailsPanel";
|
|
2
|
-
export * from "./PublicReaderPage";
|
|
3
|
-
export * from "./ReaderNavigationPanel";
|
|
4
|
-
export * from "./pageViewportScaleModel";
|
|
5
|
-
export * from "./readerPageRegistry";
|
|
6
|
-
export * from "./readerPageRoute";
|
|
7
|
-
export * from "./readerScroll";
|
|
8
|
-
export * from "./readerStateModel";
|
|
9
|
-
export * from "./readerTypes";
|
|
10
|
-
export * from "./usePageViewportScale";
|
|
11
|
-
export * from "./useReaderRuntime";
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
export type PageLayoutMode = "single" | "spread";
|
|
2
|
-
|
|
3
|
-
export type PageViewportScaleMode =
|
|
4
|
-
| "fit-width"
|
|
5
|
-
| "fit-page"
|
|
6
|
-
| "scale-25"
|
|
7
|
-
| "scale-50"
|
|
8
|
-
| "scale-75"
|
|
9
|
-
| "scale-100"
|
|
10
|
-
| "scale-125"
|
|
11
|
-
| "scale-150"
|
|
12
|
-
| "scale-200";
|
|
13
|
-
|
|
14
|
-
export const PAGE_VIEWPORT_SCALE_OPTIONS: Array<{
|
|
15
|
-
value: PageViewportScaleMode;
|
|
16
|
-
label: string;
|
|
17
|
-
}> = [
|
|
18
|
-
{ value: "scale-25", label: "25%" },
|
|
19
|
-
{ value: "scale-50", label: "50%" },
|
|
20
|
-
{ value: "scale-75", label: "75%" },
|
|
21
|
-
{ value: "scale-100", label: "100%" },
|
|
22
|
-
{ value: "scale-125", label: "125%" },
|
|
23
|
-
{ value: "scale-150", label: "150%" },
|
|
24
|
-
{ value: "scale-200", label: "200%" },
|
|
25
|
-
{ value: "fit-width", label: "符合頁面寬度" },
|
|
26
|
-
{ value: "fit-page", label: "符合全開頁面" },
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const MIN_PAGE_VIEWPORT_SCALE = 0.12;
|
|
30
|
-
const MAX_FIT_PAGE_VIEWPORT_SCALE = 1;
|
|
31
|
-
const MAX_FIXED_PAGE_VIEWPORT_SCALE = 2;
|
|
32
|
-
|
|
33
|
-
export function resolvePageViewportScale({
|
|
34
|
-
mode,
|
|
35
|
-
fitWidthScale,
|
|
36
|
-
fitPageScale,
|
|
37
|
-
}: {
|
|
38
|
-
mode: PageViewportScaleMode;
|
|
39
|
-
fitWidthScale: number;
|
|
40
|
-
fitPageScale: number;
|
|
41
|
-
}) {
|
|
42
|
-
if (mode === "fit-width") return clampPageViewportScale(fitWidthScale, MAX_FIT_PAGE_VIEWPORT_SCALE);
|
|
43
|
-
if (mode === "fit-page") return clampPageViewportScale(fitPageScale, MAX_FIT_PAGE_VIEWPORT_SCALE);
|
|
44
|
-
return scaleModeToFixedValue(mode);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function formatPageViewportScaleLabel(mode: PageViewportScaleMode, scale: number) {
|
|
48
|
-
void mode;
|
|
49
|
-
return formatPageViewportScalePercent(scale);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function formatPageViewportScalePercent(scale: number) {
|
|
53
|
-
return `${Math.round(clampPageViewportScale(scale, MAX_FIXED_PAGE_VIEWPORT_SCALE) * 100)}%`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function formatPageViewportScaleValue(scale: number) {
|
|
57
|
-
return clampPageViewportScale(scale, MAX_FIXED_PAGE_VIEWPORT_SCALE)
|
|
58
|
-
.toFixed(4)
|
|
59
|
-
.replace(/0+$/, "")
|
|
60
|
-
.replace(/\.$/, "");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function scaleModeToFixedValue(mode: PageViewportScaleMode) {
|
|
64
|
-
const match = /^scale-(\d+)$/.exec(mode);
|
|
65
|
-
if (!match) return 1;
|
|
66
|
-
return clampPageViewportScale(Number.parseInt(match[1] ?? "100", 10) / 100, MAX_FIXED_PAGE_VIEWPORT_SCALE);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function clampPageViewportScale(value: number, maxScale: number) {
|
|
70
|
-
if (!Number.isFinite(value)) return 1;
|
|
71
|
-
const safeMaxScale = Number.isFinite(maxScale) && maxScale > 0 ? maxScale : MAX_FIXED_PAGE_VIEWPORT_SCALE;
|
|
72
|
-
return Math.min(Math.max(value, MIN_PAGE_VIEWPORT_SCALE), safeMaxScale);
|
|
73
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export interface ReaderPageRegistry<TNode> {
|
|
2
|
-
refs: Array<TNode | null>;
|
|
3
|
-
registerPage: (pageIndex: number) => (node: TNode | null) => void;
|
|
4
|
-
trim: (pageCount: number) => void;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function createReaderPageRegistry<TNode = HTMLElement>(
|
|
8
|
-
onChange: (version: number) => void,
|
|
9
|
-
): ReaderPageRegistry<TNode> {
|
|
10
|
-
const refs: Array<TNode | null> = [];
|
|
11
|
-
const callbacks = new Map<number, (node: TNode | null) => void>();
|
|
12
|
-
let version = 0;
|
|
13
|
-
|
|
14
|
-
const bump = () => {
|
|
15
|
-
version += 1;
|
|
16
|
-
onChange(version);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
refs,
|
|
21
|
-
registerPage(pageIndex: number) {
|
|
22
|
-
const existing = callbacks.get(pageIndex);
|
|
23
|
-
if (existing) return existing;
|
|
24
|
-
|
|
25
|
-
const callback = (node: TNode | null) => {
|
|
26
|
-
if (refs[pageIndex] === node) return;
|
|
27
|
-
refs[pageIndex] = node;
|
|
28
|
-
bump();
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
callbacks.set(pageIndex, callback);
|
|
32
|
-
return callback;
|
|
33
|
-
},
|
|
34
|
-
trim(pageCount: number) {
|
|
35
|
-
refs.length = Math.max(pageCount, 0);
|
|
36
|
-
for (const pageIndex of callbacks.keys()) {
|
|
37
|
-
if (pageIndex >= pageCount) callbacks.delete(pageIndex);
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
}
|