@open-press/core 0.7.1 → 0.8.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/engine/commands/dev.mjs +2 -2
- package/engine/output/chrome-pdf.mjs +18 -3
- package/engine/output/static-server.mjs +39 -0
- package/engine/react/comment-endpoint.mjs +13 -39
- package/engine/react/comment-marker.mjs +30 -6
- package/engine/react/document-entry.mjs +11 -0
- package/engine/react/document-export.mjs +30 -5
- package/engine/react/http-json.mjs +24 -0
- package/engine/react/mdx-compile.mjs +96 -3
- package/engine/react/measurement-css.mjs +93 -1
- package/engine/react/object-entities.mjs +119 -0
- package/engine/react/pipeline/allocate.mjs +10 -7
- package/engine/react/pipeline/frame-measurement.mjs +2 -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 +12 -14
- package/engine/react/style-discovery.mjs +1 -4
- package/engine/runtime/file-walk.mjs +22 -0
- package/engine/runtime/inspection.mjs +1 -20
- 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 +4 -31
- package/package.json +1 -1
- package/src/openpress/{App.tsx → app/OpenPressApp.tsx} +25 -12
- package/src/openpress/{renderer.tsx → app/OpenPressRuntime.tsx} +10 -7
- package/src/openpress/app/index.ts +2 -0
- package/src/openpress/core/Frame.tsx +9 -11
- package/src/openpress/core/FrameContext.tsx +8 -3
- package/src/openpress/core/MdxArea.tsx +11 -12
- package/src/openpress/core/cn.ts +4 -0
- package/src/openpress/core/index.tsx +2 -1
- package/src/openpress/core/primitives.tsx +29 -8
- package/src/openpress/core/types.ts +8 -0
- 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} +42 -0
- package/src/openpress/document-model/index.ts +6 -0
- package/src/openpress/document-model/objectEntityModel.ts +51 -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/manuscript/index.tsx +49 -7
- 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 +10 -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 +407 -0
- package/src/openpress/workbench/actions/DeploymentControl.tsx +157 -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 +5 -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 +248 -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 +76 -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 +523 -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 +243 -55
- package/src/styles/openpress/responsive.css +145 -270
- package/src/styles/openpress/workbench-panels.css +214 -178
- package/src/styles/openpress/workbench.css +986 -451
- package/src/styles/openpress.css +1 -1
- package/vite.config.ts +50 -0
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import { isLocalWorkspaceHost } from "
|
|
4
|
-
import type { DeploymentInfo, ReaderDocument } from "
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { OpenPressRuntime } from "./OpenPressRuntime";
|
|
3
|
+
import { isLocalWorkspaceHost } from "../shared";
|
|
4
|
+
import type { DeploymentInfo, ReaderDocument } from "../document-model";
|
|
5
5
|
|
|
6
6
|
type LoadState =
|
|
7
7
|
| { status: "loading" }
|
|
@@ -39,22 +39,26 @@ function LoadingScreen() {
|
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export function
|
|
42
|
+
export function OpenPressApp() {
|
|
43
43
|
const [state, setState] = useState<LoadState>({ status: "loading" });
|
|
44
44
|
|
|
45
|
+
const refreshDocument = useCallback(async () => {
|
|
46
|
+
const document = await loadReaderDocument();
|
|
47
|
+
setState((current) => {
|
|
48
|
+
if (current.status !== "ready") return current;
|
|
49
|
+
return { ...current, document };
|
|
50
|
+
});
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
45
53
|
useEffect(() => {
|
|
46
54
|
let cancelled = false;
|
|
47
55
|
|
|
48
56
|
async function loadDocument() {
|
|
49
57
|
try {
|
|
50
|
-
const [
|
|
51
|
-
|
|
58
|
+
const [document, deploymentInfo] = await Promise.all([
|
|
59
|
+
loadReaderDocument(),
|
|
52
60
|
loadDeploymentInfo(),
|
|
53
61
|
]);
|
|
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
62
|
if (!cancelled) {
|
|
59
63
|
setState({ status: "ready", document, deploymentInfo });
|
|
60
64
|
}
|
|
@@ -81,13 +85,22 @@ export function App() {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
return (
|
|
84
|
-
<
|
|
88
|
+
<OpenPressRuntime
|
|
85
89
|
document={state.document}
|
|
86
90
|
deploymentInfo={state.deploymentInfo}
|
|
91
|
+
onDocumentRefresh={refreshDocument}
|
|
87
92
|
/>
|
|
88
93
|
);
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
async function loadReaderDocument(): Promise<ReaderDocument> {
|
|
97
|
+
const response = await fetch("/openpress/document.json", { cache: "no-store" });
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(`Unable to load /openpress/document.json (${response.status})`);
|
|
100
|
+
}
|
|
101
|
+
return (await response.json()) as ReaderDocument;
|
|
102
|
+
}
|
|
103
|
+
|
|
91
104
|
async function loadDeploymentInfo(): Promise<DeploymentInfo> {
|
|
92
105
|
if (typeof window !== "undefined" && isLocalWorkspaceHost(window.location.hostname)) {
|
|
93
106
|
const localInfo = await loadDeploymentInfoFrom("/__openpress/status");
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { useMemo, type CSSProperties } from "react";
|
|
2
|
-
import { PrintDocument, PublicViewer } from "
|
|
3
|
-
import { isPrintModeLocation, isWorkspaceModeLocation } from "
|
|
4
|
-
import { HtmlWorkbench } from "
|
|
2
|
+
import { PrintDocument, PublicViewer } from "../reader";
|
|
3
|
+
import { isPrintModeLocation, isWorkspaceModeLocation } from "../shared";
|
|
4
|
+
import { HtmlWorkbench } from "../workbench";
|
|
5
5
|
import type {
|
|
6
6
|
DeploymentInfo,
|
|
7
7
|
ReaderDocument,
|
|
8
8
|
HtmlPageBlock,
|
|
9
9
|
Theme,
|
|
10
|
-
} from "
|
|
10
|
+
} from "../document-model";
|
|
11
11
|
|
|
12
|
-
interface
|
|
12
|
+
interface OpenPressRuntimeProps {
|
|
13
13
|
document: ReaderDocument;
|
|
14
14
|
deploymentInfo?: DeploymentInfo;
|
|
15
|
+
onDocumentRefresh?: () => void | Promise<void>;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export function
|
|
18
|
+
export function OpenPressRuntime({
|
|
18
19
|
document,
|
|
19
20
|
deploymentInfo = { online: false },
|
|
20
|
-
|
|
21
|
+
onDocumentRefresh,
|
|
22
|
+
}: OpenPressRuntimeProps) {
|
|
21
23
|
const style = themeToCssVariables(document.theme);
|
|
22
24
|
const htmlPages = document.blocks.filter((block): block is HtmlPageBlock => block.kind === "htmlPage");
|
|
23
25
|
const workspaceMode = useMemo(() => {
|
|
@@ -45,6 +47,7 @@ export function Renderer({
|
|
|
45
47
|
style={style}
|
|
46
48
|
devMode={workspaceMode}
|
|
47
49
|
deploymentInfo={deploymentInfo}
|
|
50
|
+
onDocumentRefresh={onDocumentRefresh}
|
|
48
51
|
/>
|
|
49
52
|
);
|
|
50
53
|
}
|
|
@@ -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 } 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,
|
|
@@ -40,13 +37,13 @@ export function Frame({
|
|
|
40
37
|
const areaCounts: Record<string, number> = {};
|
|
41
38
|
const frameContextValue: FrameContextValue = {
|
|
42
39
|
frameKey: frameKey ?? "",
|
|
43
|
-
consumeArea(chainId: string)
|
|
40
|
+
consumeArea(chainId: string) {
|
|
44
41
|
const index = areaCounts[chainId] ?? 0;
|
|
45
42
|
areaCounts[chainId] = index + 1;
|
|
46
|
-
if (!frameAllocation) return null;
|
|
43
|
+
if (!frameAllocation) return { indexInFrame: index, blocks: null };
|
|
47
44
|
const chainSlots = frameAllocation[chainId];
|
|
48
|
-
if (!chainSlots) return null;
|
|
49
|
-
return chainSlots[index] ?? null;
|
|
45
|
+
if (!chainSlots) return { indexInFrame: index, blocks: null };
|
|
46
|
+
return { indexInFrame: index, blocks: chainSlots[index] ?? null };
|
|
50
47
|
},
|
|
51
48
|
};
|
|
52
49
|
|
|
@@ -56,8 +53,9 @@ export function Frame({
|
|
|
56
53
|
<FrameContext.Provider value={frameContextValue}>
|
|
57
54
|
<section
|
|
58
55
|
{...(rest as Record<string, unknown>)}
|
|
59
|
-
className={
|
|
56
|
+
className={cn("reader-page", className)}
|
|
60
57
|
data-openpress-frame-key={frameKey}
|
|
58
|
+
data-openpress-object-id={createFrameObjectEntityId(frameKey)}
|
|
61
59
|
data-frame-role={role}
|
|
62
60
|
data-page-kind={pageKind}
|
|
63
61
|
data-frame-chrome={chrome ? "true" : "false"}
|
|
@@ -9,11 +9,16 @@ 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
|
-
// frame has no allocation (measurement pass) or no blocks for this chain.
|
|
16
|
-
consumeArea(chainId: string): ReactNode | null;
|
|
21
|
+
consumeArea(chainId: string): ConsumedMdxArea;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
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>
|
|
@@ -10,7 +10,7 @@ export { Frame, FRAME_MARKER } from "./Frame";
|
|
|
10
10
|
export { FrameContext } from "./FrameContext";
|
|
11
11
|
export { MdxArea } from "./MdxArea";
|
|
12
12
|
export { useSource } from "./useSource";
|
|
13
|
-
export { BaseFigure, BaseCallout } from "./primitives";
|
|
13
|
+
export { BaseFigure, BaseCallout, MediaFigure, ImageFigure } from "./primitives";
|
|
14
14
|
|
|
15
15
|
export type {
|
|
16
16
|
FrameProps,
|
|
@@ -19,6 +19,7 @@ export type {
|
|
|
19
19
|
MdxAreaOverflow,
|
|
20
20
|
PressProps,
|
|
21
21
|
BaseFigureProps,
|
|
22
|
+
MediaFigureProps,
|
|
22
23
|
BaseCalloutKind,
|
|
23
24
|
BaseCalloutProps,
|
|
24
25
|
Manifest,
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
function classNames(...values: Array<string | undefined>) {
|
|
4
|
-
const joined = values.filter(Boolean).join(" ");
|
|
5
|
-
return joined.length > 0 ? joined : undefined;
|
|
6
|
-
}
|
|
1
|
+
import { cn } from "./cn";
|
|
2
|
+
import type { BaseCalloutProps, BaseFigureProps, MediaFigureProps } from "./types";
|
|
7
3
|
|
|
8
4
|
export function BaseFigure({ caption, className, children, ...figureProps }: BaseFigureProps) {
|
|
9
5
|
return (
|
|
10
|
-
<figure {...figureProps} className={
|
|
6
|
+
<figure {...figureProps} className={cn("openpress-figure", className)}>
|
|
11
7
|
<div data-figure-body>{children}</div>
|
|
12
8
|
{caption === undefined ? null : <figcaption>{caption}</figcaption>}
|
|
13
9
|
</figure>
|
|
@@ -16,8 +12,33 @@ export function BaseFigure({ caption, className, children, ...figureProps }: Bas
|
|
|
16
12
|
|
|
17
13
|
export function BaseCallout({ kind = "info", className, children, ...calloutProps }: BaseCalloutProps) {
|
|
18
14
|
return (
|
|
19
|
-
<aside {...calloutProps} className={
|
|
15
|
+
<aside {...calloutProps} className={cn("openpress-callout", className)} data-callout-kind={kind}>
|
|
20
16
|
{children}
|
|
21
17
|
</aside>
|
|
22
18
|
);
|
|
23
19
|
}
|
|
20
|
+
|
|
21
|
+
export function MediaFigure({
|
|
22
|
+
src,
|
|
23
|
+
alt,
|
|
24
|
+
caption,
|
|
25
|
+
className,
|
|
26
|
+
imgClassName,
|
|
27
|
+
loading = "eager",
|
|
28
|
+
...figureProps
|
|
29
|
+
}: MediaFigureProps) {
|
|
30
|
+
return (
|
|
31
|
+
<BaseFigure {...figureProps} className={cn("openpress-media-figure", className)} caption={caption}>
|
|
32
|
+
<img src={resolveMediaSrc(src)} alt={alt} loading={loading} className={imgClassName} />
|
|
33
|
+
</BaseFigure>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const ImageFigure = MediaFigure;
|
|
38
|
+
|
|
39
|
+
function resolveMediaSrc(src: string) {
|
|
40
|
+
const trimmed = String(src ?? "").trim();
|
|
41
|
+
if (!trimmed) return "";
|
|
42
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/)/i.test(trimmed)) return trimmed;
|
|
43
|
+
return `/openpress/media/${trimmed.replace(/^\.?\/*/, "")}`;
|
|
44
|
+
}
|
|
@@ -38,6 +38,14 @@ export type BaseFigureProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
|
38
38
|
children: ReactNode;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
export type MediaFigureProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
42
|
+
src: string;
|
|
43
|
+
alt: string;
|
|
44
|
+
caption: ReactNode;
|
|
45
|
+
imgClassName?: string;
|
|
46
|
+
loading?: "eager" | "lazy";
|
|
47
|
+
};
|
|
48
|
+
|
|
41
49
|
export type BaseCalloutKind = "info" | "warn" | "success" | "error" | (string & {});
|
|
42
50
|
|
|
43
51
|
export type BaseCalloutProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// `PublicPage` / `HtmlWorkbench` HMR-clean (Fast Refresh expects component
|
|
4
4
|
// files to export only components).
|
|
5
5
|
|
|
6
|
-
import type { DisplayPage } from "
|
|
6
|
+
import type { DisplayPage } from "../reader";
|
|
7
7
|
|
|
8
8
|
export function createAnchorPageMap(pages: DisplayPage[]) {
|
|
9
9
|
const map = new Map<string, number>();
|
|
@@ -25,6 +25,7 @@ export interface DocumentSource {
|
|
|
25
25
|
editMode?: string;
|
|
26
26
|
styles?: DocumentStyle[];
|
|
27
27
|
blockMap?: Record<string, SourceBlock>;
|
|
28
|
+
objectEntities?: Record<string, ObjectEntity>;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export interface DocumentStyle {
|
|
@@ -49,6 +50,9 @@ export interface SourceBlock {
|
|
|
49
50
|
pageIndex?: number;
|
|
50
51
|
pageNumber?: number;
|
|
51
52
|
source?: SourceLocation;
|
|
53
|
+
frameKey?: string;
|
|
54
|
+
chainId?: string;
|
|
55
|
+
sectionSlug?: string;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
export interface DocumentMeta {
|
|
@@ -93,4 +97,42 @@ export interface HtmlPageBlock {
|
|
|
93
97
|
anchors?: string[];
|
|
94
98
|
className?: string;
|
|
95
99
|
source?: BlockSource;
|
|
100
|
+
frameKey?: string;
|
|
101
|
+
role?: string | null;
|
|
102
|
+
chrome?: boolean;
|
|
103
|
+
blockIds?: string[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type ObjectEntityKind =
|
|
107
|
+
| "page"
|
|
108
|
+
| "frame"
|
|
109
|
+
| "mdx-area"
|
|
110
|
+
| "mdx-block"
|
|
111
|
+
| "component"
|
|
112
|
+
| "media";
|
|
113
|
+
|
|
114
|
+
export interface EditableSourceRef {
|
|
115
|
+
path: string;
|
|
116
|
+
file?: string;
|
|
117
|
+
source?: SourceLocation;
|
|
118
|
+
line?: number;
|
|
119
|
+
column?: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ObjectEntityRef {
|
|
123
|
+
id: string;
|
|
124
|
+
kind: ObjectEntityKind;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ObjectEntity {
|
|
128
|
+
id: string;
|
|
129
|
+
kind: ObjectEntityKind;
|
|
130
|
+
label: string;
|
|
131
|
+
parentId?: string;
|
|
132
|
+
pageId?: string;
|
|
133
|
+
blockId?: string;
|
|
134
|
+
frameKey?: string;
|
|
135
|
+
chainId?: string;
|
|
136
|
+
source?: EditableSourceRef;
|
|
137
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
96
138
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ObjectEntity, ObjectEntityKind, ReaderDocument, SourceBlock } from "./documentTypes";
|
|
2
|
+
|
|
3
|
+
export function createObjectEntityId(kind: ObjectEntityKind, ...parts: Array<string | number>) {
|
|
4
|
+
return [kind, ...parts.map((part) => encodeURIComponent(String(part)))].join(":");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createBlockObjectEntityId(blockId: string) {
|
|
8
|
+
return createObjectEntityId("mdx-block", blockId);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createFrameObjectEntityId(frameKey: string) {
|
|
12
|
+
return createObjectEntityId("frame", frameKey);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createPageObjectEntityId(frameKey: string) {
|
|
16
|
+
return createObjectEntityId("page", frameKey);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createMdxAreaObjectEntityId(frameKey: string, chainId: string, indexInFrame: number) {
|
|
20
|
+
return createObjectEntityId("mdx-area", frameKey, chainId, indexInFrame);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getObjectEntityMap(document: Pick<ReaderDocument, "source"> | null | undefined): Record<string, ObjectEntity> {
|
|
24
|
+
return document?.source?.objectEntities ?? {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getObjectEntity(document: Pick<ReaderDocument, "source"> | null | undefined, objectId: string): ObjectEntity | null {
|
|
28
|
+
return getObjectEntityMap(document)[objectId] ?? null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function sourceBlockToObjectEntity(block: SourceBlock): ObjectEntity {
|
|
32
|
+
return {
|
|
33
|
+
id: createBlockObjectEntityId(block.id),
|
|
34
|
+
kind: "mdx-block",
|
|
35
|
+
label: block.name ? `${block.name} ${block.id}` : block.id,
|
|
36
|
+
blockId: block.id,
|
|
37
|
+
frameKey: block.frameKey,
|
|
38
|
+
chainId: block.chainId,
|
|
39
|
+
pageId: block.frameKey ? createPageObjectEntityId(block.frameKey) : undefined,
|
|
40
|
+
source: {
|
|
41
|
+
path: block.path,
|
|
42
|
+
source: block.source,
|
|
43
|
+
line: block.source?.line,
|
|
44
|
+
column: block.source?.column,
|
|
45
|
+
},
|
|
46
|
+
metadata: {
|
|
47
|
+
blockKind: block.kind ?? null,
|
|
48
|
+
sectionSlug: block.sectionSlug ?? block.chapterSlug ?? null,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
// document type that wants section flow imports from here; documents that
|
|
10
10
|
// do not (slides, folios, calendars) skip this module entirely.
|
|
11
11
|
|
|
12
|
-
import { Fragment, useContext, type ReactNode } from "react";
|
|
13
|
-
import { Frame, FrameContext, PressContext, useSource } from "../core";
|
|
12
|
+
import { Fragment, useContext, type ComponentType, type ReactNode } from "react";
|
|
13
|
+
import { Frame, FrameContext, MdxArea, PressContext, useSource } from "../core";
|
|
14
14
|
import type { MdxAreaOverflow, ResolvedSource } from "../core";
|
|
15
|
+
import { createMdxAreaObjectEntityId } from "../document-model/objectEntityModel";
|
|
15
16
|
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
17
18
|
// <Sections>
|
|
@@ -38,11 +39,11 @@ export interface SectionsOpenerProps {
|
|
|
38
39
|
|
|
39
40
|
export interface SectionsProps {
|
|
40
41
|
source: string;
|
|
41
|
-
page
|
|
42
|
-
opener?:
|
|
42
|
+
page?: ComponentType<SectionsPageProps>;
|
|
43
|
+
opener?: ComponentType<SectionsOpenerProps>;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
export function Sections({ source: sourceId, page: Page, opener: Opener }: SectionsProps) {
|
|
46
|
+
export function Sections({ source: sourceId, page: Page = DefaultSectionPage, opener: Opener }: SectionsProps) {
|
|
46
47
|
const source = useSource(sourceId);
|
|
47
48
|
const press = useContext(PressContext);
|
|
48
49
|
const hints = press?.hints ?? null;
|
|
@@ -92,6 +93,41 @@ export function Sections({ source: sourceId, page: Page, opener: Opener }: Secti
|
|
|
92
93
|
export const Chapters = Sections;
|
|
93
94
|
export type ChaptersProps = SectionsProps;
|
|
94
95
|
|
|
96
|
+
export function DefaultSectionPage({
|
|
97
|
+
frameKey,
|
|
98
|
+
chainId,
|
|
99
|
+
pageIndex,
|
|
100
|
+
totalPages,
|
|
101
|
+
sectionSlug,
|
|
102
|
+
sectionTitle,
|
|
103
|
+
sectionTone,
|
|
104
|
+
}: SectionsPageProps) {
|
|
105
|
+
return (
|
|
106
|
+
<Frame
|
|
107
|
+
frameKey={frameKey}
|
|
108
|
+
role="manuscript.content"
|
|
109
|
+
className="reader-page--content"
|
|
110
|
+
data-page-index={pageIndex}
|
|
111
|
+
data-total-pages={totalPages}
|
|
112
|
+
data-section-id={sectionSlug}
|
|
113
|
+
data-chapter-tone={sectionTone}
|
|
114
|
+
>
|
|
115
|
+
<div className="page-frame">
|
|
116
|
+
<header className="page-header" aria-hidden="true" />
|
|
117
|
+
<main className="page-body">
|
|
118
|
+
<MdxArea chainId={chainId} />
|
|
119
|
+
</main>
|
|
120
|
+
<footer className="page-footer" aria-hidden="true">
|
|
121
|
+
<span className="footer-left">{sectionTitle}</span>
|
|
122
|
+
<span className="footer-right">
|
|
123
|
+
{totalPages > 1 ? `${pageIndex + 1}/${totalPages}` : pageIndex + 1}
|
|
124
|
+
</span>
|
|
125
|
+
</footer>
|
|
126
|
+
</div>
|
|
127
|
+
</Frame>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
95
131
|
// ---------------------------------------------------------------------------
|
|
96
132
|
// <Toc>
|
|
97
133
|
// ---------------------------------------------------------------------------
|
|
@@ -178,15 +214,21 @@ function DefaultTocPage({ frameKey, chainId, pageIndex, totalPages, heading, cla
|
|
|
178
214
|
|
|
179
215
|
export function TocArea({ chainId, maxLevel, overflow = "extend", className }: TocAreaProps) {
|
|
180
216
|
const frame = useContext(FrameContext);
|
|
181
|
-
const
|
|
217
|
+
const consumed = frame?.consumeArea(chainId) ?? null;
|
|
218
|
+
const blocks = consumed?.blocks ?? null;
|
|
219
|
+
const objectId = frame && consumed
|
|
220
|
+
? createMdxAreaObjectEntityId(frame.frameKey, chainId, consumed.indexInFrame)
|
|
221
|
+
: undefined;
|
|
182
222
|
return (
|
|
183
223
|
<div
|
|
184
224
|
className="openpress-mdx-area openpress-toc-area"
|
|
185
225
|
data-openpress-mdx-area="true"
|
|
186
226
|
data-openpress-mdx-area-chain={chainId}
|
|
227
|
+
data-openpress-mdx-area-index={consumed?.indexInFrame}
|
|
228
|
+
data-openpress-object-id={objectId}
|
|
187
229
|
data-openpress-toc-max-level={maxLevel}
|
|
188
230
|
data-openpress-mdx-area-overflow={overflow}
|
|
189
|
-
data-openpress-mdx-area-empty={blocks == null ? "true" :
|
|
231
|
+
data-openpress-mdx-area-empty={blocks == null ? "true" : "false"}
|
|
190
232
|
>
|
|
191
233
|
<ol className={["toc-list", className].filter(Boolean).join(" ") || undefined}>
|
|
192
234
|
{blocks}
|