@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,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
|
-
}
|