@open-press/core 1.1.4 → 1.2.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 (51) hide show
  1. package/engine/cli.mjs +3 -3
  2. package/engine/commands/_shared.mjs +89 -13
  3. package/engine/commands/deploy.mjs +19 -4
  4. package/engine/commands/image.mjs +9 -3
  5. package/engine/commands/pdf.mjs +4 -1
  6. package/engine/output/chrome-pdf.mjs +102 -0
  7. package/engine/output/static-server.mjs +64 -17
  8. package/engine/react/document-export.mjs +22 -0
  9. package/package.json +1 -1
  10. package/src/openpress/app/OpenPressApp.tsx +5 -1
  11. package/src/openpress/app/OpenPressRuntime.tsx +85 -6
  12. package/src/openpress/reader/PageThumbnailsPanel.tsx +28 -5
  13. package/src/openpress/reader/PublicReaderPage.tsx +163 -74
  14. package/src/openpress/reader/SlidePresentationPage.tsx +37 -15
  15. package/src/openpress/reader/SlidePublicPage.tsx +332 -0
  16. package/src/openpress/reader/index.ts +1 -0
  17. package/src/openpress/reader/pageViewportScaleModel.ts +5 -3
  18. package/src/openpress/reader/usePageViewportScale.ts +9 -5
  19. package/src/openpress/reader/usePanelState.ts +14 -5
  20. package/src/openpress/shared/index.ts +1 -0
  21. package/src/openpress/shared/staticSearch.ts +174 -0
  22. package/src/openpress/workbench/Workbench.tsx +61 -176
  23. package/src/openpress/workbench/actions/DeploymentControl.tsx +1 -1
  24. package/src/openpress/workbench/actions/ExportControl.tsx +267 -0
  25. package/src/openpress/workbench/actions/SearchControl.tsx +32 -43
  26. package/src/openpress/workbench/actions/index.ts +1 -1
  27. package/src/openpress/workbench/actions/useDeploymentWorkbench.ts +21 -5
  28. package/src/openpress/workbench/hooks/useWorkbenchNavigation.ts +42 -0
  29. package/src/openpress/workbench/inspector/useInspectorComments.ts +6 -6
  30. package/src/openpress/workbench/project/ProjectEntryPanel.tsx +2 -278
  31. package/src/openpress/workbench/shell/WorkbenchShell.tsx +44 -18
  32. package/src/openpress/workbench/shell/WorkbenchToolbarActions.tsx +206 -0
  33. package/src/styles/openpress/app-shell.css +0 -83
  34. package/src/styles/openpress/print-route.css +1 -3
  35. package/src/styles/openpress/project-preview-panel.css +5 -783
  36. package/src/styles/openpress/public-viewer.css +7 -249
  37. package/src/styles/openpress/reader-runtime.css +0 -274
  38. package/src/styles/openpress/slide-presenter.css +150 -0
  39. package/src/styles/openpress/slide-public-viewer.css +222 -0
  40. package/src/styles/openpress/workbench-dialog.css +267 -0
  41. package/src/styles/openpress/workbench-export.css +154 -0
  42. package/src/styles/openpress/workbench-inline-editor.css +128 -0
  43. package/src/styles/openpress/workbench-panels.css +0 -88
  44. package/src/styles/openpress/workbench-search.css +257 -0
  45. package/src/styles/openpress/workbench-toolbar.css +422 -0
  46. package/src/styles/openpress/workbench.css +34 -1263
  47. package/src/styles/openpress/workspace-gallery.css +0 -5
  48. package/src/styles/openpress.css +7 -1
  49. package/vite.config.ts +66 -17
  50. package/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
  51. package/src/styles/openpress/media-workspace.css +0 -230
@@ -6,6 +6,7 @@ type WorkbenchShellContextValue = {
6
6
  rightPanelOpen: boolean;
7
7
  onToggleLeftPanel: () => void;
8
8
  onToggleRightPanel: () => void;
9
+ withRightPanel: boolean;
9
10
  };
10
11
 
11
12
  const WorkbenchShellContext = createContext<WorkbenchShellContextValue | null>(null);
@@ -18,7 +19,6 @@ function useWorkbenchShell() {
18
19
 
19
20
  function WorkbenchShellRoot({
20
21
  style,
21
- devMode,
22
22
  viewMode,
23
23
  pressType = "pages",
24
24
  presentationMode = false,
@@ -28,10 +28,11 @@ function WorkbenchShellRoot({
28
28
  rightPanelOpen,
29
29
  onToggleLeftPanel,
30
30
  onToggleRightPanel,
31
+ withRightPanel = true,
32
+ publicViewer = false,
31
33
  children,
32
34
  }: {
33
35
  style: CSSProperties;
34
- devMode: boolean;
35
36
  viewMode: string;
36
37
  pressType?: string;
37
38
  presentationMode?: boolean;
@@ -41,20 +42,42 @@ function WorkbenchShellRoot({
41
42
  rightPanelOpen: boolean;
42
43
  onToggleLeftPanel: () => void;
43
44
  onToggleRightPanel: () => void;
45
+ // When false the toolbar omits the right-panel toggle button and the
46
+ // shell grid runs without a right column. Used by the public viewer
47
+ // where the right panel currently has no content (comments + project
48
+ // entry are workbench-only).
49
+ withRightPanel?: boolean;
50
+ // Marks the outer <main> with `data-openpress-public-viewer` so CSS
51
+ // and external integrations can target the public reading surface.
52
+ publicViewer?: boolean;
44
53
  children: ReactNode;
45
54
  }) {
46
- const scrimOpen = leftPanelOpen || rightPanelOpen;
47
- const handleScrimClick = rightPanelOpen ? onToggleRightPanel : onToggleLeftPanel;
55
+ const effectiveRightOpen = withRightPanel ? rightPanelOpen : false;
56
+ const scrimOpen = leftPanelOpen || effectiveRightOpen;
57
+ const handleScrimClick = effectiveRightOpen ? onToggleRightPanel : onToggleLeftPanel;
48
58
  const shellClassName = [
49
59
  "reader-app openpress-reader-app openpress-public-viewer openpress-dev-public-viewer openpress-workbench-shell is-ready",
50
60
  leftPanelOpen ? "" : "is-closed-left",
51
- rightPanelOpen ? "" : "is-closed-right",
61
+ effectiveRightOpen ? "" : "is-closed-right",
62
+ withRightPanel ? "" : "openpress-workbench-shell--no-right-panel",
52
63
  presentationMode ? "is-presentation-mode" : "",
53
64
  ].filter(Boolean).join(" ");
54
65
 
55
66
  return (
56
- <WorkbenchShellContext.Provider value={{ leftPanelOpen, rightPanelOpen, onToggleLeftPanel, onToggleRightPanel }}>
57
- <main className="openpress-workbench" style={style} data-dev-mode={devMode ? "true" : "false"}>
67
+ <WorkbenchShellContext.Provider
68
+ value={{
69
+ leftPanelOpen,
70
+ rightPanelOpen: effectiveRightOpen,
71
+ onToggleLeftPanel,
72
+ onToggleRightPanel,
73
+ withRightPanel,
74
+ }}
75
+ >
76
+ <main
77
+ className="openpress-workbench"
78
+ style={style}
79
+ data-openpress-public-viewer={publicViewer ? "true" : undefined}
80
+ >
58
81
  <div
59
82
  className={shellClassName}
60
83
  data-openpress-react-runtime="true"
@@ -82,6 +105,7 @@ export function WorkbenchToolbar({ children }: { children: ReactNode }) {
82
105
  rightPanelOpen,
83
106
  onToggleLeftPanel,
84
107
  onToggleRightPanel,
108
+ withRightPanel,
85
109
  } = useWorkbenchShell();
86
110
  const LeftIcon = leftPanelOpen ? PanelLeftClose : PanelLeftOpen;
87
111
  const RightIcon = rightPanelOpen ? PanelRightClose : PanelRightOpen;
@@ -109,17 +133,19 @@ export function WorkbenchToolbar({ children }: { children: ReactNode }) {
109
133
  <div className="openpress-workbench-toolbar__content">
110
134
  {children}
111
135
  </div>
112
- <button
113
- type="button"
114
- className="openpress-workbench-toolbar-panel-toggle"
115
- data-openpress-toggle-right-panel
116
- data-openpress-panel-open={rightPanelOpen ? "true" : "false"}
117
- aria-label={rightLabel}
118
- title={rightLabel}
119
- onClick={onToggleRightPanel}
120
- >
121
- <RightIcon aria-hidden="true" />
122
- </button>
136
+ {withRightPanel ? (
137
+ <button
138
+ type="button"
139
+ className="openpress-workbench-toolbar-panel-toggle"
140
+ data-openpress-toggle-right-panel
141
+ data-openpress-panel-open={rightPanelOpen ? "true" : "false"}
142
+ aria-label={rightLabel}
143
+ title={rightLabel}
144
+ onClick={onToggleRightPanel}
145
+ >
146
+ <RightIcon aria-hidden="true" />
147
+ </button>
148
+ ) : null}
123
149
  </header>
124
150
  );
125
151
  }
@@ -0,0 +1,206 @@
1
+ import { Home, MousePointer2, Play, Ruler } from "lucide-react";
2
+ import type { Dispatch, SetStateAction } from "react";
3
+ import type { DeploymentInfo, HtmlPageBlock, SourceBlock, Theme } from "../../document-model";
4
+ import type { PageLayoutMode, PageViewportScaleMode } from "../../reader";
5
+ import {
6
+ DeploymentControl,
7
+ ExportControl,
8
+ PageZoomControl,
9
+ SearchControl,
10
+ } from "../actions";
11
+ import type { DeployStatus, InspectorCommentStatus, PdfActionStatus } from "../workbenchTypes";
12
+ import type { PageGeometrySpec } from "../workbenchFormatters";
13
+ import type { InlineDocumentEditStatus } from "../document";
14
+
15
+ export function WorkbenchToolbarActions({
16
+ pages,
17
+ currentPageIndex,
18
+ pressTitle,
19
+ theme,
20
+ workspaceMode,
21
+ sourceBlocksByPath,
22
+ onSelectPage,
23
+ onBackToWorkspace,
24
+ isSlidePress,
25
+ onOpenPresentation,
26
+ pageGeometry,
27
+ scaleMode,
28
+ scaleLabel,
29
+ pageLayoutMode,
30
+ onScaleModeChange,
31
+ onPageLayoutModeChange,
32
+ inlineEditStatus,
33
+ editStatusMessage,
34
+ inspectorMode,
35
+ inspectorToolbarExpanded,
36
+ inspectorSelectionLabel,
37
+ onInspectorModeChange,
38
+ inspectorCommentStatus,
39
+ inspectorCommentStatusMessage,
40
+ deploymentInfo,
41
+ deploymentStatus,
42
+ localDeployEnabled,
43
+ onDeploy,
44
+ onExportPdf,
45
+ pdfDisabled,
46
+ pdfLabel,
47
+ pdfStatusMessage,
48
+ pdfActionStatus,
49
+ }: {
50
+ pages: HtmlPageBlock[];
51
+ currentPageIndex: number;
52
+ pressTitle: string;
53
+ theme?: Theme;
54
+ workspaceMode: boolean;
55
+ sourceBlocksByPath: Record<string, SourceBlock[]>;
56
+ onSelectPage: (pageIndex: number, options?: { behavior?: ScrollBehavior }) => void;
57
+ onBackToWorkspace?: () => void;
58
+ isSlidePress: boolean;
59
+ onOpenPresentation?: (pageIndex: number) => void;
60
+ pageGeometry: PageGeometrySpec;
61
+ scaleMode: PageViewportScaleMode;
62
+ scaleLabel: string;
63
+ pageLayoutMode: PageLayoutMode;
64
+ onScaleModeChange: Dispatch<SetStateAction<PageViewportScaleMode>>;
65
+ onPageLayoutModeChange: Dispatch<SetStateAction<PageLayoutMode>>;
66
+ inlineEditStatus: InlineDocumentEditStatus;
67
+ editStatusMessage: string;
68
+ inspectorMode: boolean;
69
+ inspectorToolbarExpanded: boolean;
70
+ inspectorSelectionLabel: string;
71
+ onInspectorModeChange: (enabled: boolean) => void;
72
+ inspectorCommentStatus: InspectorCommentStatus;
73
+ inspectorCommentStatusMessage: string;
74
+ deploymentInfo: DeploymentInfo;
75
+ deploymentStatus: DeployStatus;
76
+ localDeployEnabled: boolean;
77
+ onDeploy: () => Promise<void>;
78
+ onExportPdf: () => void;
79
+ pdfDisabled: boolean;
80
+ pdfLabel: string;
81
+ pdfStatusMessage: string | null;
82
+ pdfActionStatus: PdfActionStatus;
83
+ }) {
84
+ return (
85
+ <>
86
+ {onBackToWorkspace ? (
87
+ <div className="openpress-workbench-toolbar__group" aria-label="工作台導覽">
88
+ <button
89
+ type="button"
90
+ className="openpress-workbench-toolbar-action openpress-workbench-toolbar-action--back"
91
+ data-openpress-back-to-workspace
92
+ onClick={onBackToWorkspace}
93
+ title="回到工作台"
94
+ aria-label="回到工作台"
95
+ >
96
+ <Home aria-hidden="true" />
97
+ <span className="openpress-workbench-toolbar-action__label">工作台</span>
98
+ </button>
99
+ </div>
100
+ ) : null}
101
+ <div className="openpress-workbench-toolbar__group" aria-label="匯出">
102
+ <ExportControl
103
+ pages={pages}
104
+ currentPageIndex={currentPageIndex}
105
+ pressTitle={pressTitle}
106
+ theme={theme}
107
+ onExportPdf={onExportPdf}
108
+ pdfDisabled={pdfDisabled}
109
+ pdfLabel={pdfLabel}
110
+ pdfStatusMessage={pdfStatusMessage}
111
+ pdfActionStatus={pdfActionStatus}
112
+ />
113
+ </div>
114
+ <div className="openpress-workbench-toolbar__group openpress-workbench-toolbar__group--page" aria-label="頁面規格">
115
+ {isSlidePress && onOpenPresentation ? (
116
+ <button
117
+ type="button"
118
+ className="openpress-workbench-toolbar-action"
119
+ data-openpress-slide-present
120
+ data-openpress-toolbar-expanded="false"
121
+ data-openpress-toolbar-active="false"
122
+ aria-pressed="false"
123
+ title="進入放映模式"
124
+ aria-label="進入放映模式"
125
+ onClick={() => onOpenPresentation(currentPageIndex)}
126
+ >
127
+ <Play aria-hidden="true" />
128
+ <span className="openpress-workbench-toolbar-action__label">放映</span>
129
+ </button>
130
+ ) : null}
131
+ <button
132
+ type="button"
133
+ className="openpress-workbench-page-geometry"
134
+ data-openpress-page-geometry
135
+ title={pageGeometry.title}
136
+ aria-label={`頁面規格 ${pageGeometry.title}`}
137
+ >
138
+ <Ruler aria-hidden="true" />
139
+ <span className="openpress-workbench-page-geometry__label">{pageGeometry.label}</span>
140
+ <span className="openpress-workbench-page-geometry__dimensions">{pageGeometry.dimensions}</span>
141
+ </button>
142
+ <PageZoomControl
143
+ scaleMode={scaleMode}
144
+ scaleLabel={scaleLabel}
145
+ pageLayoutMode={pageLayoutMode}
146
+ onScaleModeChange={onScaleModeChange}
147
+ onPageLayoutModeChange={onPageLayoutModeChange}
148
+ />
149
+ </div>
150
+ <div className="openpress-workbench-toolbar__group openpress-workbench-toolbar__group--right" aria-label="工作台狀態與發布">
151
+ {workspaceMode ? (
152
+ <SearchControl
153
+ sourceBlocksByPath={sourceBlocksByPath}
154
+ onSelectPage={onSelectPage}
155
+ />
156
+ ) : null}
157
+ {workspaceMode && editStatusMessage ? (
158
+ <span
159
+ className="openpress-dev-edit-status openpress-dev-edit-status--toolbar"
160
+ data-openpress-edit-status={inlineEditStatus.state}
161
+ role="status"
162
+ aria-live="polite"
163
+ >
164
+ {inlineEditStatus.state === "saving" ? <span className="openpress-dev-edit-status__spinner" aria-hidden="true" /> : null}
165
+ <span>{editStatusMessage}</span>
166
+ </span>
167
+ ) : null}
168
+ {workspaceMode ? (
169
+ <button
170
+ type="button"
171
+ className="openpress-workbench-toolbar-action"
172
+ data-openpress-inspector-toggle
173
+ data-openpress-inspector-active={inspectorMode ? "true" : "false"}
174
+ data-openpress-toolbar-expanded={inspectorToolbarExpanded ? "true" : "false"}
175
+ data-openpress-toolbar-active={inspectorToolbarExpanded ? "true" : "false"}
176
+ onClick={() => onInspectorModeChange(!inspectorMode)}
177
+ aria-pressed={inspectorMode}
178
+ title={inspectorMode ? "關閉註解" : "開啟註解"}
179
+ aria-label={inspectorMode ? "關閉註解" : "開啟註解"}
180
+ >
181
+ <MousePointer2 aria-hidden="true" />
182
+ <span className="openpress-workbench-toolbar-action__label">{inspectorMode ? "註解中" : "註解"}</span>
183
+ <span className="openpress-dev-inspector-status">{inspectorSelectionLabel}</span>
184
+ </button>
185
+ ) : null}
186
+ {workspaceMode && inspectorMode ? (
187
+ <span
188
+ className="openpress-dev-inspector-status"
189
+ role="status"
190
+ aria-live="polite"
191
+ data-openpress-inspector-comment-status={inspectorCommentStatus}
192
+ >
193
+ {inspectorCommentStatusMessage}
194
+ </span>
195
+ ) : null}
196
+ {localDeployEnabled ? (
197
+ <DeploymentControl
198
+ info={deploymentInfo}
199
+ status={deploymentStatus}
200
+ onDeploy={onDeploy}
201
+ />
202
+ ) : null}
203
+ </div>
204
+ </>
205
+ );
206
+ }
@@ -105,89 +105,6 @@ body {
105
105
  font-size: 13px;
106
106
  }
107
107
 
108
- /* ── Action Overlay(部署)──────────────────────────────────── */
109
- .openpress-action-overlay {
110
- position: fixed;
111
- inset: 0;
112
- z-index: 200;
113
- display: flex;
114
- align-items: center;
115
- justify-content: center;
116
- background: rgb(10 10 10 / 72%);
117
- backdrop-filter: blur(12px);
118
- -webkit-backdrop-filter: blur(12px);
119
- animation: openpress-overlay-in 0.18s ease both;
120
- }
121
-
122
- @keyframes openpress-overlay-in {
123
- from { opacity: 0; }
124
- to { opacity: 1; }
125
- }
126
-
127
- .openpress-action-overlay__card {
128
- display: flex;
129
- flex-direction: column;
130
- align-items: center;
131
- gap: 10px;
132
- border: 1px solid rgb(255 255 255 / 12%);
133
- border-radius: 16px;
134
- padding: 36px 48px;
135
- background: rgb(22 22 22 / 96%);
136
- box-shadow: 0 24px 64px rgb(0 0 0 / 60%);
137
- text-align: center;
138
- min-width: 220px;
139
- }
140
-
141
- .openpress-action-overlay__title {
142
- margin: 0;
143
- color: #f2f2f0;
144
- font-size: 15px;
145
- font-weight: 600;
146
- letter-spacing: 0.01em;
147
- }
148
-
149
- .openpress-action-overlay__sub {
150
- margin: 0;
151
- color: rgb(200 200 200 / 60%);
152
- font-size: 12px;
153
- }
154
-
155
- /* ── Deploy Icon ─────────────────────────────────────────────── */
156
- .openpress-deploy-icon {
157
- position: relative;
158
- width: 80px;
159
- height: 80px;
160
- display: flex;
161
- align-items: center;
162
- justify-content: center;
163
- }
164
-
165
- .openpress-deploy-icon--deploying { color: var(--openpress-accent, #e5c97a); }
166
- .openpress-deploy-icon--deployed { color: #6ee7a0; }
167
- .openpress-deploy-icon--failed { color: #f87171; }
168
-
169
- .openpress-deploy-rocket {
170
- animation: openpress-rocket-fly 1.4s ease-in-out infinite alternate;
171
- }
172
-
173
- @keyframes openpress-rocket-fly {
174
- from { transform: translateY(4px) rotate(-6deg); }
175
- to { transform: translateY(-6px) rotate(6deg); }
176
- }
177
-
178
- .openpress-deploy-orbit {
179
- position: absolute;
180
- inset: 0;
181
- width: 80px;
182
- height: 80px;
183
- animation: openpress-orbit-spin 2.4s linear infinite;
184
- }
185
-
186
- @keyframes openpress-orbit-spin {
187
- from { transform: rotate(0deg); }
188
- to { transform: rotate(360deg); }
189
- }
190
-
191
108
  /* ── Empty document state ───────────────────────────────────── */
192
109
 
193
110
  .openpress-empty-state {
@@ -27,7 +27,6 @@
27
27
  }
28
28
 
29
29
  .openpress-workbench,
30
- .openpress-public-shell,
31
30
  .openpress-reader-app,
32
31
  .openpress-reader-app.is-closed-left,
33
32
  .openpress-reader-app.is-closed-right,
@@ -47,8 +46,7 @@
47
46
  .reader-navbar,
48
47
  .reader-side-nav,
49
48
  .openpress-workspace-panel,
50
- .openpress-public-identity,
51
- .openpress-html-page__toolbar {
49
+ .openpress-public-identity {
52
50
  display: none !important;
53
51
  }
54
52