@open-press/cli 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 +29 -13
- package/dist/cli.js +44 -195
- package/package.json +4 -5
- package/template/core/AGENTS.md +18 -14
- package/template/core/CHANGELOG.md +57 -9
- package/template/core/README.md +6 -3
- package/template/core/engine/cli.mjs +8 -8
- package/template/core/engine/commands/_shared.mjs +37 -15
- package/template/core/engine/commands/dev.mjs +2 -2
- package/template/core/engine/commands/image.mjs +29 -0
- package/template/core/engine/commands/skills-sync.mjs +71 -0
- package/template/core/engine/commands/typecheck.mjs +63 -1
- package/template/core/engine/commands/upgrade.mjs +3 -3
- package/template/core/engine/document-export.mjs +1 -1
- package/template/core/engine/output/chrome-pdf.mjs +110 -3
- package/template/core/engine/output/static-server.mjs +87 -9
- package/template/core/engine/react/comment-endpoint.mjs +13 -39
- package/template/core/engine/react/comment-marker.mjs +43 -19
- package/template/core/engine/react/document-entry.mjs +46 -28
- package/template/core/engine/react/document-export.mjs +328 -164
- package/template/core/engine/react/http-json.mjs +24 -0
- package/template/core/engine/react/mdx-compile.mjs +126 -3
- package/template/core/engine/react/measurement-css.mjs +114 -1
- package/template/core/engine/react/object-entities.mjs +204 -0
- package/template/core/engine/react/pagination/allocator.mjs +48 -3
- package/template/core/engine/react/pagination.mjs +1 -1
- package/template/core/engine/react/pipeline/allocate.mjs +41 -72
- package/template/core/engine/react/pipeline/frame-measurement.mjs +6 -0
- package/template/core/engine/react/press-tree-inspection.mjs +172 -0
- package/template/core/engine/react/project-asset-endpoint.mjs +6 -24
- package/template/core/engine/react/source-edit-endpoint.d.mts +10 -0
- package/template/core/engine/react/source-edit-endpoint.mjs +75 -0
- package/template/core/engine/react/sources/mdx-resolver.mjs +13 -15
- package/template/core/engine/react/style-discovery.mjs +23 -8
- package/template/core/engine/runtime/config.d.mts +8 -0
- package/template/core/engine/runtime/config.mjs +57 -60
- package/template/core/engine/runtime/file-utils.mjs +9 -1
- package/template/core/engine/runtime/file-walk.mjs +22 -0
- package/template/core/engine/runtime/inspection.mjs +1 -20
- package/template/core/engine/runtime/page-geometry.mjs +131 -0
- package/template/core/engine/runtime/path-utils.mjs +20 -0
- package/template/core/engine/runtime/source-text-tools.d.mts +102 -0
- package/template/core/engine/runtime/source-text-tools.mjs +551 -16
- package/template/core/engine/runtime/source-workspace.mjs +16 -34
- package/template/core/engine/runtime/validation.mjs +19 -10
- package/template/core/openpress.config.mjs +3 -7
- package/template/core/package.json +3 -5
- package/template/core/src/main.tsx +2 -2
- package/template/core/src/openpress/app/OpenPressApp.tsx +296 -0
- package/template/core/src/openpress/{renderer.tsx → app/OpenPressRuntime.tsx} +20 -9
- package/template/core/src/openpress/app/WorkspaceGalleryPage.tsx +219 -0
- package/template/core/src/openpress/app/index.ts +2 -0
- package/template/core/src/openpress/core/Frame.tsx +26 -15
- package/template/core/src/openpress/core/FrameContext.tsx +10 -3
- package/template/core/src/openpress/core/MdxArea.tsx +11 -12
- package/template/core/src/openpress/core/Press.tsx +25 -4
- package/template/core/src/openpress/core/Workspace.tsx +36 -0
- package/template/core/src/openpress/core/cn.ts +4 -0
- package/template/core/src/openpress/core/index.tsx +11 -3
- package/template/core/src/openpress/core/primitives.tsx +74 -6
- package/template/core/src/openpress/core/types.ts +94 -41
- package/template/core/src/openpress/core/useSource.ts +1 -1
- package/template/core/src/openpress/{anchorMap.ts → document-model/anchorMapModel.ts} +1 -1
- package/template/core/src/openpress/{indexes.ts → document-model/documentIndexes.ts} +1 -1
- package/template/core/src/openpress/{types.ts → document-model/documentTypes.ts} +51 -0
- package/template/core/src/openpress/document-model/index.ts +7 -0
- package/template/core/src/openpress/document-model/objectEntityModel.ts +55 -0
- package/template/core/src/openpress/{projectIdentity.ts → document-model/projectIdentityModel.ts} +1 -1
- package/template/core/src/openpress/{reactDocumentMetadata.ts → document-model/reactDocumentMetadataModel.ts} +1 -1
- package/template/core/src/openpress/document-model/workspaceManifestModel.ts +57 -0
- package/template/core/src/openpress/manuscript/index.tsx +49 -7
- package/template/core/src/openpress/mdx/index.ts +15 -7
- package/template/core/src/openpress/reader/PageThumbnailsPanel.tsx +168 -0
- package/template/core/src/openpress/{publicPage.tsx → reader/PublicReaderPage.tsx} +31 -51
- package/template/core/src/openpress/{workbenchPanels.tsx → reader/ReaderNavigationPanel.tsx} +6 -5
- package/template/core/src/openpress/reader/index.ts +11 -0
- package/template/core/src/openpress/reader/pageViewportScaleModel.ts +73 -0
- package/template/core/src/openpress/reader/readerTypes.ts +4 -0
- package/template/core/src/openpress/reader/usePageViewportScale.ts +119 -0
- package/template/core/src/openpress/reader/usePanelState.ts +56 -0
- package/template/core/src/openpress/reader/useReaderHashSync.ts +61 -0
- package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +48 -0
- package/template/core/src/openpress/reader/useReaderRuntime.ts +146 -0
- package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +64 -0
- package/template/core/src/openpress/shared/Panel.tsx +77 -0
- package/template/core/src/openpress/shared/index.ts +4 -0
- package/template/core/src/openpress/shared/numberUtils.ts +3 -0
- package/template/core/src/openpress/{runtimeMode.ts → shared/runtimeMode.ts} +0 -11
- package/template/core/src/openpress/workbench/Workbench.tsx +506 -0
- package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +157 -0
- package/template/core/src/openpress/workbench/actions/ExportImageControl.tsx +96 -0
- package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +182 -0
- package/template/core/src/openpress/workbench/actions/SearchControl.tsx +345 -0
- package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +112 -0
- package/template/core/src/openpress/workbench/actions/index.ts +6 -0
- package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +136 -0
- package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +72 -0
- package/template/core/src/openpress/workbench/dialog/index.ts +1 -0
- package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +127 -0
- package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +207 -0
- package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +9 -0
- package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +34 -0
- package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +525 -0
- package/template/core/src/openpress/workbench/document/index.ts +10 -0
- package/template/core/src/openpress/workbench/index.ts +2 -0
- package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +459 -0
- package/template/core/src/openpress/workbench/inspector/index.ts +5 -0
- package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +125 -0
- package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +160 -0
- package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +408 -0
- package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +254 -0
- package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +41 -0
- package/template/core/src/openpress/workbench/mentions/index.ts +2 -0
- package/template/core/src/openpress/{composerMentions.ts → workbench/mentions/useComposerMentions.ts} +1 -4
- package/template/core/src/openpress/workbench/panels/Panel.tsx +1 -0
- package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +80 -0
- package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +29 -0
- package/template/core/src/openpress/workbench/panels/index.ts +3 -0
- package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +525 -0
- package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +35 -0
- package/template/core/src/openpress/workbench/project/index.ts +2 -0
- package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +11 -0
- package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +167 -0
- package/template/core/src/openpress/workbench/shell/index.ts +1 -0
- package/template/core/src/openpress/workbench/workbenchFormatters.ts +120 -0
- package/template/core/src/openpress/workbench/workbenchTypes.ts +35 -0
- package/template/core/src/styles/openpress/print-route.css +0 -2
- package/template/core/src/styles/openpress/{project-workspace.css → project-preview-panel.css} +13 -407
- package/template/core/src/styles/openpress/public-viewer.css +25 -320
- package/template/core/src/styles/openpress/reader-runtime.css +252 -55
- package/template/core/src/styles/openpress/responsive.css +145 -270
- package/template/core/src/styles/openpress/workbench-panels.css +327 -178
- package/template/core/src/styles/openpress/workbench.css +986 -451
- package/template/core/src/styles/openpress/workspace-gallery.css +300 -0
- package/template/core/src/styles/openpress.css +2 -1
- package/template/core/tsconfig.json +1 -1
- package/template/core/vite.config.ts +50 -0
- package/template/core/engine/commands/init.mjs +0 -24
- package/template/core/engine/init.mjs +0 -90
- package/template/core/src/openpress/App.tsx +0 -127
- package/template/core/src/openpress/inspector.ts +0 -282
- package/template/core/src/openpress/projectWorkspace.tsx +0 -919
- package/template/core/src/openpress/readerRuntime.ts +0 -230
- package/template/core/src/openpress/workbench.tsx +0 -1265
- package/template/core/src/openpress/workbenchTypes.ts +0 -4
- package/template/packs/academic-paper/document/chapters/01-introduction/content/01-introduction.mdx +0 -35
- package/template/packs/academic-paper/document/chapters/02-methods/content/01-methods.mdx +0 -50
- package/template/packs/academic-paper/document/chapters/03-results-and-discussion/content/01-results.mdx +0 -47
- package/template/packs/academic-paper/document/chapters/04-acknowledgment/content/01-acknowledgment.mdx +0 -26
- package/template/packs/academic-paper/document/chapters/05-references/content/01-references.mdx +0 -32
- package/template/packs/academic-paper/document/components/ChapterOpenerVisual/index.tsx +0 -76
- package/template/packs/academic-paper/document/components/Page.tsx +0 -60
- package/template/packs/academic-paper/document/components/TokenSwatchGrid/index.tsx +0 -46
- package/template/packs/academic-paper/document/components/TokenSwatchGrid/style.css +0 -63
- package/template/packs/academic-paper/document/components/TypeSpecimen/index.tsx +0 -38
- package/template/packs/academic-paper/document/components/TypeSpecimen/style.css +0 -111
- package/template/packs/academic-paper/document/design.md +0 -279
- package/template/packs/academic-paper/document/index.tsx +0 -123
- package/template/packs/academic-paper/document/media/README.md +0 -13
- package/template/packs/academic-paper/document/media/figure-placeholder.svg +0 -9
- package/template/packs/academic-paper/document/openpress.config.mjs +0 -26
- package/template/packs/academic-paper/document/theme/README.md +0 -11
- package/template/packs/academic-paper/document/theme/base/page-contract.css +0 -522
- package/template/packs/academic-paper/document/theme/base/print.css +0 -93
- package/template/packs/academic-paper/document/theme/base/typography.css +0 -333
- package/template/packs/academic-paper/document/theme/fonts.css +0 -3
- package/template/packs/academic-paper/document/theme/page-surfaces/back-cover.css +0 -43
- package/template/packs/academic-paper/document/theme/page-surfaces/chapter-opener.css +0 -205
- package/template/packs/academic-paper/document/theme/page-surfaces/cover.css +0 -294
- package/template/packs/academic-paper/document/theme/page-surfaces/toc.css +0 -149
- package/template/packs/academic-paper/document/theme/patterns/_chart-frame.css +0 -49
- package/template/packs/academic-paper/document/theme/patterns/figure-grid.css +0 -68
- package/template/packs/academic-paper/document/theme/patterns/table-utilities.css +0 -66
- package/template/packs/academic-paper/document/theme/shell/reader-controls.css +0 -761
- package/template/packs/academic-paper/document/theme/tokens.css +0 -80
- package/template/packs/academic-paper/openpress.config.mjs +0 -5
- package/template/packs/claude-document/document/chapters/01-document-shape/content/01-document-shape.mdx +0 -51
- package/template/packs/claude-document/document/chapters/02-review-loop/content/01-review-loop.mdx +0 -31
- package/template/packs/claude-document/document/components/ChapterOpenerVisual.tsx +0 -96
- package/template/packs/claude-document/document/components/Page.tsx +0 -37
- package/template/packs/claude-document/document/design.md +0 -142
- package/template/packs/claude-document/document/index.tsx +0 -94
- package/template/packs/claude-document/document/media/README.md +0 -13
- package/template/packs/claude-document/document/openpress.config.mjs +0 -26
- package/template/packs/claude-document/document/theme/README.md +0 -15
- package/template/packs/claude-document/document/theme/base/page-contract.css +0 -525
- package/template/packs/claude-document/document/theme/base/print.css +0 -93
- package/template/packs/claude-document/document/theme/base/typography.css +0 -612
- package/template/packs/claude-document/document/theme/fonts.css +0 -4
- package/template/packs/claude-document/document/theme/page-surfaces/back-cover.css +0 -72
- package/template/packs/claude-document/document/theme/page-surfaces/chapter-opener.css +0 -236
- package/template/packs/claude-document/document/theme/page-surfaces/cover.css +0 -309
- package/template/packs/claude-document/document/theme/page-surfaces/toc.css +0 -225
- package/template/packs/claude-document/document/theme/patterns/_chart-frame.css +0 -53
- package/template/packs/claude-document/document/theme/patterns/figure-grid.css +0 -68
- package/template/packs/claude-document/document/theme/patterns/table-utilities.css +0 -66
- package/template/packs/claude-document/document/theme/shell/reader-controls.css +0 -789
- package/template/packs/claude-document/document/theme/tokens.css +0 -89
- package/template/packs/claude-document/openpress.config.mjs +0 -5
- package/template/packs/editorial-monograph/document/chapters/01-product-and-use-cases/content/01-product-and-use-cases.mdx +0 -31
- package/template/packs/editorial-monograph/document/chapters/02-workflow/content/01-workflow.mdx +0 -89
- package/template/packs/editorial-monograph/document/chapters/03-agent-skills-contributors/content/01-agent-skills-contributors.mdx +0 -51
- package/template/packs/editorial-monograph/document/chapters/04-validation-deploy/content/01-validation-deploy.mdx +0 -39
- package/template/packs/editorial-monograph/document/components/ChapterOpenerVisual/index.tsx +0 -76
- package/template/packs/editorial-monograph/document/components/Page.tsx +0 -37
- package/template/packs/editorial-monograph/document/components/TokenSwatchGrid/index.tsx +0 -46
- package/template/packs/editorial-monograph/document/components/TokenSwatchGrid/style.css +0 -63
- package/template/packs/editorial-monograph/document/components/TypeSpecimen/index.tsx +0 -38
- package/template/packs/editorial-monograph/document/components/TypeSpecimen/style.css +0 -111
- package/template/packs/editorial-monograph/document/design.md +0 -279
- package/template/packs/editorial-monograph/document/index.tsx +0 -97
- package/template/packs/editorial-monograph/document/media/README.md +0 -13
- package/template/packs/editorial-monograph/document/openpress.config.mjs +0 -26
- package/template/packs/editorial-monograph/document/theme/README.md +0 -11
- package/template/packs/editorial-monograph/document/theme/base/page-contract.css +0 -505
- package/template/packs/editorial-monograph/document/theme/base/print.css +0 -93
- package/template/packs/editorial-monograph/document/theme/base/typography.css +0 -336
- package/template/packs/editorial-monograph/document/theme/fonts.css +0 -3
- package/template/packs/editorial-monograph/document/theme/page-surfaces/back-cover.css +0 -43
- package/template/packs/editorial-monograph/document/theme/page-surfaces/chapter-opener.css +0 -205
- package/template/packs/editorial-monograph/document/theme/page-surfaces/cover.css +0 -147
- package/template/packs/editorial-monograph/document/theme/page-surfaces/toc.css +0 -149
- package/template/packs/editorial-monograph/document/theme/patterns/_chart-frame.css +0 -49
- package/template/packs/editorial-monograph/document/theme/patterns/figure-grid.css +0 -68
- package/template/packs/editorial-monograph/document/theme/patterns/table-utilities.css +0 -66
- package/template/packs/editorial-monograph/document/theme/shell/reader-controls.css +0 -761
- package/template/packs/editorial-monograph/document/theme/tokens.css +0 -80
- package/template/packs/editorial-monograph/openpress.config.mjs +0 -5
- /package/template/core/src/openpress/{readerPageRegistry.ts → reader/readerPageRegistry.ts} +0 -0
- /package/template/core/src/openpress/{pageRoute.ts → reader/readerPageRoute.ts} +0 -0
- /package/template/core/src/openpress/{readerScroll.ts → reader/readerScroll.ts} +0 -0
- /package/template/core/src/openpress/{readerState.ts → reader/readerStateModel.ts} +0 -0
- /package/template/core/src/openpress/{frameScheduler.ts → shared/frameScheduler.ts} +0 -0
- /package/template/core/src/openpress/{projectSources.ts → workbench/project/projectSourceModel.ts} +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type CSSProperties, type KeyboardEvent } from "react";
|
|
2
|
+
import type { HtmlPageBlock, ReaderDocument, WorkspaceManifest, WorkspaceManifestPress } from "../document-model";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
manifest: WorkspaceManifest;
|
|
6
|
+
// Called when the reader navigates into a specific Press. The host
|
|
7
|
+
// is responsible for routing (history.pushState, hash, etc.); the
|
|
8
|
+
// gallery just emits the chosen slug.
|
|
9
|
+
onSelectPress: (press: WorkspaceManifestPress) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Reader landing page for multi-Press workspaces. Shows a Figma-style
|
|
13
|
+
// uniform-grid card per Press; each card lazily loads that Press's
|
|
14
|
+
// document.json and renders the first page as a thumbnail preview.
|
|
15
|
+
// Single-Press workspaces skip the gallery entirely.
|
|
16
|
+
export function WorkspaceGalleryPage({ manifest, onSelectPress }: Props) {
|
|
17
|
+
const heading = manifest.name ?? "Workspace";
|
|
18
|
+
const pressCount = String(manifest.presses.length).padStart(2, "0");
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<main className="openpress-workspace-gallery" aria-labelledby="workspace-gallery-heading">
|
|
22
|
+
<header className="openpress-workspace-gallery__header">
|
|
23
|
+
<div className="openpress-workspace-gallery__headline">
|
|
24
|
+
<p className="openpress-workspace-gallery__eyebrow">Workspace</p>
|
|
25
|
+
<h1 id="workspace-gallery-heading">{heading}</h1>
|
|
26
|
+
</div>
|
|
27
|
+
<p className="openpress-workspace-gallery__count">
|
|
28
|
+
<span>{pressCount}</span>
|
|
29
|
+
<small>{manifest.presses.length === 1 ? "document" : "documents"}</small>
|
|
30
|
+
</p>
|
|
31
|
+
</header>
|
|
32
|
+
|
|
33
|
+
<ul className="openpress-workspace-gallery__grid" role="list">
|
|
34
|
+
{manifest.presses.map((press) => (
|
|
35
|
+
<li key={press.slug || "root"} className="openpress-workspace-gallery__item">
|
|
36
|
+
<PressCard press={press} onSelect={() => onSelectPress(press)} />
|
|
37
|
+
</li>
|
|
38
|
+
))}
|
|
39
|
+
</ul>
|
|
40
|
+
</main>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Card is a div+role=button (not <button>) so it can contain the
|
|
45
|
+
// rendered page HTML — buttons may only hold phrasing content, and
|
|
46
|
+
// page HTML is block-level.
|
|
47
|
+
function PressCard({ press, onSelect }: { press: WorkspaceManifestPress; onSelect: () => void }) {
|
|
48
|
+
const handleKey = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
49
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
onSelect();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
role="button"
|
|
58
|
+
tabIndex={0}
|
|
59
|
+
className="openpress-workspace-gallery__card"
|
|
60
|
+
onClick={onSelect}
|
|
61
|
+
onKeyDown={handleKey}
|
|
62
|
+
aria-label={`Open ${press.title}`}
|
|
63
|
+
>
|
|
64
|
+
<PressThumbnail press={press} />
|
|
65
|
+
<div className="openpress-workspace-gallery__body">
|
|
66
|
+
<div className="openpress-workspace-gallery__title">{press.title}</div>
|
|
67
|
+
<div className="openpress-workspace-gallery__meta">
|
|
68
|
+
{press.slug ? <span className="openpress-workspace-gallery__slug">{press.slug}</span> : null}
|
|
69
|
+
{press.page?.pageLabel ? (
|
|
70
|
+
<span className="openpress-workspace-gallery__geom">{press.page.pageLabel}</span>
|
|
71
|
+
) : null}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function PressThumbnail({ press }: { press: WorkspaceManifestPress }) {
|
|
79
|
+
const [state, setState] = useState<ThumbnailState>({ status: "loading" });
|
|
80
|
+
|
|
81
|
+
// Lazy-load each Press's document.json so the gallery doesn't block
|
|
82
|
+
// on a network waterfall when there are many Press. Errors degrade
|
|
83
|
+
// to the geometry-only placeholder used by the loading state.
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
let cancelled = false;
|
|
86
|
+
fetchFirstPage(press.documentUrl).then((page) => {
|
|
87
|
+
if (cancelled) return;
|
|
88
|
+
setState(page ? { status: "ready", page } : { status: "error" });
|
|
89
|
+
}).catch(() => {
|
|
90
|
+
if (!cancelled) setState({ status: "error" });
|
|
91
|
+
});
|
|
92
|
+
return () => { cancelled = true; };
|
|
93
|
+
}, [press.documentUrl]);
|
|
94
|
+
|
|
95
|
+
// Outer card is uniform 4:3 (set in CSS). The page itself letterboxes
|
|
96
|
+
// inside via centered scale, so A4 portrait renders tall-and-narrow,
|
|
97
|
+
// social square renders centered, 16:9 slide stretches edge-to-edge.
|
|
98
|
+
return (
|
|
99
|
+
<div className="openpress-workspace-gallery__thumb" aria-hidden="true">
|
|
100
|
+
{state.status === "ready" ? (
|
|
101
|
+
<PageMiniature page={state.page} press={press} />
|
|
102
|
+
) : (
|
|
103
|
+
<div className="openpress-workspace-gallery__thumb-placeholder" data-state={state.status}>
|
|
104
|
+
<div className="openpress-workspace-gallery__thumb-skel" style={skelAspectStyle(press)} />
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function skelAspectStyle(press: WorkspaceManifestPress): CSSProperties {
|
|
112
|
+
const w = parsePxLength(press.page?.pageWidth);
|
|
113
|
+
const h = parsePxLength(press.page?.pageHeight);
|
|
114
|
+
if (w && h) return { aspectRatio: `${w} / ${h}`, height: "75%" };
|
|
115
|
+
return { aspectRatio: "1 / 1.414", height: "75%" };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function PageMiniature({ page, press }: { page: HtmlPageBlock; press: WorkspaceManifestPress }) {
|
|
119
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
120
|
+
const [scale, setScale] = useState<number | null>(null);
|
|
121
|
+
const pageWidthPx = parsePxLength(press.page?.pageWidth) ?? 1080;
|
|
122
|
+
const pageHeightPx = parsePxLength(press.page?.pageHeight) ?? pageWidthPx;
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
const el = containerRef.current;
|
|
126
|
+
if (!el) return;
|
|
127
|
+
const update = () => {
|
|
128
|
+
const w = el.clientWidth;
|
|
129
|
+
const h = el.clientHeight;
|
|
130
|
+
if (w > 0 && h > 0) {
|
|
131
|
+
setScale(Math.min(w / pageWidthPx, h / pageHeightPx));
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
update();
|
|
135
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
136
|
+
const ro = new ResizeObserver(update);
|
|
137
|
+
ro.observe(el);
|
|
138
|
+
return () => ro.disconnect();
|
|
139
|
+
}, [pageWidthPx, pageHeightPx]);
|
|
140
|
+
|
|
141
|
+
const scaledWidth = scale ? pageWidthPx * scale : 0;
|
|
142
|
+
const scaledHeight = scale ? pageHeightPx * scale : 0;
|
|
143
|
+
const frameStyle: CSSProperties = {
|
|
144
|
+
width: `${scaledWidth}px`,
|
|
145
|
+
height: `${scaledHeight}px`,
|
|
146
|
+
position: "relative",
|
|
147
|
+
visibility: scale ? "visible" : "hidden",
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Match the wrapping used by PublicReaderPage so scoped CSS targeting
|
|
151
|
+
// `.openpress-html-page__html` selectors lights up identically. The
|
|
152
|
+
// outer frame owns centering; the page only scales from its top-left
|
|
153
|
+
// origin, which avoids mixed translate/scale centering drift.
|
|
154
|
+
const pageStyle: CSSProperties = {
|
|
155
|
+
"--openpress-page-width": `${pageWidthPx}px`,
|
|
156
|
+
"--openpress-page-height": `${pageHeightPx}px`,
|
|
157
|
+
width: `${pageWidthPx}px`,
|
|
158
|
+
height: `${pageHeightPx}px`,
|
|
159
|
+
transform: scale ? `scale(${scale})` : undefined,
|
|
160
|
+
transformOrigin: "top left",
|
|
161
|
+
position: "absolute",
|
|
162
|
+
top: 0,
|
|
163
|
+
left: 0,
|
|
164
|
+
} as CSSProperties;
|
|
165
|
+
const pageClass = page.className
|
|
166
|
+
? `openpress-html-page ${page.className}`
|
|
167
|
+
: "openpress-html-page";
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div className="openpress-workspace-gallery__thumb-stage" ref={containerRef}>
|
|
171
|
+
<div className="openpress-workspace-gallery__thumb-frame" style={frameStyle}>
|
|
172
|
+
<div className={pageClass} style={pageStyle} data-openpress-thumb-page="true">
|
|
173
|
+
<div
|
|
174
|
+
className="openpress-html-page__html"
|
|
175
|
+
// Trusted HTML — same source as the reader's main render path.
|
|
176
|
+
dangerouslySetInnerHTML={{ __html: page.html }}
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
type ThumbnailState =
|
|
185
|
+
| { status: "loading" }
|
|
186
|
+
| { status: "error" }
|
|
187
|
+
| { status: "ready"; page: HtmlPageBlock };
|
|
188
|
+
|
|
189
|
+
async function fetchFirstPage(url: string): Promise<HtmlPageBlock | null> {
|
|
190
|
+
try {
|
|
191
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
192
|
+
if (!response.ok) return null;
|
|
193
|
+
const doc = (await response.json()) as ReaderDocument;
|
|
194
|
+
const firstPage = doc.blocks.find((b): b is HtmlPageBlock => b.kind === "htmlPage");
|
|
195
|
+
return firstPage ?? null;
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Convert a CSS length string (px / mm / cm / in) into device pixels
|
|
202
|
+
// at 96 dpi. A4 pages are stored as "210mm" / "297mm" so the gallery
|
|
203
|
+
// and thumbnail scalers need this to compute their fit ratio — using
|
|
204
|
+
// the bare string would always fall back to the default fallback.
|
|
205
|
+
function parsePxLength(value: string | undefined): number | null {
|
|
206
|
+
if (!value) return null;
|
|
207
|
+
const match = value.trim().match(/^([\d.]+)\s*(px|mm|cm|in)$/i);
|
|
208
|
+
if (!match) return null;
|
|
209
|
+
const n = Number(match[1]);
|
|
210
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
211
|
+
const unit = match[2].toLowerCase();
|
|
212
|
+
switch (unit) {
|
|
213
|
+
case "px": return n;
|
|
214
|
+
case "mm": return n * (96 / 25.4);
|
|
215
|
+
case "cm": return n * (96 / 2.54);
|
|
216
|
+
case "in": return n * 96;
|
|
217
|
+
default: return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import { useContext
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { cn } from "./cn";
|
|
2
3
|
import { FrameContext, type FrameContextValue } from "./FrameContext";
|
|
3
4
|
import { PressContext } from "./Press";
|
|
4
5
|
import type { FrameProps } from "./types";
|
|
6
|
+
import { createFrameObjectEntityId, createPageObjectEntityId, createScopedObjectEntityId } from "../document-model/objectEntityModel";
|
|
5
7
|
|
|
6
8
|
// Substring reserved for the overflow extension pipeline.
|
|
7
9
|
const RESERVED_EXTENDED = ":extended:";
|
|
8
10
|
|
|
9
11
|
export const FRAME_MARKER: unique symbol = Symbol.for("@open-press/core:Frame");
|
|
10
12
|
|
|
11
|
-
function classNames(...values: Array<string | undefined>) {
|
|
12
|
-
const joined = values.filter(Boolean).join(" ");
|
|
13
|
-
return joined.length > 0 ? joined : undefined;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
13
|
export function Frame({
|
|
17
14
|
frameKey,
|
|
18
15
|
role,
|
|
@@ -31,37 +28,51 @@ export function Frame({
|
|
|
31
28
|
);
|
|
32
29
|
}
|
|
33
30
|
|
|
31
|
+
const parentFrame = useContext(FrameContext);
|
|
34
32
|
const press = useContext(PressContext);
|
|
35
33
|
const allocation = press?.allocation ?? null;
|
|
36
34
|
const frameAllocation = frameKey && allocation ? allocation[frameKey] : undefined;
|
|
35
|
+
const pageId = parentFrame?.pageId ?? createPageObjectEntityId(frameKey);
|
|
36
|
+
const objectId = parentFrame
|
|
37
|
+
? createScopedObjectEntityId("frame", parentFrame.objectId, frameKey)
|
|
38
|
+
: createFrameObjectEntityId(frameKey);
|
|
37
39
|
|
|
38
40
|
// Mutable per-render counter. SSR renders a Frame exactly once, so a plain
|
|
39
41
|
// object is fine — no useRef needed.
|
|
40
42
|
const areaCounts: Record<string, number> = {};
|
|
41
43
|
const frameContextValue: FrameContextValue = {
|
|
42
44
|
frameKey: frameKey ?? "",
|
|
43
|
-
|
|
45
|
+
objectId,
|
|
46
|
+
pageId,
|
|
47
|
+
consumeArea(chainId: string) {
|
|
44
48
|
const index = areaCounts[chainId] ?? 0;
|
|
45
49
|
areaCounts[chainId] = index + 1;
|
|
46
|
-
if (!frameAllocation) return null;
|
|
50
|
+
if (!frameAllocation) return { indexInFrame: index, blocks: null };
|
|
47
51
|
const chainSlots = frameAllocation[chainId];
|
|
48
|
-
if (!chainSlots) return null;
|
|
49
|
-
return chainSlots[index] ?? null;
|
|
52
|
+
if (!chainSlots) return { indexInFrame: index, blocks: null };
|
|
53
|
+
return { indexInFrame: index, blocks: chainSlots[index] ?? null };
|
|
50
54
|
},
|
|
51
55
|
};
|
|
52
56
|
|
|
53
57
|
const pageKind = derivePageKind(role);
|
|
58
|
+
const isNestedFrame = Boolean(parentFrame);
|
|
54
59
|
|
|
55
60
|
return (
|
|
56
61
|
<FrameContext.Provider value={frameContextValue}>
|
|
57
62
|
<section
|
|
58
63
|
{...(rest as Record<string, unknown>)}
|
|
59
|
-
className={
|
|
60
|
-
data-openpress-frame-key={frameKey}
|
|
64
|
+
className={cn(isNestedFrame ? undefined : "reader-page", className)}
|
|
65
|
+
data-openpress-frame-key={isNestedFrame ? undefined : frameKey}
|
|
66
|
+
data-openpress-region-frame-key={isNestedFrame ? frameKey : undefined}
|
|
67
|
+
data-openpress-object-id={objectId}
|
|
68
|
+
data-openpress-object-kind="frame"
|
|
69
|
+
data-openpress-object-label={role ?? frameKey}
|
|
70
|
+
data-openpress-object-parent-id={parentFrame?.objectId ?? (isNestedFrame ? undefined : pageId)}
|
|
71
|
+
data-openpress-object-page-id={pageId}
|
|
61
72
|
data-frame-role={role}
|
|
62
|
-
data-page-kind={pageKind}
|
|
63
|
-
data-frame-chrome={chrome ? "true" : "false"}
|
|
64
|
-
data-page-footer={chrome ? "true" : "false"}
|
|
73
|
+
data-page-kind={isNestedFrame ? undefined : pageKind}
|
|
74
|
+
data-frame-chrome={isNestedFrame ? undefined : chrome ? "true" : "false"}
|
|
75
|
+
data-page-footer={isNestedFrame ? undefined : chrome ? "true" : "false"}
|
|
65
76
|
>
|
|
66
77
|
{children}
|
|
67
78
|
</section>
|
|
@@ -9,11 +9,18 @@ import { createContext, type ReactNode } from "react";
|
|
|
9
9
|
// and so on. Empty Frames (no allocation) return null, which renders the
|
|
10
10
|
// MdxArea as a measurement placeholder.
|
|
11
11
|
|
|
12
|
+
export interface ConsumedMdxArea {
|
|
13
|
+
indexInFrame: number;
|
|
14
|
+
// Null when the frame has no allocation (measurement pass) or no blocks
|
|
15
|
+
// for this chain at the claimed index.
|
|
16
|
+
blocks: ReactNode | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
export interface FrameContextValue {
|
|
13
20
|
frameKey: string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
consumeArea(chainId: string):
|
|
21
|
+
objectId: string;
|
|
22
|
+
pageId: string;
|
|
23
|
+
consumeArea(chainId: string): ConsumedMdxArea;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
export const FrameContext = createContext<FrameContextValue | null>(null);
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { useContext, type ReactNode } from "react";
|
|
2
|
+
import { cn } from "./cn";
|
|
2
3
|
import { FrameContext } from "./FrameContext";
|
|
3
4
|
import type { MdxAreaProps } from "./types";
|
|
4
|
-
|
|
5
|
-
function classNames(...values: Array<string | undefined>) {
|
|
6
|
-
const joined = values.filter(Boolean).join(" ");
|
|
7
|
-
return joined.length > 0 ? joined : undefined;
|
|
8
|
-
}
|
|
5
|
+
import { createMdxAreaObjectEntityId } from "../document-model/objectEntityModel";
|
|
9
6
|
|
|
10
7
|
export function MdxArea({
|
|
11
8
|
chainId,
|
|
@@ -14,20 +11,22 @@ export function MdxArea({
|
|
|
14
11
|
...rest
|
|
15
12
|
}: MdxAreaProps) {
|
|
16
13
|
const frame = useContext(FrameContext);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
const consumed = frame?.consumeArea(chainId) ?? null;
|
|
15
|
+
const blocks: ReactNode | null = consumed?.blocks ?? null;
|
|
16
|
+
const objectId = frame && consumed
|
|
17
|
+
? createMdxAreaObjectEntityId(frame.frameKey, chainId, consumed.indexInFrame)
|
|
18
|
+
: undefined;
|
|
22
19
|
|
|
23
20
|
return (
|
|
24
21
|
<div
|
|
25
22
|
{...(rest as Record<string, unknown>)}
|
|
26
|
-
className={
|
|
23
|
+
className={cn("openpress-mdx-area", className)}
|
|
27
24
|
data-openpress-mdx-area="true"
|
|
28
25
|
data-openpress-mdx-area-chain={chainId}
|
|
26
|
+
data-openpress-mdx-area-index={consumed?.indexInFrame}
|
|
27
|
+
data-openpress-object-id={objectId}
|
|
29
28
|
data-openpress-mdx-area-overflow={overflow}
|
|
30
|
-
data-openpress-mdx-area-empty={blocks == null ? "true" :
|
|
29
|
+
data-openpress-mdx-area-empty={blocks == null ? "true" : "false"}
|
|
31
30
|
>
|
|
32
31
|
{blocks}
|
|
33
32
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createContext, Fragment
|
|
2
|
-
import type { FrameAllocation, ResolvedSource, TocEntry } from "./types";
|
|
1
|
+
import { createContext, Fragment } from "react";
|
|
2
|
+
import type { FrameAllocation, PressProps, ResolvedSource, TocEntry } from "./types";
|
|
3
3
|
|
|
4
4
|
// Marker the engine uses to distinguish a Press default export from any other
|
|
5
5
|
// React component. Workspaces register a default export whose `type` is this
|
|
@@ -14,6 +14,18 @@ export interface AllocationHints {
|
|
|
14
14
|
totalPagesPerChain: Record<string, number>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Metadata read from <Press> props by the engine pipeline. The 1.0 contract
|
|
18
|
+
// declares these on the component; v0.x reads them from openpress.config.mjs
|
|
19
|
+
// instead and leaves these as undefined. The engine merges both sources
|
|
20
|
+
// (props override config) until v1.0 removes config support.
|
|
21
|
+
export interface PressMetadata {
|
|
22
|
+
title?: string;
|
|
23
|
+
page?: PressProps["page"];
|
|
24
|
+
slug?: string;
|
|
25
|
+
theme?: string;
|
|
26
|
+
componentsDir?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
export interface PressContextValue {
|
|
18
30
|
sources: Record<string, ResolvedSource>;
|
|
19
31
|
// Allocation map keyed by frameKey -> chainId -> areaIndex -> blocks.
|
|
@@ -23,12 +35,21 @@ export interface PressContextValue {
|
|
|
23
35
|
// the first measurement pass.
|
|
24
36
|
hints: AllocationHints | null;
|
|
25
37
|
toc: Record<string, TocEntry[]> | null;
|
|
38
|
+
// Metadata declared on <Press> props in v1.0. Engine providers may
|
|
39
|
+
// omit this on v0.x; consumers should treat undefined as "no metadata
|
|
40
|
+
// declared on Press — fall back to openpress.config.mjs values".
|
|
41
|
+
metadata?: PressMetadata;
|
|
26
42
|
}
|
|
27
43
|
|
|
28
44
|
export const PressContext = createContext<PressContextValue | null>(null);
|
|
29
45
|
|
|
30
|
-
export function Press(
|
|
31
|
-
|
|
46
|
+
export function Press(props: PressProps) {
|
|
47
|
+
// Press is intentionally inert at render time — the engine reads its
|
|
48
|
+
// props and children through React.Children inspection during the
|
|
49
|
+
// export pipeline, then injects context above any nested helpers.
|
|
50
|
+
// For the v0.x shape (children-only usage), this still passes children
|
|
51
|
+
// through unchanged.
|
|
52
|
+
return <Fragment>{props.children}</Fragment>;
|
|
32
53
|
}
|
|
33
54
|
|
|
34
55
|
(Press as unknown as { openpressMarker: typeof PRESS_MARKER }).openpressMarker = PRESS_MARKER;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createContext, Fragment } from "react";
|
|
2
|
+
import type { WorkspaceProps } from "./types";
|
|
3
|
+
|
|
4
|
+
// Marker the engine uses to identify a Workspace default export, in the
|
|
5
|
+
// same way PRESS_MARKER identifies Press. Multi-doc projects nest one
|
|
6
|
+
// or more <Press> children inside <Workspace>; single-doc projects use
|
|
7
|
+
// a Workspace with one Press child (uniform shape — no exceptions).
|
|
8
|
+
export const WORKSPACE_MARKER: unique symbol = Symbol.for("@open-press/core:Workspace");
|
|
9
|
+
|
|
10
|
+
export interface WorkspaceContextValue {
|
|
11
|
+
// Project-level label surfaced in the gallery header / tab bar / PDF
|
|
12
|
+
// metadata. Undefined if the Workspace did not declare a name.
|
|
13
|
+
name?: string;
|
|
14
|
+
// Workspace-level shared theme directory. Press children that don't
|
|
15
|
+
// set their own `theme` prop inherit from this.
|
|
16
|
+
theme?: string;
|
|
17
|
+
// Workspace-level shared media directory.
|
|
18
|
+
media?: string;
|
|
19
|
+
// Number of Press children registered in this Workspace. Set by the
|
|
20
|
+
// engine during expansion; useful to detect "gallery vs single-doc"
|
|
21
|
+
// routing without re-walking the tree.
|
|
22
|
+
pressCount: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const WorkspaceContext = createContext<WorkspaceContextValue | null>(null);
|
|
26
|
+
|
|
27
|
+
// Workspace is intentionally inert at render time. The engine inspects
|
|
28
|
+
// its props (name / theme / media) and iterates Press children during
|
|
29
|
+
// the export pipeline; rendering just passes children through so the
|
|
30
|
+
// Press → Frame → MdxArea tree underneath behaves identically to v0.x
|
|
31
|
+
// when there's only one Press child.
|
|
32
|
+
export function Workspace(props: WorkspaceProps) {
|
|
33
|
+
return <Fragment>{props.children}</Fragment>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
(Workspace as unknown as { openpressMarker: typeof WORKSPACE_MARKER }).openpressMarker = WORKSPACE_MARKER;
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
// the engine is not allowed to know about higher-level conventions.
|
|
7
7
|
|
|
8
8
|
export { Press, PressContext, PRESS_MARKER } from "./Press";
|
|
9
|
+
export { Workspace, WorkspaceContext, WORKSPACE_MARKER } from "./Workspace";
|
|
9
10
|
export { Frame, FRAME_MARKER } from "./Frame";
|
|
10
11
|
export { FrameContext } from "./FrameContext";
|
|
11
12
|
export { MdxArea } from "./MdxArea";
|
|
12
13
|
export { useSource } from "./useSource";
|
|
13
|
-
export { BaseFigure, BaseCallout } from "./primitives";
|
|
14
|
+
export { ObjectEntity, Text, BaseFigure, BaseCallout, MediaFigure, ImageFigure } from "./primitives";
|
|
14
15
|
|
|
15
16
|
export type {
|
|
16
17
|
FrameProps,
|
|
@@ -18,10 +19,16 @@ export type {
|
|
|
18
19
|
MdxAreaProps,
|
|
19
20
|
MdxAreaOverflow,
|
|
20
21
|
PressProps,
|
|
22
|
+
PageGeometry,
|
|
23
|
+
PressSource,
|
|
24
|
+
WorkspaceProps,
|
|
21
25
|
BaseFigureProps,
|
|
26
|
+
MediaFigureProps,
|
|
22
27
|
BaseCalloutKind,
|
|
23
28
|
BaseCalloutProps,
|
|
24
|
-
|
|
29
|
+
ObjectEntityElement,
|
|
30
|
+
ObjectEntityProps,
|
|
31
|
+
TextProps,
|
|
25
32
|
// Source-side types are re-exported here for convenience so authors can
|
|
26
33
|
// import `ResolvedSource` from the same place they import primitives.
|
|
27
34
|
ResolvedSource,
|
|
@@ -35,5 +42,6 @@ export type {
|
|
|
35
42
|
FrameAllocation,
|
|
36
43
|
} from "./types";
|
|
37
44
|
|
|
38
|
-
export type { PressContextValue, AllocationHints } from "./Press";
|
|
45
|
+
export type { PressContextValue, AllocationHints, PressMetadata } from "./Press";
|
|
46
|
+
export type { WorkspaceContextValue } from "./Workspace";
|
|
39
47
|
export type { FrameContextValue } from "./FrameContext";
|
|
@@ -1,13 +1,56 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { cn } from "./cn";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { FrameContext } from "./FrameContext";
|
|
4
|
+
import type { BaseCalloutProps, BaseFigureProps, MediaFigureProps, ObjectEntityProps, TextProps } from "./types";
|
|
5
|
+
import { createScopedObjectEntityId } from "../document-model/objectEntityModel";
|
|
2
6
|
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
export function ObjectEntity({
|
|
8
|
+
as: Element = "span",
|
|
9
|
+
objectId,
|
|
10
|
+
kind,
|
|
11
|
+
label,
|
|
12
|
+
parentId,
|
|
13
|
+
pageId,
|
|
14
|
+
blockId,
|
|
15
|
+
frameKey,
|
|
16
|
+
chainId,
|
|
17
|
+
source,
|
|
18
|
+
metadata,
|
|
19
|
+
children,
|
|
20
|
+
...entityProps
|
|
21
|
+
}: ObjectEntityProps) {
|
|
22
|
+
const frame = useContext(FrameContext);
|
|
23
|
+
const resolvedParentId = parentId ?? frame?.objectId;
|
|
24
|
+
const resolvedPageId = pageId ?? frame?.pageId;
|
|
25
|
+
const resolvedFrameKey = frameKey ?? frame?.frameKey;
|
|
26
|
+
const resolvedObjectId = createScopedObjectEntityId(kind, resolvedParentId, objectId);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Element
|
|
30
|
+
{...entityProps}
|
|
31
|
+
data-openpress-object-id={resolvedObjectId}
|
|
32
|
+
data-openpress-object-kind={kind}
|
|
33
|
+
data-openpress-object-label={label}
|
|
34
|
+
data-openpress-object-parent-id={resolvedParentId}
|
|
35
|
+
data-openpress-object-page-id={resolvedPageId}
|
|
36
|
+
data-openpress-block-id={blockId}
|
|
37
|
+
data-openpress-object-frame-key={resolvedFrameKey}
|
|
38
|
+
data-openpress-object-chain-id={chainId}
|
|
39
|
+
data-openpress-object-source={source ? JSON.stringify(source) : undefined}
|
|
40
|
+
data-openpress-object-metadata={metadata ? JSON.stringify(metadata) : undefined}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
</Element>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function Text(props: TextProps) {
|
|
48
|
+
return <ObjectEntity {...props} kind="text" />;
|
|
6
49
|
}
|
|
7
50
|
|
|
8
51
|
export function BaseFigure({ caption, className, children, ...figureProps }: BaseFigureProps) {
|
|
9
52
|
return (
|
|
10
|
-
<figure {...figureProps} className={
|
|
53
|
+
<figure {...figureProps} className={cn("openpress-figure", className)}>
|
|
11
54
|
<div data-figure-body>{children}</div>
|
|
12
55
|
{caption === undefined ? null : <figcaption>{caption}</figcaption>}
|
|
13
56
|
</figure>
|
|
@@ -16,8 +59,33 @@ export function BaseFigure({ caption, className, children, ...figureProps }: Bas
|
|
|
16
59
|
|
|
17
60
|
export function BaseCallout({ kind = "info", className, children, ...calloutProps }: BaseCalloutProps) {
|
|
18
61
|
return (
|
|
19
|
-
<aside {...calloutProps} className={
|
|
62
|
+
<aside {...calloutProps} className={cn("openpress-callout", className)} data-callout-kind={kind}>
|
|
20
63
|
{children}
|
|
21
64
|
</aside>
|
|
22
65
|
);
|
|
23
66
|
}
|
|
67
|
+
|
|
68
|
+
export function MediaFigure({
|
|
69
|
+
src,
|
|
70
|
+
alt,
|
|
71
|
+
caption,
|
|
72
|
+
className,
|
|
73
|
+
imgClassName,
|
|
74
|
+
loading = "eager",
|
|
75
|
+
...figureProps
|
|
76
|
+
}: MediaFigureProps) {
|
|
77
|
+
return (
|
|
78
|
+
<BaseFigure {...figureProps} className={cn("openpress-media-figure", className)} caption={caption}>
|
|
79
|
+
<img src={resolveMediaSrc(src)} alt={alt} loading={loading} className={imgClassName} />
|
|
80
|
+
</BaseFigure>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const ImageFigure = MediaFigure;
|
|
85
|
+
|
|
86
|
+
function resolveMediaSrc(src: string) {
|
|
87
|
+
const trimmed = String(src ?? "").trim();
|
|
88
|
+
if (!trimmed) return "";
|
|
89
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/)/i.test(trimmed)) return trimmed;
|
|
90
|
+
return `/openpress/media/${trimmed.replace(/^\.?\/*/, "")}`;
|
|
91
|
+
}
|