@open-press/core 0.7.1 → 1.0.0
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 +6 -3
- package/engine/cli.mjs +8 -8
- package/engine/commands/_shared.mjs +37 -15
- package/engine/commands/dev.mjs +2 -2
- package/engine/commands/image.mjs +29 -0
- package/engine/commands/skills-sync.mjs +71 -0
- package/engine/commands/typecheck.mjs +63 -1
- package/engine/commands/upgrade.mjs +3 -3
- package/engine/document-export.mjs +1 -1
- package/engine/output/chrome-pdf.mjs +110 -3
- package/engine/output/static-server.mjs +87 -9
- package/engine/react/comment-endpoint.mjs +13 -39
- package/engine/react/comment-marker.mjs +43 -19
- package/engine/react/document-entry.mjs +46 -28
- package/engine/react/document-export.mjs +328 -164
- package/engine/react/http-json.mjs +24 -0
- package/engine/react/mdx-compile.mjs +126 -3
- package/engine/react/measurement-css.mjs +114 -1
- package/engine/react/object-entities.mjs +204 -0
- package/engine/react/pagination/allocator.mjs +48 -3
- package/engine/react/pagination.mjs +1 -1
- package/engine/react/pipeline/allocate.mjs +41 -72
- package/engine/react/pipeline/frame-measurement.mjs +6 -0
- package/engine/react/press-tree-inspection.mjs +172 -0
- package/engine/react/project-asset-endpoint.mjs +6 -24
- package/engine/react/source-edit-endpoint.d.mts +10 -0
- package/engine/react/source-edit-endpoint.mjs +75 -0
- package/engine/react/sources/mdx-resolver.mjs +13 -15
- package/engine/react/style-discovery.mjs +23 -8
- package/engine/runtime/config.d.mts +8 -0
- package/engine/runtime/config.mjs +57 -60
- package/engine/runtime/file-utils.mjs +9 -1
- package/engine/runtime/file-walk.mjs +22 -0
- package/engine/runtime/inspection.mjs +1 -20
- package/engine/runtime/page-geometry.mjs +131 -0
- package/engine/runtime/path-utils.mjs +20 -0
- package/engine/runtime/source-text-tools.d.mts +102 -0
- package/engine/runtime/source-text-tools.mjs +551 -16
- package/engine/runtime/source-workspace.mjs +16 -34
- package/engine/runtime/validation.mjs +19 -10
- package/package.json +3 -5
- package/src/openpress/app/OpenPressApp.tsx +296 -0
- package/src/openpress/{renderer.tsx → app/OpenPressRuntime.tsx} +20 -9
- package/src/openpress/app/WorkspaceGalleryPage.tsx +219 -0
- package/src/openpress/app/index.ts +2 -0
- package/src/openpress/core/Frame.tsx +26 -15
- package/src/openpress/core/FrameContext.tsx +10 -3
- package/src/openpress/core/MdxArea.tsx +11 -12
- package/src/openpress/core/Press.tsx +25 -4
- package/src/openpress/core/Workspace.tsx +36 -0
- package/src/openpress/core/cn.ts +4 -0
- package/src/openpress/core/index.tsx +11 -3
- package/src/openpress/core/primitives.tsx +74 -6
- package/src/openpress/core/types.ts +94 -41
- package/src/openpress/core/useSource.ts +1 -1
- package/src/openpress/{anchorMap.ts → document-model/anchorMapModel.ts} +1 -1
- package/src/openpress/{indexes.ts → document-model/documentIndexes.ts} +1 -1
- package/src/openpress/{types.ts → document-model/documentTypes.ts} +51 -0
- package/src/openpress/document-model/index.ts +7 -0
- package/src/openpress/document-model/objectEntityModel.ts +55 -0
- package/src/openpress/{projectIdentity.ts → document-model/projectIdentityModel.ts} +1 -1
- package/src/openpress/{reactDocumentMetadata.ts → document-model/reactDocumentMetadataModel.ts} +1 -1
- package/src/openpress/document-model/workspaceManifestModel.ts +57 -0
- package/src/openpress/manuscript/index.tsx +49 -7
- package/src/openpress/mdx/index.ts +15 -7
- package/src/openpress/reader/PageThumbnailsPanel.tsx +168 -0
- package/src/openpress/{publicPage.tsx → reader/PublicReaderPage.tsx} +31 -51
- package/src/openpress/{workbenchPanels.tsx → reader/ReaderNavigationPanel.tsx} +6 -5
- package/src/openpress/reader/index.ts +11 -0
- package/src/openpress/reader/pageViewportScaleModel.ts +73 -0
- package/src/openpress/reader/readerTypes.ts +4 -0
- package/src/openpress/reader/usePageViewportScale.ts +119 -0
- package/src/openpress/reader/usePanelState.ts +56 -0
- package/src/openpress/reader/useReaderHashSync.ts +61 -0
- package/src/openpress/reader/useReaderKeyboardNav.ts +48 -0
- package/src/openpress/reader/useReaderRuntime.ts +146 -0
- package/src/openpress/reader/useReaderScrollAnchor.ts +64 -0
- package/src/openpress/shared/Panel.tsx +77 -0
- package/src/openpress/shared/index.ts +4 -0
- package/src/openpress/shared/numberUtils.ts +3 -0
- package/src/openpress/{runtimeMode.ts → shared/runtimeMode.ts} +0 -11
- package/src/openpress/workbench/Workbench.tsx +506 -0
- package/src/openpress/workbench/actions/DeploymentControl.tsx +157 -0
- package/src/openpress/workbench/actions/ExportImageControl.tsx +96 -0
- package/src/openpress/workbench/actions/PageZoomControl.tsx +182 -0
- package/src/openpress/workbench/actions/SearchControl.tsx +345 -0
- package/src/openpress/workbench/actions/deploymentStatusModel.ts +112 -0
- package/src/openpress/workbench/actions/index.ts +6 -0
- package/src/openpress/workbench/actions/useDeploymentWorkbench.ts +136 -0
- package/src/openpress/workbench/dialog/WorkbenchDialog.tsx +72 -0
- package/src/openpress/workbench/dialog/index.ts +1 -0
- package/src/openpress/workbench/document/components/DocumentPanel.tsx +127 -0
- package/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +207 -0
- package/src/openpress/workbench/document/components/ReaderStage.tsx +9 -0
- package/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +34 -0
- package/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +525 -0
- package/src/openpress/workbench/document/index.ts +10 -0
- package/src/openpress/workbench/index.ts +2 -0
- package/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +459 -0
- package/src/openpress/workbench/inspector/index.ts +5 -0
- package/src/openpress/workbench/inspector/inlineCommentModel.ts +125 -0
- package/src/openpress/workbench/inspector/inspectorGeometryModel.ts +160 -0
- package/src/openpress/workbench/inspector/inspectorModel.ts +408 -0
- package/src/openpress/workbench/inspector/useInspectorComments.ts +254 -0
- package/src/openpress/workbench/mentions/MentionSuggestionList.tsx +41 -0
- package/src/openpress/workbench/mentions/index.ts +2 -0
- package/src/openpress/{composerMentions.ts → workbench/mentions/useComposerMentions.ts} +1 -4
- package/src/openpress/workbench/panels/Panel.tsx +1 -0
- package/src/openpress/workbench/panels/PendingCommentsPanel.tsx +80 -0
- package/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +29 -0
- package/src/openpress/workbench/panels/index.ts +3 -0
- package/src/openpress/workbench/project/ProjectEntryPanel.tsx +525 -0
- package/src/openpress/workbench/project/ProjectPreviewDialog.tsx +35 -0
- package/src/openpress/workbench/project/index.ts +2 -0
- package/src/openpress/workbench/project/projectPreviewTypes.ts +11 -0
- package/src/openpress/workbench/shell/WorkbenchShell.tsx +167 -0
- package/src/openpress/workbench/shell/index.ts +1 -0
- package/src/openpress/workbench/workbenchFormatters.ts +120 -0
- package/src/openpress/workbench/workbenchTypes.ts +35 -0
- package/src/styles/openpress/print-route.css +0 -2
- package/src/styles/openpress/{project-workspace.css → project-preview-panel.css} +13 -407
- package/src/styles/openpress/public-viewer.css +25 -320
- package/src/styles/openpress/reader-runtime.css +252 -55
- package/src/styles/openpress/responsive.css +145 -270
- package/src/styles/openpress/workbench-panels.css +327 -178
- package/src/styles/openpress/workbench.css +986 -451
- package/src/styles/openpress/workspace-gallery.css +300 -0
- package/src/styles/openpress.css +2 -1
- package/tsconfig.json +1 -1
- package/vite.config.ts +50 -0
- package/engine/commands/init.mjs +0 -24
- package/engine/init.mjs +0 -90
- package/src/openpress/App.tsx +0 -127
- package/src/openpress/inspector.ts +0 -282
- package/src/openpress/projectWorkspace.tsx +0 -919
- package/src/openpress/readerRuntime.ts +0 -230
- package/src/openpress/workbench.tsx +0 -1265
- package/src/openpress/workbenchTypes.ts +0 -4
- /package/src/openpress/{readerPageRegistry.ts → reader/readerPageRegistry.ts} +0 -0
- /package/src/openpress/{pageRoute.ts → reader/readerPageRoute.ts} +0 -0
- /package/src/openpress/{readerScroll.ts → reader/readerScroll.ts} +0 -0
- /package/src/openpress/{readerState.ts → reader/readerStateModel.ts} +0 -0
- /package/src/openpress/{frameScheduler.ts → shared/frameScheduler.ts} +0 -0
- /package/src/openpress/{projectSources.ts → workbench/project/projectSourceModel.ts} +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/* Workspace gallery — the reader's landing page for multi-Press
|
|
2
|
+
workspaces. Shows a card per <Press> child; clicking enters that
|
|
3
|
+
document's reader. Single-Press workspaces skip the gallery and
|
|
4
|
+
load the document directly, so this CSS is dormant until users
|
|
5
|
+
add a second <Press> to their <Workspace>.
|
|
6
|
+
|
|
7
|
+
Layout intent: Figma-style file grid — uniform card width, fixed
|
|
8
|
+
thumbnail aspect ratio (4:3), filename + meta below. Each card
|
|
9
|
+
loads its first page asynchronously and renders it scaled-down
|
|
10
|
+
inside the thumbnail slot. */
|
|
11
|
+
|
|
12
|
+
.openpress-workspace-gallery {
|
|
13
|
+
--workspace-bg: #10110f;
|
|
14
|
+
--workspace-bg-soft: #171813;
|
|
15
|
+
--workspace-ink: #f4f1e8;
|
|
16
|
+
--workspace-muted: rgba(244, 241, 232, 0.52);
|
|
17
|
+
--workspace-line: rgba(244, 241, 232, 0.12);
|
|
18
|
+
--workspace-card: #f7f5ee;
|
|
19
|
+
--workspace-card-ink: #141411;
|
|
20
|
+
--workspace-card-muted: #65635d;
|
|
21
|
+
--workspace-card-line: rgba(20, 20, 17, 0.1);
|
|
22
|
+
--workspace-card-stage: #e8e5dc;
|
|
23
|
+
display: grid;
|
|
24
|
+
gap: 2rem;
|
|
25
|
+
min-height: 100vh;
|
|
26
|
+
max-width: none;
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: 3.6rem clamp(2rem, 4vw, 4.5rem) 6rem;
|
|
29
|
+
font-family: var(--openpress-font-body, system-ui, sans-serif);
|
|
30
|
+
color: var(--workspace-ink);
|
|
31
|
+
background:
|
|
32
|
+
linear-gradient(180deg, var(--workspace-bg-soft), var(--workspace-bg) 42rem),
|
|
33
|
+
var(--workspace-bg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.openpress-workspace-gallery__header {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
39
|
+
align-items: end;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
gap: 2.5rem;
|
|
42
|
+
padding: 0 0 1.45rem;
|
|
43
|
+
border-bottom: 1px solid var(--workspace-line);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.openpress-workspace-gallery__headline {
|
|
47
|
+
display: grid;
|
|
48
|
+
gap: 0.75rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.openpress-workspace-gallery__eyebrow {
|
|
52
|
+
margin: 0;
|
|
53
|
+
color: var(--workspace-muted);
|
|
54
|
+
font-family: var(--openpress-font-mono, ui-monospace, monospace);
|
|
55
|
+
font-size: 0.68rem;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
letter-spacing: 0.16em;
|
|
58
|
+
text-transform: uppercase;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.openpress-workspace-gallery__header h1 {
|
|
62
|
+
margin: 0;
|
|
63
|
+
font-family: var(--openpress-font-display, var(--openpress-font-body, system-ui));
|
|
64
|
+
font-size: clamp(2.6rem, 5.4vw, 5.2rem);
|
|
65
|
+
font-weight: 720;
|
|
66
|
+
line-height: 0.94;
|
|
67
|
+
letter-spacing: -0.035em;
|
|
68
|
+
color: var(--workspace-ink);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.openpress-workspace-gallery__count {
|
|
72
|
+
margin: 0;
|
|
73
|
+
display: grid;
|
|
74
|
+
justify-items: end;
|
|
75
|
+
gap: 0.25rem;
|
|
76
|
+
min-width: 4.5rem;
|
|
77
|
+
color: var(--workspace-ink);
|
|
78
|
+
font-family: var(--openpress-font-mono, ui-monospace, monospace);
|
|
79
|
+
line-height: 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.openpress-workspace-gallery__count span {
|
|
83
|
+
color: var(--workspace-ink);
|
|
84
|
+
font-size: 2rem;
|
|
85
|
+
font-weight: 500;
|
|
86
|
+
letter-spacing: -0.04em;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.openpress-workspace-gallery__count small {
|
|
90
|
+
color: var(--workspace-muted);
|
|
91
|
+
font-size: 0.62rem;
|
|
92
|
+
font-weight: 600;
|
|
93
|
+
letter-spacing: 0.14em;
|
|
94
|
+
text-transform: uppercase;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.openpress-workspace-gallery__grid {
|
|
98
|
+
list-style: none;
|
|
99
|
+
margin: 0;
|
|
100
|
+
padding: 0;
|
|
101
|
+
display: grid;
|
|
102
|
+
align-items: start;
|
|
103
|
+
/* Uniform card size — Figma-style. Outer thumb is fixed 4:3, the
|
|
104
|
+
inner page letterboxes to its own geometry. */
|
|
105
|
+
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
|
106
|
+
gap: 1.5rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.openpress-workspace-gallery__item {
|
|
110
|
+
display: flex;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.openpress-workspace-gallery__card {
|
|
114
|
+
appearance: none;
|
|
115
|
+
display: grid;
|
|
116
|
+
grid-template-rows: auto minmax(6.75rem, auto);
|
|
117
|
+
align-self: start;
|
|
118
|
+
width: 100%;
|
|
119
|
+
padding: 0;
|
|
120
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
121
|
+
border-radius: 8px;
|
|
122
|
+
background: var(--workspace-card);
|
|
123
|
+
color: var(--workspace-card-ink);
|
|
124
|
+
text-align: left;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
transition:
|
|
128
|
+
transform 160ms ease,
|
|
129
|
+
box-shadow 160ms ease,
|
|
130
|
+
border-color 160ms ease;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.openpress-workspace-gallery__card:hover,
|
|
134
|
+
.openpress-workspace-gallery__card:focus-visible {
|
|
135
|
+
border-color: rgba(255, 255, 255, 0.28);
|
|
136
|
+
transform: translateY(-2px);
|
|
137
|
+
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34);
|
|
138
|
+
outline: none;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.openpress-workspace-gallery__thumb {
|
|
142
|
+
position: relative;
|
|
143
|
+
display: block;
|
|
144
|
+
width: 100%;
|
|
145
|
+
/* Uniform 4:3 outer slot across every card. The page itself
|
|
146
|
+
letterboxes inside via centered scale, so each Press shows at its
|
|
147
|
+
own true aspect against the gradient background. */
|
|
148
|
+
aspect-ratio: 4 / 3;
|
|
149
|
+
background:
|
|
150
|
+
linear-gradient(
|
|
151
|
+
135deg,
|
|
152
|
+
color-mix(in srgb, var(--workspace-card-ink) 5%, var(--workspace-card-stage)),
|
|
153
|
+
var(--workspace-card-stage)
|
|
154
|
+
);
|
|
155
|
+
border-bottom: 1px solid var(--workspace-card-line);
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.openpress-workspace-gallery__thumb::before {
|
|
160
|
+
content: "";
|
|
161
|
+
position: absolute;
|
|
162
|
+
inset: 0;
|
|
163
|
+
pointer-events: none;
|
|
164
|
+
background-image:
|
|
165
|
+
linear-gradient(rgba(20, 20, 17, 0.05) 1px, transparent 1px),
|
|
166
|
+
linear-gradient(90deg, rgba(20, 20, 17, 0.05) 1px, transparent 1px);
|
|
167
|
+
background-size: 24px 24px;
|
|
168
|
+
opacity: 0.5;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.openpress-workspace-gallery__thumb-stage {
|
|
172
|
+
position: absolute;
|
|
173
|
+
inset: clamp(0.85rem, 6%, 1.45rem);
|
|
174
|
+
display: grid;
|
|
175
|
+
place-items: center;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.openpress-workspace-gallery__thumb-frame {
|
|
179
|
+
position: relative;
|
|
180
|
+
box-shadow:
|
|
181
|
+
0 18px 36px rgba(20, 20, 17, 0.18),
|
|
182
|
+
0 0 0 1px rgba(20, 20, 17, 0.08);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.openpress-workspace-gallery__thumb-stage .openpress-public-page {
|
|
186
|
+
display: block;
|
|
187
|
+
pointer-events: none;
|
|
188
|
+
user-select: none;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.openpress-workspace-gallery__thumb-placeholder {
|
|
192
|
+
position: absolute;
|
|
193
|
+
inset: clamp(0.85rem, 6%, 1.45rem);
|
|
194
|
+
display: grid;
|
|
195
|
+
place-items: center;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.openpress-workspace-gallery__thumb-skel {
|
|
199
|
+
display: block;
|
|
200
|
+
width: 70%;
|
|
201
|
+
height: 70%;
|
|
202
|
+
background:
|
|
203
|
+
repeating-linear-gradient(
|
|
204
|
+
135deg,
|
|
205
|
+
rgba(20, 20, 17, 0.04) 0 6px,
|
|
206
|
+
transparent 6px 14px
|
|
207
|
+
),
|
|
208
|
+
#fff;
|
|
209
|
+
border: 1px solid var(--workspace-card-line);
|
|
210
|
+
border-radius: 3px;
|
|
211
|
+
box-shadow: 0 14px 28px rgba(20, 20, 17, 0.14);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.openpress-workspace-gallery__thumb-placeholder[data-state="loading"] .openpress-workspace-gallery__thumb-skel {
|
|
215
|
+
animation: openpress-gallery-skel-pulse 1.4s ease-in-out infinite;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@keyframes openpress-gallery-skel-pulse {
|
|
219
|
+
0%, 100% { opacity: 1; }
|
|
220
|
+
50% { opacity: 0.55; }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.openpress-workspace-gallery__body {
|
|
224
|
+
display: grid;
|
|
225
|
+
align-content: space-between;
|
|
226
|
+
gap: 1.2rem;
|
|
227
|
+
min-height: 6.75rem;
|
|
228
|
+
padding: 1.1rem 1.22rem 1.15rem;
|
|
229
|
+
background: var(--workspace-card);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.openpress-workspace-gallery__title {
|
|
233
|
+
display: block;
|
|
234
|
+
color: var(--workspace-card-ink);
|
|
235
|
+
font-size: 1rem;
|
|
236
|
+
font-weight: 700;
|
|
237
|
+
line-height: 1.2;
|
|
238
|
+
white-space: nowrap;
|
|
239
|
+
overflow: hidden;
|
|
240
|
+
text-overflow: ellipsis;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.openpress-workspace-gallery__meta {
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: space-between;
|
|
247
|
+
gap: 0.7rem;
|
|
248
|
+
flex-wrap: wrap;
|
|
249
|
+
color: var(--workspace-card-muted);
|
|
250
|
+
font-family: var(--openpress-font-mono, ui-monospace, monospace);
|
|
251
|
+
font-size: 0.66rem;
|
|
252
|
+
letter-spacing: 0.03em;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.openpress-workspace-gallery__slug {
|
|
256
|
+
max-width: 13rem;
|
|
257
|
+
color: color-mix(in srgb, var(--workspace-card-ink) 72%, transparent);
|
|
258
|
+
font-weight: 500;
|
|
259
|
+
overflow: hidden;
|
|
260
|
+
text-overflow: ellipsis;
|
|
261
|
+
text-transform: uppercase;
|
|
262
|
+
white-space: nowrap;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.openpress-workspace-gallery__dot {
|
|
266
|
+
color: color-mix(in srgb, var(--workspace-card-muted) 55%, transparent);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.openpress-workspace-gallery__pages,
|
|
270
|
+
.openpress-workspace-gallery__geom {
|
|
271
|
+
color: var(--workspace-card-muted);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.openpress-workspace-gallery__geom {
|
|
275
|
+
display: inline-flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
min-height: 1.35rem;
|
|
278
|
+
padding: 0 0.48rem;
|
|
279
|
+
border: 1px solid var(--workspace-card-line);
|
|
280
|
+
border-radius: 4px;
|
|
281
|
+
background: rgba(255, 255, 255, 0.36);
|
|
282
|
+
color: color-mix(in srgb, var(--workspace-card-ink) 76%, transparent);
|
|
283
|
+
font-size: 0.62rem;
|
|
284
|
+
white-space: nowrap;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
@media (max-width: 720px) {
|
|
288
|
+
.openpress-workspace-gallery {
|
|
289
|
+
padding: 2.25rem 1rem 4rem;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.openpress-workspace-gallery__header {
|
|
293
|
+
display: grid;
|
|
294
|
+
align-items: start;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.openpress-workspace-gallery__grid {
|
|
298
|
+
grid-template-columns: 1fr;
|
|
299
|
+
}
|
|
300
|
+
}
|
package/src/styles/openpress.css
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
@import url("/openpress/components.css");
|
|
5
5
|
|
|
6
6
|
@import "./openpress/app-shell.css";
|
|
7
|
+
@import "./openpress/workspace-gallery.css";
|
|
7
8
|
@import "./openpress/workbench.css";
|
|
8
9
|
@import "./openpress/workbench-panels.css";
|
|
9
|
-
@import "./openpress/project-
|
|
10
|
+
@import "./openpress/project-preview-panel.css";
|
|
10
11
|
@import "./openpress/media-workspace.css";
|
|
11
12
|
@import "./openpress/reader-runtime.css";
|
|
12
13
|
@import "./openpress/public-viewer.css";
|
package/tsconfig.json
CHANGED
package/vite.config.ts
CHANGED
|
@@ -6,8 +6,10 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
6
6
|
import { defineConfig } from "vite";
|
|
7
7
|
import react from "@vitejs/plugin-react";
|
|
8
8
|
import { loadConfig, publicPdfHref } from "./engine/runtime/config.mjs";
|
|
9
|
+
import { searchSourceText } from "./engine/runtime/source-text-tools.mjs";
|
|
9
10
|
import { handleCommentRequest } from "./engine/react/comment-endpoint.mjs";
|
|
10
11
|
import { handleProjectAssetRequest } from "./engine/react/project-asset-endpoint.mjs";
|
|
12
|
+
import { handleSourceEditRequest } from "./engine/react/source-edit-endpoint.mjs";
|
|
11
13
|
|
|
12
14
|
const frameworkRoot = fileURLToPath(new URL("./", import.meta.url));
|
|
13
15
|
const workspaceRoot = process.env.OPENPRESS_WORKSPACE_ROOT
|
|
@@ -53,6 +55,7 @@ const workspaceDefines = {
|
|
|
53
55
|
|
|
54
56
|
export default defineConfig({
|
|
55
57
|
base: "./",
|
|
58
|
+
cacheDir: path.join(workspaceRoot, ".openpress", "vite-client"),
|
|
56
59
|
plugins: [openpressLocalDeployPlugin(), react()],
|
|
57
60
|
define: workspaceDefines,
|
|
58
61
|
resolve: {
|
|
@@ -68,6 +71,17 @@ export default defineConfig({
|
|
|
68
71
|
...workspaceAliases,
|
|
69
72
|
},
|
|
70
73
|
},
|
|
74
|
+
optimizeDeps: {
|
|
75
|
+
include: [
|
|
76
|
+
"@mdx-js/react",
|
|
77
|
+
"lucide-react",
|
|
78
|
+
"react",
|
|
79
|
+
"react-dom",
|
|
80
|
+
"react-dom/client",
|
|
81
|
+
"react/jsx-dev-runtime",
|
|
82
|
+
"react/jsx-runtime",
|
|
83
|
+
],
|
|
84
|
+
},
|
|
71
85
|
build: {
|
|
72
86
|
outDir: outputDir,
|
|
73
87
|
emptyOutDir: true,
|
|
@@ -108,6 +122,12 @@ function openpressLocalDeployPlugin() {
|
|
|
108
122
|
server.middlewares.use("/__openpress/status", (req, res) => {
|
|
109
123
|
void handleLocalStatusRequest(req, res);
|
|
110
124
|
});
|
|
125
|
+
server.middlewares.use("/__openpress/search", (req, res) => {
|
|
126
|
+
void handleLocalSearchRequest(req, res);
|
|
127
|
+
});
|
|
128
|
+
server.middlewares.use("/__openpress/source-edit", (req, res) => {
|
|
129
|
+
void handleSourceEditRequest(req, res, { root: workspaceRoot });
|
|
130
|
+
});
|
|
111
131
|
server.middlewares.use("/__openpress/deploy", (req, res) => {
|
|
112
132
|
void handleLocalDeployRequest(req, res);
|
|
113
133
|
});
|
|
@@ -267,6 +287,32 @@ async function handleLocalStatusRequest(req: IncomingMessage, res: ServerRespons
|
|
|
267
287
|
});
|
|
268
288
|
}
|
|
269
289
|
|
|
290
|
+
async function handleLocalSearchRequest(req: IncomingMessage, res: ServerResponse) {
|
|
291
|
+
if (req.method !== "GET") {
|
|
292
|
+
writeJson(res, 405, { ok: false, message: "Search endpoint requires GET." });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const requestUrl = new URL(req.url ?? "/", "http://localhost");
|
|
297
|
+
const query = (requestUrl.searchParams.get("q") ?? "").trim();
|
|
298
|
+
if (!query) {
|
|
299
|
+
writeJson(res, 400, { ok: false, message: "Search query is required." });
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const report = await searchSourceText({
|
|
305
|
+
config: openpressConfig,
|
|
306
|
+
query,
|
|
307
|
+
scope: searchScopeFrom(requestUrl.searchParams),
|
|
308
|
+
caseSensitive: requestUrl.searchParams.get("caseSensitive") === "true",
|
|
309
|
+
});
|
|
310
|
+
writeJson(res, 200, { ok: true, ...report });
|
|
311
|
+
} catch (error) {
|
|
312
|
+
writeJson(res, 500, { ok: false, message: error instanceof Error ? error.message : String(error) });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
270
316
|
async function handleLocalDeployRequest(req: IncomingMessage, res: ServerResponse) {
|
|
271
317
|
if (req.method !== "POST") {
|
|
272
318
|
writeJson(res, 405, { ok: false, message: "Deploy endpoint requires POST." });
|
|
@@ -368,6 +414,10 @@ function localDeploySetupMessage() {
|
|
|
368
414
|
return `Deployment adapter \`${openpressConfig.deploy.adapter}\` is not configured.`;
|
|
369
415
|
}
|
|
370
416
|
|
|
417
|
+
function searchScopeFrom(searchParams: URLSearchParams) {
|
|
418
|
+
return searchParams.get("scope") === "all" ? "all" : "content";
|
|
419
|
+
}
|
|
420
|
+
|
|
371
421
|
async function fileExists(filePath: string) {
|
|
372
422
|
try {
|
|
373
423
|
await fs.access(filePath);
|
package/engine/commands/init.mjs
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { initWorkspace, listStylePackSkills } from "../init.mjs";
|
|
2
|
-
import { formatDisplayPath, parseInitOptions } from "./_shared.mjs";
|
|
3
|
-
|
|
4
|
-
export const needsWorkspace = false;
|
|
5
|
-
|
|
6
|
-
export async function run({ argv }) {
|
|
7
|
-
const options = parseInitOptions(argv);
|
|
8
|
-
if (!options.target) {
|
|
9
|
-
console.error("openpress init: target path is required");
|
|
10
|
-
console.error("Usage: openpress init <target> [--skill <name>] [--force]");
|
|
11
|
-
const available = await listStylePackSkills();
|
|
12
|
-
if (available.length) console.error(`Style packs available: ${available.join(", ")}`);
|
|
13
|
-
return 1;
|
|
14
|
-
}
|
|
15
|
-
const result = await initWorkspace(options);
|
|
16
|
-
const displayPath = formatDisplayPath(result.targetPath);
|
|
17
|
-
console.log(`OpenPress init: created ${displayPath} from style pack "${result.skill}".`);
|
|
18
|
-
console.log("Next steps:");
|
|
19
|
-
console.log(` cd ${displayPath}`);
|
|
20
|
-
console.log(" # 填入 openpress.config.mjs 的 title / subtitle / organization");
|
|
21
|
-
console.log(" # 改 document/index.tsx 與 document/chapters/**/*.mdx 為實際內容");
|
|
22
|
-
console.log(" node engine/cli.mjs validate");
|
|
23
|
-
return 0;
|
|
24
|
-
}
|
package/engine/init.mjs
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const SELF_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const ENGINE_ROOT = path.resolve(SELF_DIR, "..");
|
|
7
|
-
const SKILLS_DIR = path.join(ENGINE_ROOT, "skills");
|
|
8
|
-
|
|
9
|
-
const DEFAULT_SKILL = "editorial-monograph";
|
|
10
|
-
|
|
11
|
-
export async function initWorkspace({ target, skill = DEFAULT_SKILL, force = false }) {
|
|
12
|
-
if (!target) throw new Error("openpress init: target path is required");
|
|
13
|
-
const targetPath = path.resolve(target);
|
|
14
|
-
|
|
15
|
-
const starterPath = path.join(SKILLS_DIR, skill, "starter");
|
|
16
|
-
try {
|
|
17
|
-
const stat = await fs.stat(starterPath);
|
|
18
|
-
if (!stat.isDirectory()) {
|
|
19
|
-
throw new Error(`openpress init: skill "${skill}" has no starter/ directory at ${starterPath}`);
|
|
20
|
-
}
|
|
21
|
-
} catch (error) {
|
|
22
|
-
if (error?.code === "ENOENT") {
|
|
23
|
-
const available = await listStylePackSkills();
|
|
24
|
-
throw new Error(
|
|
25
|
-
`openpress init: skill "${skill}" not found or has no starter. ` +
|
|
26
|
-
`Available style packs: ${available.join(", ") || "(none)"}`,
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
throw error;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!force) {
|
|
33
|
-
try {
|
|
34
|
-
const stat = await fs.stat(targetPath);
|
|
35
|
-
if (stat.isDirectory()) {
|
|
36
|
-
const entries = await fs.readdir(targetPath);
|
|
37
|
-
if (entries.length > 0) {
|
|
38
|
-
throw new Error(`openpress init: target ${targetPath} exists and is not empty. Pass --force to overwrite.`);
|
|
39
|
-
}
|
|
40
|
-
} else {
|
|
41
|
-
throw new Error(`openpress init: target ${targetPath} exists and is not a directory.`);
|
|
42
|
-
}
|
|
43
|
-
} catch (error) {
|
|
44
|
-
if (error?.code !== "ENOENT") throw error;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
await fs.mkdir(targetPath, { recursive: true });
|
|
49
|
-
await copyDirectory(starterPath, targetPath);
|
|
50
|
-
|
|
51
|
-
return { targetPath, skill };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function listStylePackSkills() {
|
|
55
|
-
try {
|
|
56
|
-
const entries = await fs.readdir(SKILLS_DIR, { withFileTypes: true });
|
|
57
|
-
const names = [];
|
|
58
|
-
for (const entry of entries) {
|
|
59
|
-
if (!entry.isDirectory()) continue;
|
|
60
|
-
const starter = path.join(SKILLS_DIR, entry.name, "starter");
|
|
61
|
-
try {
|
|
62
|
-
const stat = await fs.stat(starter);
|
|
63
|
-
if (stat.isDirectory()) names.push(entry.name);
|
|
64
|
-
} catch {
|
|
65
|
-
// skill without starter/ is not a style pack — skip
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return names.sort();
|
|
69
|
-
} catch (error) {
|
|
70
|
-
if (error?.code === "ENOENT") return [];
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function copyDirectory(source, destination) {
|
|
76
|
-
await fs.mkdir(destination, { recursive: true });
|
|
77
|
-
for (const entry of await fs.readdir(source, { withFileTypes: true })) {
|
|
78
|
-
if (entry.name === ".DS_Store") continue;
|
|
79
|
-
const sourcePath = path.join(source, entry.name);
|
|
80
|
-
const destPath = path.join(destination, entry.name);
|
|
81
|
-
if (entry.isDirectory()) {
|
|
82
|
-
await copyDirectory(sourcePath, destPath);
|
|
83
|
-
} else if (entry.isFile()) {
|
|
84
|
-
await fs.copyFile(sourcePath, destPath);
|
|
85
|
-
} else if (entry.isSymbolicLink()) {
|
|
86
|
-
const link = await fs.readlink(sourcePath);
|
|
87
|
-
await fs.symlink(link, destPath);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
package/src/openpress/App.tsx
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { Renderer } from "./renderer";
|
|
3
|
-
import { isLocalWorkspaceHost } from "./runtimeMode";
|
|
4
|
-
import type { DeploymentInfo, ReaderDocument } from "./types";
|
|
5
|
-
|
|
6
|
-
type LoadState =
|
|
7
|
-
| { status: "loading" }
|
|
8
|
-
| {
|
|
9
|
-
status: "ready";
|
|
10
|
-
document: ReaderDocument;
|
|
11
|
-
deploymentInfo: DeploymentInfo;
|
|
12
|
-
}
|
|
13
|
-
| { status: "error"; message: string };
|
|
14
|
-
|
|
15
|
-
interface DeployConfig {
|
|
16
|
-
pdf?: string;
|
|
17
|
-
deployed_at?: string;
|
|
18
|
-
public_url?: string;
|
|
19
|
-
dirty?: boolean;
|
|
20
|
-
deploy_configured?: boolean;
|
|
21
|
-
deploy_adapter?: string;
|
|
22
|
-
deploy_source?: string;
|
|
23
|
-
deploy_project_name?: string | null;
|
|
24
|
-
deploy_setup_message?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const offlineDeploymentInfo: DeploymentInfo = { online: false };
|
|
28
|
-
|
|
29
|
-
function LoadingScreen() {
|
|
30
|
-
return (
|
|
31
|
-
<div className="openpress-loading-screen" aria-label="載入中" role="status">
|
|
32
|
-
<div className="openpress-loading-screen__inner">
|
|
33
|
-
<div className="openpress-loading-dots" aria-hidden="true">
|
|
34
|
-
<span /><span /><span />
|
|
35
|
-
</div>
|
|
36
|
-
<span className="openpress-loading-screen__label">載入文件</span>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function App() {
|
|
43
|
-
const [state, setState] = useState<LoadState>({ status: "loading" });
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
let cancelled = false;
|
|
47
|
-
|
|
48
|
-
async function loadDocument() {
|
|
49
|
-
try {
|
|
50
|
-
const [response, deploymentInfo] = await Promise.all([
|
|
51
|
-
fetch("/openpress/document.json", { cache: "no-store" }),
|
|
52
|
-
loadDeploymentInfo(),
|
|
53
|
-
]);
|
|
54
|
-
if (!response.ok) {
|
|
55
|
-
throw new Error(`Unable to load /openpress/document.json (${response.status})`);
|
|
56
|
-
}
|
|
57
|
-
const document = (await response.json()) as ReaderDocument;
|
|
58
|
-
if (!cancelled) {
|
|
59
|
-
setState({ status: "ready", document, deploymentInfo });
|
|
60
|
-
}
|
|
61
|
-
} catch (error) {
|
|
62
|
-
if (!cancelled) {
|
|
63
|
-
setState({
|
|
64
|
-
status: "error",
|
|
65
|
-
message: error instanceof Error ? error.message : "Unable to load OpenPress document.",
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
void loadDocument();
|
|
72
|
-
return () => {
|
|
73
|
-
cancelled = true;
|
|
74
|
-
};
|
|
75
|
-
}, []);
|
|
76
|
-
|
|
77
|
-
if (state.status === "loading") return <LoadingScreen />;
|
|
78
|
-
|
|
79
|
-
if (state.status === "error") {
|
|
80
|
-
return <div className="openpress-load-state openpress-load-state--error">{state.message}</div>;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<Renderer
|
|
85
|
-
document={state.document}
|
|
86
|
-
deploymentInfo={state.deploymentInfo}
|
|
87
|
-
/>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function loadDeploymentInfo(): Promise<DeploymentInfo> {
|
|
92
|
-
if (typeof window !== "undefined" && isLocalWorkspaceHost(window.location.hostname)) {
|
|
93
|
-
const localInfo = await loadDeploymentInfoFrom("/__openpress/status");
|
|
94
|
-
if (localInfo) return localInfo;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return (await loadDeploymentInfoFrom("/openpress/deploy.json")) ?? offlineDeploymentInfo;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async function loadDeploymentInfoFrom(path: string): Promise<DeploymentInfo | null> {
|
|
101
|
-
try {
|
|
102
|
-
const response = await fetch(path, { cache: "no-store" });
|
|
103
|
-
if (!response.ok) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
const config = (await response.json()) as DeployConfig;
|
|
107
|
-
return deploymentConfigToInfo(config);
|
|
108
|
-
} catch {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function deploymentConfigToInfo(config: DeployConfig): DeploymentInfo {
|
|
114
|
-
const configured = config.deploy_configured !== false;
|
|
115
|
-
return {
|
|
116
|
-
online: configured && Boolean(config.deployed_at || config.public_url),
|
|
117
|
-
deployedAt: config.deployed_at,
|
|
118
|
-
pdf: typeof config.pdf === "string" ? config.pdf : undefined,
|
|
119
|
-
publicUrl: typeof config.public_url === "string" ? config.public_url : undefined,
|
|
120
|
-
dirty: config.dirty === true,
|
|
121
|
-
configured,
|
|
122
|
-
adapter: typeof config.deploy_adapter === "string" ? config.deploy_adapter : undefined,
|
|
123
|
-
source: typeof config.deploy_source === "string" ? config.deploy_source : undefined,
|
|
124
|
-
projectName: typeof config.deploy_project_name === "string" ? config.deploy_project_name : undefined,
|
|
125
|
-
setupMessage: typeof config.deploy_setup_message === "string" ? config.deploy_setup_message : undefined,
|
|
126
|
-
};
|
|
127
|
-
}
|