@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.
Files changed (175) hide show
  1. package/README.md +11 -12
  2. package/dist/cli.js +298 -79
  3. package/package.json +9 -7
  4. package/template/core/AGENTS.md +0 -130
  5. package/template/core/CHANGELOG.md +0 -218
  6. package/template/core/README.md +0 -43
  7. package/template/core/engine/cli.mjs +0 -96
  8. package/template/core/engine/commands/_shared.mjs +0 -199
  9. package/template/core/engine/commands/deploy.mjs +0 -31
  10. package/template/core/engine/commands/dev.mjs +0 -49
  11. package/template/core/engine/commands/doctor.mjs +0 -229
  12. package/template/core/engine/commands/export.mjs +0 -8
  13. package/template/core/engine/commands/image.mjs +0 -29
  14. package/template/core/engine/commands/inspect.mjs +0 -35
  15. package/template/core/engine/commands/pdf.mjs +0 -26
  16. package/template/core/engine/commands/preview.mjs +0 -26
  17. package/template/core/engine/commands/render.mjs +0 -17
  18. package/template/core/engine/commands/replace.mjs +0 -41
  19. package/template/core/engine/commands/search.mjs +0 -33
  20. package/template/core/engine/commands/skills-sync.mjs +0 -71
  21. package/template/core/engine/commands/typecheck.mjs +0 -67
  22. package/template/core/engine/commands/upgrade.mjs +0 -159
  23. package/template/core/engine/commands/validate.mjs +0 -17
  24. package/template/core/engine/document-export.mjs +0 -15
  25. package/template/core/engine/output/chrome-pdf.d.mts +0 -34
  26. package/template/core/engine/output/chrome-pdf.mjs +0 -450
  27. package/template/core/engine/output/deploy-sync.mjs +0 -15
  28. package/template/core/engine/output/fonts.mjs +0 -62
  29. package/template/core/engine/output/katex-assets.mjs +0 -45
  30. package/template/core/engine/output/page-block.mjs +0 -30
  31. package/template/core/engine/output/pdf-media.mjs +0 -45
  32. package/template/core/engine/output/public-assets.mjs +0 -19
  33. package/template/core/engine/output/static-server.mjs +0 -571
  34. package/template/core/engine/react/caption-numbering.mjs +0 -73
  35. package/template/core/engine/react/comment-endpoint.d.mts +0 -11
  36. package/template/core/engine/react/comment-endpoint.mjs +0 -102
  37. package/template/core/engine/react/comment-marker.mjs +0 -374
  38. package/template/core/engine/react/document-entry.mjs +0 -331
  39. package/template/core/engine/react/document-export.mjs +0 -512
  40. package/template/core/engine/react/http-json.mjs +0 -24
  41. package/template/core/engine/react/mdx-compile.mjs +0 -629
  42. package/template/core/engine/react/measurement-css.mjs +0 -157
  43. package/template/core/engine/react/object-entities.mjs +0 -204
  44. package/template/core/engine/react/pagination/allocator.mjs +0 -167
  45. package/template/core/engine/react/pagination/regions.mjs +0 -81
  46. package/template/core/engine/react/pagination-constants.mjs +0 -3
  47. package/template/core/engine/react/pagination.mjs +0 -9
  48. package/template/core/engine/react/pipeline/allocate.mjs +0 -217
  49. package/template/core/engine/react/pipeline/final-render.mjs +0 -94
  50. package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -306
  51. package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
  52. package/template/core/engine/react/press-tree-inspection.mjs +0 -172
  53. package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
  54. package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
  55. package/template/core/engine/react/section-css.mjs +0 -56
  56. package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
  57. package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
  58. package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
  59. package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
  60. package/template/core/engine/react/style-discovery.mjs +0 -160
  61. package/template/core/engine/runtime/config.d.mts +0 -48
  62. package/template/core/engine/runtime/config.mjs +0 -172
  63. package/template/core/engine/runtime/file-utils.mjs +0 -114
  64. package/template/core/engine/runtime/file-walk.mjs +0 -22
  65. package/template/core/engine/runtime/inspection.mjs +0 -328
  66. package/template/core/engine/runtime/issue-report.mjs +0 -44
  67. package/template/core/engine/runtime/page-geometry.mjs +0 -131
  68. package/template/core/engine/runtime/path-utils.mjs +0 -20
  69. package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
  70. package/template/core/engine/runtime/source-text-tools.mjs +0 -832
  71. package/template/core/engine/runtime/source-workspace.mjs +0 -168
  72. package/template/core/engine/runtime/validation.mjs +0 -183
  73. package/template/core/index.html +0 -13
  74. package/template/core/openpress.config.mjs +0 -8
  75. package/template/core/package.json +0 -89
  76. package/template/core/src/main.tsx +0 -16
  77. package/template/core/src/openpress/app/OpenPressApp.tsx +0 -296
  78. package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -102
  79. package/template/core/src/openpress/app/WorkspaceGalleryPage.tsx +0 -219
  80. package/template/core/src/openpress/app/index.ts +0 -2
  81. package/template/core/src/openpress/core/Frame.tsx +0 -91
  82. package/template/core/src/openpress/core/FrameContext.tsx +0 -26
  83. package/template/core/src/openpress/core/MdxArea.tsx +0 -34
  84. package/template/core/src/openpress/core/Press.tsx +0 -55
  85. package/template/core/src/openpress/core/Workspace.tsx +0 -36
  86. package/template/core/src/openpress/core/cn.ts +0 -4
  87. package/template/core/src/openpress/core/index.tsx +0 -47
  88. package/template/core/src/openpress/core/primitives.tsx +0 -91
  89. package/template/core/src/openpress/core/types.ts +0 -236
  90. package/template/core/src/openpress/core/useSource.ts +0 -28
  91. package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
  92. package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
  93. package/template/core/src/openpress/document-model/documentTypes.ts +0 -147
  94. package/template/core/src/openpress/document-model/index.ts +0 -7
  95. package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -55
  96. package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
  97. package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
  98. package/template/core/src/openpress/document-model/workspaceManifestModel.ts +0 -57
  99. package/template/core/src/openpress/manuscript/index.tsx +0 -238
  100. package/template/core/src/openpress/mdx/index.ts +0 -96
  101. package/template/core/src/openpress/numbering/index.ts +0 -294
  102. package/template/core/src/openpress/reader/PageThumbnailsPanel.tsx +0 -168
  103. package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
  104. package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
  105. package/template/core/src/openpress/reader/index.ts +0 -11
  106. package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
  107. package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
  108. package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
  109. package/template/core/src/openpress/reader/readerScroll.ts +0 -92
  110. package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
  111. package/template/core/src/openpress/reader/readerTypes.ts +0 -4
  112. package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
  113. package/template/core/src/openpress/reader/usePanelState.ts +0 -56
  114. package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
  115. package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
  116. package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
  117. package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
  118. package/template/core/src/openpress/shared/Panel.tsx +0 -77
  119. package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
  120. package/template/core/src/openpress/shared/index.ts +0 -4
  121. package/template/core/src/openpress/shared/numberUtils.ts +0 -3
  122. package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
  123. package/template/core/src/openpress/workbench/Workbench.tsx +0 -506
  124. package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
  125. package/template/core/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
  126. package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
  127. package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
  128. package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
  129. package/template/core/src/openpress/workbench/actions/index.ts +0 -6
  130. package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
  131. package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
  132. package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
  133. package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
  134. package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
  135. package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
  136. package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
  137. package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
  138. package/template/core/src/openpress/workbench/document/index.ts +0 -10
  139. package/template/core/src/openpress/workbench/index.ts +0 -2
  140. package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
  141. package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
  142. package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
  143. package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
  144. package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
  145. package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -254
  146. package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
  147. package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
  148. package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
  149. package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
  150. package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -80
  151. package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
  152. package/template/core/src/openpress/workbench/panels/index.ts +0 -3
  153. package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -525
  154. package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
  155. package/template/core/src/openpress/workbench/project/index.ts +0 -2
  156. package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
  157. package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
  158. package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
  159. package/template/core/src/openpress/workbench/shell/index.ts +0 -1
  160. package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
  161. package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
  162. package/template/core/src/styles/openpress/app-shell.css +0 -251
  163. package/template/core/src/styles/openpress/media-workspace.css +0 -230
  164. package/template/core/src/styles/openpress/print-route.css +0 -184
  165. package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
  166. package/template/core/src/styles/openpress/public-viewer.css +0 -688
  167. package/template/core/src/styles/openpress/reader-runtime.css +0 -989
  168. package/template/core/src/styles/openpress/responsive.css +0 -245
  169. package/template/core/src/styles/openpress/workbench-panels.css +0 -707
  170. package/template/core/src/styles/openpress/workbench.css +0 -1255
  171. package/template/core/src/styles/openpress/workspace-gallery.css +0 -300
  172. package/template/core/src/styles/openpress.css +0 -15
  173. package/template/core/src/vite-env.d.ts +0 -9
  174. package/template/core/tsconfig.json +0 -40
  175. package/template/core/vite.config.ts +0 -584
@@ -1,157 +0,0 @@
1
- import { useId, useState } from "react";
2
- import { Check, Rocket } from "lucide-react";
3
- import type { DeploymentInfo } from "../../document-model";
4
- import { WorkbenchDialog } from "../dialog";
5
- import type { DeployStatus } from "../workbenchTypes";
6
- import {
7
- deployButtonText,
8
- deploymentStatusKind,
9
- deploymentStatusSummary,
10
- deploymentStatusText,
11
- } from "./deploymentStatusModel";
12
-
13
- export function DeploymentControl({
14
- info,
15
- status,
16
- onDeploy,
17
- }: {
18
- info: DeploymentInfo;
19
- status: DeployStatus;
20
- onDeploy: () => void | Promise<void>;
21
- }) {
22
- const titleId = useId();
23
- const [dialogOpen, setDialogOpen] = useState(false);
24
- const kind = deploymentStatusKind(info, status);
25
- const buttonText = deployButtonText(info, status);
26
- const description = deploymentStatusText(info, status);
27
- const summary = deploymentStatusSummary(info, status);
28
- const sourceLabel = deploymentSourceLabel(info);
29
- const busy = status === "deploying";
30
- const confirmDisabled = busy || status === "unavailable" || info.configured === false;
31
-
32
- const confirmDeploy = () => {
33
- if (confirmDisabled) return;
34
- setDialogOpen(false);
35
- void onDeploy();
36
- };
37
-
38
- const dialog = dialogOpen ? (
39
- <WorkbenchDialog
40
- titleId={titleId}
41
- title="部署資訊"
42
- eyebrow="Deployment"
43
- titleMeta={<span className="openpress-deploy-dialog__source">{sourceLabel}</span>}
44
- className="openpress-deploy-dialog"
45
- backdropClassName="openpress-deploy-dialog-backdrop"
46
- closeLabel="關閉部署資訊"
47
- onClose={() => setDialogOpen(false)}
48
- footer={(
49
- <>
50
- <button type="button" onClick={() => setDialogOpen(false)}>取消</button>
51
- <button type="button" disabled={confirmDisabled} onClick={confirmDeploy}>
52
- <Check aria-hidden="true" />
53
- <span>{busy ? "部署中" : "確認部署"}</span>
54
- </button>
55
- </>
56
- )}
57
- >
58
- <dl data-openpress-deploy-align="left-values">
59
- <DeployStatusRow label="狀態" value={summary} kind={kind} />
60
- <DeployLinkRow label="公開頁面" url={info.publicUrl} />
61
- <DeployLinkRow label="PDF" url={info.pdf} />
62
- </dl>
63
- {info.configured === false ? (
64
- <p className="openpress-deploy-dialog__message" role="status">
65
- {info.setupMessage ?? "部署設定尚未完成。"}
66
- </p>
67
- ) : null}
68
- </WorkbenchDialog>
69
- ) : null;
70
-
71
- return (
72
- <>
73
- <button
74
- type="button"
75
- className="openpress-workbench-toolbar-action"
76
- data-openpress-deploy
77
- data-openpress-deploy-status={kind}
78
- data-openpress-toolbar-expanded="false"
79
- data-openpress-toolbar-active="false"
80
- data-deploy-status={status}
81
- aria-busy={busy ? "true" : "false"}
82
- aria-label={buttonText}
83
- title={description}
84
- onClick={() => setDialogOpen(true)}
85
- >
86
- <Rocket aria-hidden="true" />
87
- </button>
88
- {busy ? (
89
- <span
90
- className="openpress-dev-deploy-status openpress-dev-deploy-status--toolbar"
91
- data-openpress-deploy-status={kind}
92
- role="status"
93
- aria-live="polite"
94
- >
95
- <span className="openpress-dev-deploy-status__dot" aria-hidden="true" />
96
- <span>部署中</span>
97
- </span>
98
- ) : null}
99
- {dialog}
100
- </>
101
- );
102
- }
103
-
104
- function DeployStatusRow({ label, value, kind }: { label: string; value: string; kind: string }) {
105
- return (
106
- <div>
107
- <dt>{label}</dt>
108
- <dd>
109
- <span className="openpress-deploy-dialog__status" data-openpress-deploy-status={kind}>
110
- {value}
111
- </span>
112
- </dd>
113
- </div>
114
- );
115
- }
116
-
117
- function DeployLinkRow({ label, url }: { label: string; url?: string }) {
118
- return (
119
- <div>
120
- <dt>{label}</dt>
121
- <dd>
122
- {url ? (
123
- <a href={url} target="_blank" rel="noreferrer">
124
- {formatDeployUrl(url)}
125
- </a>
126
- ) : (
127
- "尚未產生"
128
- )}
129
- </dd>
130
- </div>
131
- );
132
- }
133
-
134
- function deploymentSourceLabel(info: DeploymentInfo) {
135
- const adapter = info.adapter?.trim().toLowerCase();
136
-
137
- if (adapter === "cloudflare-pages" || adapter === "cloudflare") return "Cloudflare Pages";
138
- if (adapter === "github-pages" || adapter === "github") return "GitHub Pages";
139
- if (adapter === "zeabur" || adapter === "zebur") return "Zeabur";
140
-
141
- return info.projectName?.trim() || info.source?.trim() || info.adapter?.trim() || "本機工作區";
142
- }
143
-
144
- function formatDeployUrl(value: string) {
145
- try {
146
- const url = new URL(value);
147
- const pathname = url.pathname === "/" ? "" : url.pathname.replace(/\/$/, "");
148
- return shortenDeployUrl(`${url.host}${pathname}`);
149
- } catch {
150
- return shortenDeployUrl(value);
151
- }
152
- }
153
-
154
- function shortenDeployUrl(value: string) {
155
- if (value.length <= 48) return value;
156
- return `${value.slice(0, 30)}...${value.slice(-14)}`;
157
- }
@@ -1,96 +0,0 @@
1
- import { useCallback, useState } from "react";
2
- import { Camera } from "lucide-react";
3
- import { toPng } from "html-to-image";
4
-
5
- type ExportStatus = "idle" | "exporting" | "done" | "error";
6
-
7
- // Exports the currently visible page as a PNG. Locates the page DOM via
8
- // the data-openpress-page-index attribute (set in PublicReaderPage) and
9
- // hands it to html-to-image, then triggers a browser download.
10
- //
11
- // Lives in the workbench toolbar so it's reachable for any Press shape
12
- // (manuscript / canvas / slide); for multi-page Press the user navigates
13
- // to the page first, then exports.
14
- export function ExportImageControl({
15
- currentPageIndex,
16
- currentPageLabel,
17
- pressTitle,
18
- }: {
19
- currentPageIndex: number;
20
- currentPageLabel: string;
21
- pressTitle: string;
22
- }) {
23
- const [status, setStatus] = useState<ExportStatus>("idle");
24
-
25
- const handleExport = useCallback(async () => {
26
- if (status === "exporting") return;
27
- setStatus("exporting");
28
-
29
- try {
30
- const pageEl = typeof window === "undefined"
31
- ? null
32
- : window.document.querySelector<HTMLElement>(
33
- `[data-openpress-page-index="${currentPageIndex}"]`,
34
- );
35
- if (!pageEl) throw new Error("找不到目前頁面");
36
-
37
- // pixelRatio: 2 — retina-ish; keeps text crisp without blowing the file size.
38
- // cacheBust: true — force re-fetch of images so stale CORS doesn't taint the canvas.
39
- const dataUrl = await toPng(pageEl, {
40
- pixelRatio: 2,
41
- cacheBust: true,
42
- backgroundColor: "#ffffff",
43
- });
44
-
45
- const safeTitle = sanitizeFilename(pressTitle) || "openpress";
46
- const safePage = sanitizeFilename(currentPageLabel) || String(currentPageIndex + 1);
47
- const link = window.document.createElement("a");
48
- link.href = dataUrl;
49
- link.download = `${safeTitle}-${safePage}.png`;
50
- window.document.body.appendChild(link);
51
- link.click();
52
- link.remove();
53
-
54
- setStatus("done");
55
- window.setTimeout(() => setStatus("idle"), 1600);
56
- } catch (error) {
57
- console.error("[openpress] page PNG export failed", error);
58
- setStatus("error");
59
- window.setTimeout(() => setStatus("idle"), 2400);
60
- }
61
- }, [currentPageIndex, currentPageLabel, pressTitle, status]);
62
-
63
- const label = status === "exporting"
64
- ? "匯出中…"
65
- : status === "done"
66
- ? "已下載"
67
- : status === "error"
68
- ? "匯出失敗"
69
- : "PNG";
70
- const title = "將目前頁面匯出為 PNG";
71
-
72
- return (
73
- <button
74
- type="button"
75
- className="openpress-workbench-toolbar-action"
76
- data-openpress-page-png-export
77
- data-openpress-export-status={status}
78
- disabled={status === "exporting"}
79
- onClick={handleExport}
80
- title={title}
81
- aria-label={title}
82
- >
83
- <Camera aria-hidden="true" />
84
- <span className="openpress-workbench-toolbar-action__label">{label}</span>
85
- </button>
86
- );
87
- }
88
-
89
- function sanitizeFilename(value: string): string {
90
- return value
91
- .replace(/[\\/:*?"<>|]+/g, "-")
92
- .replace(/\s+/g, "-")
93
- .replace(/-+/g, "-")
94
- .replace(/^-+|-+$/g, "")
95
- .slice(0, 80);
96
- }
@@ -1,182 +0,0 @@
1
- import { useEffect, useId, useRef, useState } from "react";
2
- import type { ReactNode } from "react";
3
- import { Check, ChevronDown, Columns2, File, ZoomIn } from "lucide-react";
4
- import {
5
- PAGE_VIEWPORT_SCALE_OPTIONS,
6
- type PageLayoutMode,
7
- type PageViewportScaleMode,
8
- } from "../../reader";
9
-
10
- export function PageZoomControl({
11
- scaleMode,
12
- scaleLabel,
13
- pageLayoutMode,
14
- onScaleModeChange,
15
- onPageLayoutModeChange,
16
- }: {
17
- scaleMode: PageViewportScaleMode;
18
- scaleLabel: string;
19
- pageLayoutMode: PageLayoutMode;
20
- onScaleModeChange: (mode: PageViewportScaleMode) => void;
21
- onPageLayoutModeChange: (mode: PageLayoutMode) => void;
22
- }) {
23
- const menuId = useId();
24
- const rootRef = useRef<HTMLDivElement | null>(null);
25
- const [open, setOpen] = useState(false);
26
- const fixedOptions = PAGE_VIEWPORT_SCALE_OPTIONS.filter((option) => option.value.startsWith("scale-"));
27
- const fitOptions = PAGE_VIEWPORT_SCALE_OPTIONS.filter((option) => option.value.startsWith("fit-"));
28
-
29
- useEffect(() => {
30
- if (!open) return undefined;
31
- const handlePointerDown = (event: PointerEvent) => {
32
- if (event.target instanceof Node && rootRef.current?.contains(event.target)) return;
33
- setOpen(false);
34
- };
35
- const handleKeyDown = (event: KeyboardEvent) => {
36
- if (event.key === "Escape") setOpen(false);
37
- };
38
- window.addEventListener("pointerdown", handlePointerDown);
39
- window.addEventListener("keydown", handleKeyDown);
40
- return () => {
41
- window.removeEventListener("pointerdown", handlePointerDown);
42
- window.removeEventListener("keydown", handleKeyDown);
43
- };
44
- }, [open]);
45
-
46
- const selectScale = (mode: PageViewportScaleMode) => {
47
- onScaleModeChange(mode);
48
- setOpen(false);
49
- };
50
- const selectLayout = (mode: PageLayoutMode) => {
51
- onPageLayoutModeChange(mode);
52
- setOpen(false);
53
- };
54
-
55
- return (
56
- <div className="openpress-workbench-zoom-control-wrap" ref={rootRef} data-openpress-page-zoom-control>
57
- <button
58
- type="button"
59
- className="openpress-workbench-zoom-control"
60
- data-openpress-page-zoom
61
- data-openpress-scale-mode={scaleMode}
62
- data-openpress-toolbar-active={scaleMode === "fit-width" ? "false" : "true"}
63
- title={`頁面縮放 ${scaleLabel}`}
64
- aria-label={`頁面縮放 ${scaleLabel}`}
65
- aria-haspopup="menu"
66
- aria-expanded={open}
67
- aria-controls={open ? menuId : undefined}
68
- onClick={() => setOpen((value) => !value)}
69
- >
70
- <ZoomIn aria-hidden="true" />
71
- <span>{scaleLabel}</span>
72
- <ChevronDown className="openpress-workbench-zoom-control__chevron" aria-hidden="true" />
73
- </button>
74
- {open ? (
75
- <div
76
- id={menuId}
77
- className="openpress-workbench-zoom-menu"
78
- data-openpress-page-zoom-menu
79
- role="menu"
80
- aria-label="頁面顯示與縮放"
81
- >
82
- <div className="openpress-workbench-zoom-menu__section" role="group" aria-label="頁面模式">
83
- <PageLayoutOption
84
- mode="single"
85
- active={pageLayoutMode === "single"}
86
- icon={<File aria-hidden="true" />}
87
- label="一頁"
88
- onSelect={selectLayout}
89
- />
90
- <PageLayoutOption
91
- mode="spread"
92
- active={pageLayoutMode === "spread"}
93
- icon={<Columns2 aria-hidden="true" />}
94
- label="雙頁"
95
- onSelect={selectLayout}
96
- />
97
- </div>
98
- <div className="openpress-workbench-zoom-menu__divider" role="presentation" />
99
- <div className="openpress-workbench-zoom-menu__section" role="group" aria-label="固定縮放">
100
- {fixedOptions.map((option) => (
101
- <ZoomOption
102
- key={option.value}
103
- mode={option.value}
104
- active={scaleMode === option.value}
105
- label={option.label}
106
- onSelect={selectScale}
107
- />
108
- ))}
109
- </div>
110
- <div className="openpress-workbench-zoom-menu__divider" role="presentation" />
111
- <div className="openpress-workbench-zoom-menu__section" role="group" aria-label="符合顯示">
112
- {fitOptions.map((option) => (
113
- <ZoomOption
114
- key={option.value}
115
- mode={option.value}
116
- active={scaleMode === option.value}
117
- label={option.label}
118
- onSelect={selectScale}
119
- />
120
- ))}
121
- </div>
122
- </div>
123
- ) : null}
124
- </div>
125
- );
126
- }
127
-
128
- function PageLayoutOption({
129
- mode,
130
- active,
131
- icon,
132
- label,
133
- onSelect,
134
- }: {
135
- mode: PageLayoutMode;
136
- active: boolean;
137
- icon: ReactNode;
138
- label: string;
139
- onSelect: (mode: PageLayoutMode) => void;
140
- }) {
141
- return (
142
- <button
143
- type="button"
144
- className="openpress-workbench-zoom-menu__item"
145
- data-openpress-page-layout-option={mode}
146
- role="menuitemcheckbox"
147
- aria-checked={active}
148
- onClick={() => onSelect(mode)}
149
- >
150
- <span className="openpress-workbench-zoom-menu__check">{active ? <Check aria-hidden="true" /> : null}</span>
151
- {icon}
152
- <span>{label}</span>
153
- </button>
154
- );
155
- }
156
-
157
- function ZoomOption({
158
- mode,
159
- active,
160
- label,
161
- onSelect,
162
- }: {
163
- mode: PageViewportScaleMode;
164
- active: boolean;
165
- label: string;
166
- onSelect: (mode: PageViewportScaleMode) => void;
167
- }) {
168
- return (
169
- <button
170
- type="button"
171
- className="openpress-workbench-zoom-menu__item"
172
- data-openpress-zoom-option={mode}
173
- role="menuitemradio"
174
- aria-checked={active}
175
- onClick={() => onSelect(mode)}
176
- >
177
- <span className="openpress-workbench-zoom-menu__check">{active ? <Check aria-hidden="true" /> : null}</span>
178
- <span className="openpress-workbench-zoom-menu__spacer" aria-hidden="true" />
179
- <span>{label}</span>
180
- </button>
181
- );
182
- }