@open-press/core 0.7.0 → 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.
Files changed (116) hide show
  1. package/engine/commands/dev.mjs +2 -2
  2. package/engine/commands/upgrade.mjs +47 -5
  3. package/engine/output/chrome-pdf.mjs +18 -3
  4. package/engine/output/static-server.mjs +39 -0
  5. package/engine/react/comment-endpoint.mjs +13 -39
  6. package/engine/react/comment-marker.mjs +30 -6
  7. package/engine/react/document-entry.mjs +11 -0
  8. package/engine/react/document-export.mjs +45 -5
  9. package/engine/react/http-json.mjs +24 -0
  10. package/engine/react/mdx-compile.mjs +187 -3
  11. package/engine/react/measurement-css.mjs +93 -1
  12. package/engine/react/object-entities.mjs +119 -0
  13. package/engine/react/pipeline/allocate.mjs +10 -7
  14. package/engine/react/pipeline/frame-measurement.mjs +40 -9
  15. package/engine/react/project-asset-endpoint.mjs +6 -24
  16. package/engine/react/source-edit-endpoint.d.mts +10 -0
  17. package/engine/react/source-edit-endpoint.mjs +75 -0
  18. package/engine/react/sources/mdx-resolver.mjs +12 -14
  19. package/engine/react/style-discovery.mjs +1 -4
  20. package/engine/runtime/file-walk.mjs +22 -0
  21. package/engine/runtime/inspection.mjs +1 -20
  22. package/engine/runtime/path-utils.mjs +20 -0
  23. package/engine/runtime/source-text-tools.d.mts +102 -0
  24. package/engine/runtime/source-text-tools.mjs +551 -16
  25. package/engine/runtime/source-workspace.mjs +4 -31
  26. package/package.json +1 -1
  27. package/src/openpress/{App.tsx → app/OpenPressApp.tsx} +25 -12
  28. package/src/openpress/{renderer.tsx → app/OpenPressRuntime.tsx} +10 -7
  29. package/src/openpress/app/index.ts +2 -0
  30. package/src/openpress/core/Frame.tsx +9 -11
  31. package/src/openpress/core/FrameContext.tsx +8 -3
  32. package/src/openpress/core/MdxArea.tsx +11 -12
  33. package/src/openpress/core/cn.ts +4 -0
  34. package/src/openpress/core/index.tsx +2 -1
  35. package/src/openpress/core/primitives.tsx +29 -8
  36. package/src/openpress/core/types.ts +8 -0
  37. package/src/openpress/{anchorMap.ts → document-model/anchorMapModel.ts} +1 -1
  38. package/src/openpress/{indexes.ts → document-model/documentIndexes.ts} +1 -1
  39. package/src/openpress/{types.ts → document-model/documentTypes.ts} +42 -0
  40. package/src/openpress/document-model/index.ts +6 -0
  41. package/src/openpress/document-model/objectEntityModel.ts +51 -0
  42. package/src/openpress/{projectIdentity.ts → document-model/projectIdentityModel.ts} +1 -1
  43. package/src/openpress/{reactDocumentMetadata.ts → document-model/reactDocumentMetadataModel.ts} +1 -1
  44. package/src/openpress/manuscript/index.tsx +49 -7
  45. package/src/openpress/{publicPage.tsx → reader/PublicReaderPage.tsx} +31 -51
  46. package/src/openpress/{workbenchPanels.tsx → reader/ReaderNavigationPanel.tsx} +6 -5
  47. package/src/openpress/reader/index.ts +10 -0
  48. package/src/openpress/reader/pageViewportScaleModel.ts +73 -0
  49. package/src/openpress/reader/readerTypes.ts +4 -0
  50. package/src/openpress/reader/usePageViewportScale.ts +119 -0
  51. package/src/openpress/reader/usePanelState.ts +56 -0
  52. package/src/openpress/reader/useReaderHashSync.ts +61 -0
  53. package/src/openpress/reader/useReaderKeyboardNav.ts +48 -0
  54. package/src/openpress/reader/useReaderRuntime.ts +146 -0
  55. package/src/openpress/reader/useReaderScrollAnchor.ts +64 -0
  56. package/src/openpress/shared/Panel.tsx +77 -0
  57. package/src/openpress/shared/index.ts +4 -0
  58. package/src/openpress/shared/numberUtils.ts +3 -0
  59. package/src/openpress/{runtimeMode.ts → shared/runtimeMode.ts} +0 -11
  60. package/src/openpress/workbench/Workbench.tsx +407 -0
  61. package/src/openpress/workbench/actions/DeploymentControl.tsx +157 -0
  62. package/src/openpress/workbench/actions/PageZoomControl.tsx +182 -0
  63. package/src/openpress/workbench/actions/SearchControl.tsx +345 -0
  64. package/src/openpress/workbench/actions/deploymentStatusModel.ts +112 -0
  65. package/src/openpress/workbench/actions/index.ts +5 -0
  66. package/src/openpress/workbench/actions/useDeploymentWorkbench.ts +136 -0
  67. package/src/openpress/workbench/dialog/WorkbenchDialog.tsx +72 -0
  68. package/src/openpress/workbench/dialog/index.ts +1 -0
  69. package/src/openpress/workbench/document/components/DocumentPanel.tsx +127 -0
  70. package/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +207 -0
  71. package/src/openpress/workbench/document/components/ReaderStage.tsx +9 -0
  72. package/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +34 -0
  73. package/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +525 -0
  74. package/src/openpress/workbench/document/index.ts +10 -0
  75. package/src/openpress/workbench/index.ts +2 -0
  76. package/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +459 -0
  77. package/src/openpress/workbench/inspector/index.ts +5 -0
  78. package/src/openpress/workbench/inspector/inlineCommentModel.ts +125 -0
  79. package/src/openpress/workbench/inspector/inspectorGeometryModel.ts +160 -0
  80. package/src/openpress/workbench/inspector/inspectorModel.ts +408 -0
  81. package/src/openpress/workbench/inspector/useInspectorComments.ts +248 -0
  82. package/src/openpress/workbench/mentions/MentionSuggestionList.tsx +41 -0
  83. package/src/openpress/workbench/mentions/index.ts +2 -0
  84. package/src/openpress/{composerMentions.ts → workbench/mentions/useComposerMentions.ts} +1 -4
  85. package/src/openpress/workbench/panels/Panel.tsx +1 -0
  86. package/src/openpress/workbench/panels/PendingCommentsPanel.tsx +76 -0
  87. package/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +29 -0
  88. package/src/openpress/workbench/panels/index.ts +3 -0
  89. package/src/openpress/workbench/project/ProjectEntryPanel.tsx +523 -0
  90. package/src/openpress/workbench/project/ProjectPreviewDialog.tsx +35 -0
  91. package/src/openpress/workbench/project/index.ts +2 -0
  92. package/src/openpress/workbench/project/projectPreviewTypes.ts +11 -0
  93. package/src/openpress/workbench/shell/WorkbenchShell.tsx +167 -0
  94. package/src/openpress/workbench/shell/index.ts +1 -0
  95. package/src/openpress/workbench/workbenchFormatters.ts +120 -0
  96. package/src/openpress/workbench/workbenchTypes.ts +35 -0
  97. package/src/styles/openpress/print-route.css +0 -2
  98. package/src/styles/openpress/{project-workspace.css → project-preview-panel.css} +13 -407
  99. package/src/styles/openpress/public-viewer.css +25 -320
  100. package/src/styles/openpress/reader-runtime.css +243 -55
  101. package/src/styles/openpress/responsive.css +145 -270
  102. package/src/styles/openpress/workbench-panels.css +214 -178
  103. package/src/styles/openpress/workbench.css +986 -451
  104. package/src/styles/openpress.css +1 -1
  105. package/vite.config.ts +50 -0
  106. package/src/openpress/inspector.ts +0 -282
  107. package/src/openpress/projectWorkspace.tsx +0 -919
  108. package/src/openpress/readerRuntime.ts +0 -230
  109. package/src/openpress/workbench.tsx +0 -1265
  110. package/src/openpress/workbenchTypes.ts +0 -4
  111. /package/src/openpress/{readerPageRegistry.ts → reader/readerPageRegistry.ts} +0 -0
  112. /package/src/openpress/{pageRoute.ts → reader/readerPageRoute.ts} +0 -0
  113. /package/src/openpress/{readerScroll.ts → reader/readerScroll.ts} +0 -0
  114. /package/src/openpress/{readerState.ts → reader/readerStateModel.ts} +0 -0
  115. /package/src/openpress/{frameScheduler.ts → shared/frameScheduler.ts} +0 -0
  116. /package/src/openpress/{projectSources.ts → workbench/project/projectSourceModel.ts} +0 -0
@@ -0,0 +1,75 @@
1
+ import { loadConfig } from "../runtime/config.mjs";
2
+ import { applySourceBlockTextEdit, readSourceBlockText } from "../runtime/source-text-tools.mjs";
3
+ import { exportReactDocument } from "./document-export.mjs";
4
+ import { readJsonBody, writeJson } from "./http-json.mjs";
5
+
6
+ export async function handleSourceEditRequest(req, res, {
7
+ root = ".",
8
+ refreshDocument = true,
9
+ } = {}) {
10
+ if (req.method === "GET") {
11
+ try {
12
+ const requestUrl = new URL(req.url ?? "/", "http://localhost");
13
+ const config = await loadConfig(root);
14
+ const sourceText = await readSourceBlockText({
15
+ config,
16
+ path: requestUrl.searchParams.get("path"),
17
+ source: {
18
+ line: Number(requestUrl.searchParams.get("line")),
19
+ column: Number(requestUrl.searchParams.get("column") || 1),
20
+ endLine: Number(requestUrl.searchParams.get("endLine") || requestUrl.searchParams.get("line")),
21
+ endColumn: Number(requestUrl.searchParams.get("endColumn") || requestUrl.searchParams.get("column") || 1),
22
+ },
23
+ });
24
+ writeJson(res, 200, { ok: true, source: sourceText });
25
+ } catch (error) {
26
+ writeJson(res, 400, {
27
+ ok: false,
28
+ message: error instanceof Error ? error.message : String(error),
29
+ });
30
+ }
31
+ return;
32
+ }
33
+
34
+ if (req.method !== "POST") {
35
+ writeJson(res, 405, { ok: false, message: "OpenPress source edit endpoint requires GET or POST." });
36
+ return;
37
+ }
38
+
39
+ try {
40
+ const body = await readJsonBody(req, {
41
+ bodyLabel: "OpenPress source edit request",
42
+ maxBytes: 256 * 1024,
43
+ });
44
+ const config = await loadConfig(root);
45
+ const edit = await applySourceBlockTextEdit({
46
+ config,
47
+ path: body?.path,
48
+ source: body?.source,
49
+ text: body?.text,
50
+ kind: body?.kind,
51
+ name: body?.name,
52
+ blockId: body?.blockId,
53
+ sourceMode: body?.sourceMode === true,
54
+ });
55
+ const exported = refreshDocument && body?.refreshDocument !== false
56
+ ? await exportReactDocument(root, { syncAssets: false })
57
+ : null;
58
+
59
+ writeJson(res, 200, {
60
+ ok: true,
61
+ edit,
62
+ document: exported
63
+ ? {
64
+ path: exported.documentPath,
65
+ pageCount: exported.pageCount,
66
+ }
67
+ : undefined,
68
+ });
69
+ } catch (error) {
70
+ writeJson(res, 400, {
71
+ ok: false,
72
+ message: error instanceof Error ? error.message : String(error),
73
+ });
74
+ }
75
+ }
@@ -10,6 +10,7 @@
10
10
  import fs from "node:fs/promises";
11
11
  import path from "node:path";
12
12
  import React from "react";
13
+ import { documentRelativePath, resolveDocumentRelativePath } from "../../runtime/path-utils.mjs";
13
14
  import { compileMdx } from "../mdx-compile.mjs";
14
15
  import { createHeadingState, fallbackOutlineItems, headingAttributesForBlock } from "./heading-numbering.mjs";
15
16
 
@@ -103,6 +104,7 @@ async function resolveSource({ sourceId, descriptor, documentRoot, globalCompone
103
104
  kind: block.kind,
104
105
  name: block.name,
105
106
  text: block.text,
107
+ layout: block.layout,
106
108
  chainId,
107
109
  sectionSlug: section.slug,
108
110
  path: documentRelative(file.absolutePath, documentRoot),
@@ -323,6 +325,7 @@ function compileTocBlocks({ tocBlocks, chainId, blockIds, toc }) {
323
325
  {
324
326
  className,
325
327
  "data-openpress-block-id": block.id,
328
+ "data-openpress-object-id": createBlockObjectEntityId(block.id),
326
329
  "data-openpress-toc-entry": block.sectionSlug,
327
330
  },
328
331
  React.createElement(
@@ -352,6 +355,14 @@ function locateSection(renderData, chainId) {
352
355
  throw new Error(`No section found for chainId "${chainId}" in source "${renderData.sourceId}".`);
353
356
  }
354
357
 
358
+ function createObjectEntityId(kind, ...parts) {
359
+ return [kind, ...parts.map((part) => encodeURIComponent(String(part)))].join(":");
360
+ }
361
+
362
+ function createBlockObjectEntityId(blockId) {
363
+ return createObjectEntityId("mdx-block", blockId);
364
+ }
365
+
355
366
  // ---------------------------------------------------------------------------
356
367
  // Validation
357
368
  // ---------------------------------------------------------------------------
@@ -425,17 +436,4 @@ function deriveTitleFromDirName(name) {
425
436
  .join(" ");
426
437
  }
427
438
 
428
- function documentRelative(absolutePath, documentRoot) {
429
- return path.relative(documentRoot, absolutePath).split(path.sep).join("/");
430
- }
431
-
432
- function resolveDocumentRelativePath(documentRoot, rel, label) {
433
- if (typeof rel !== "string" || !rel.trim()) throw new Error(`${label} must be a non-empty document-relative path.`);
434
- if (rel.includes("..")) throw new Error(`${label} contains "..", rejected.`);
435
- const absolutePath = path.resolve(documentRoot, rel);
436
- const relCheck = path.relative(documentRoot, absolutePath);
437
- if (relCheck.startsWith("..") || path.isAbsolute(relCheck)) {
438
- throw new Error(`${label} escapes the document root.`);
439
- }
440
- return absolutePath;
441
- }
439
+ const documentRelative = documentRelativePath;
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { documentRelativePath } from "../runtime/path-utils.mjs";
3
4
 
4
5
  // Style discovery — only used to find per-section CSS files for the
5
6
  // section-folders preset. MDX content discovery lives in `sources/mdx-resolver`.
@@ -102,10 +103,6 @@ function pathRecord(absolutePath, documentRoot) {
102
103
  };
103
104
  }
104
105
 
105
- function documentRelativePath(absolutePath, documentRoot) {
106
- return path.relative(documentRoot, absolutePath).split(path.sep).join("/");
107
- }
108
-
109
106
  function compareSectionDirectories(a, b) {
110
107
  const left = sectionSortKey(a.name);
111
108
  const right = sectionSortKey(b.name);
@@ -0,0 +1,22 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ export async function walkFiles(directory, visit) {
5
+ let entries;
6
+ try {
7
+ entries = await fs.readdir(directory, { withFileTypes: true });
8
+ } catch (error) {
9
+ if (error?.code === "ENOENT") return;
10
+ throw error;
11
+ }
12
+
13
+ for (const entry of entries) {
14
+ if (entry.name.startsWith(".")) continue;
15
+ const absolutePath = path.join(directory, entry.name);
16
+ if (entry.isDirectory()) {
17
+ await walkFiles(absolutePath, visit);
18
+ } else if (entry.isFile()) {
19
+ await visit(absolutePath);
20
+ }
21
+ }
22
+ }
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { evaluateUrlWithChrome, stopChildProcess } from "../output/chrome-pdf.mjs";
4
4
  import { buildReactStatic, startStaticServer } from "../commands/_shared.mjs";
5
+ import { walkFiles } from "./file-walk.mjs";
5
6
  import { createIssue, createIssueReport } from "./issue-report.mjs";
6
7
  import { collectActiveContentFiles, resolveActiveSourceWorkspace } from "./source-workspace.mjs";
7
8
 
@@ -217,26 +218,6 @@ async function readMediaFiles(directory) {
217
218
  return files;
218
219
  }
219
220
 
220
- async function walkFiles(directory, visit) {
221
- let entries;
222
- try {
223
- entries = await fs.readdir(directory, { withFileTypes: true });
224
- } catch (error) {
225
- if (error?.code === "ENOENT") return;
226
- throw error;
227
- }
228
-
229
- for (const entry of entries) {
230
- if (entry.name.startsWith(".")) continue;
231
- const absolutePath = path.join(directory, entry.name);
232
- if (entry.isDirectory()) {
233
- await walkFiles(absolutePath, visit);
234
- } else if (entry.isFile()) {
235
- await visit(absolutePath);
236
- }
237
- }
238
- }
239
-
240
221
  function summarizeComponentUsage(contentFiles) {
241
222
  const usages = new Map();
242
223
  for (const file of contentFiles) {
@@ -0,0 +1,20 @@
1
+ import path from "node:path";
2
+
3
+ export function rootRelativePath(config, absolutePath) {
4
+ return path.relative(config.root, absolutePath).replaceAll("\\", "/");
5
+ }
6
+
7
+ export function documentRelativePath(absolutePath, documentRoot) {
8
+ return path.relative(documentRoot, absolutePath).split(path.sep).join("/");
9
+ }
10
+
11
+ export function resolveDocumentRelativePath(documentRoot, rel, label) {
12
+ if (typeof rel !== "string" || !rel.trim()) throw new Error(`${label} must be a non-empty document-relative path.`);
13
+ if (rel.includes("..")) throw new Error(`${label} contains "..", rejected.`);
14
+ const absolutePath = path.resolve(documentRoot, rel);
15
+ const relCheck = path.relative(documentRoot, absolutePath);
16
+ if (relCheck.startsWith("..") || path.isAbsolute(relCheck)) {
17
+ throw new Error(`${label} escapes the document root.`);
18
+ }
19
+ return absolutePath;
20
+ }
@@ -0,0 +1,102 @@
1
+ import type { ResolvedConfig } from "./config.mjs";
2
+
3
+ export type SourceTextScope = "content" | "all";
4
+
5
+ export type SourceTextMatch = {
6
+ id: string;
7
+ scope: string;
8
+ file: string;
9
+ path: string;
10
+ line: number;
11
+ column: number;
12
+ index: number;
13
+ text: string;
14
+ preview: string;
15
+ };
16
+
17
+ export type SourceTextFileSummary = {
18
+ scope: string;
19
+ file: string;
20
+ path: string;
21
+ matchCount: number;
22
+ };
23
+
24
+ export type SourceSearchReport = {
25
+ kind: "search";
26
+ query: string;
27
+ scope: SourceTextScope;
28
+ caseSensitive: boolean;
29
+ matchCount: number;
30
+ files: Array<SourceTextFileSummary>;
31
+ matches: Array<SourceTextMatch>;
32
+ };
33
+
34
+ export type SourceBlockTextEditInput = {
35
+ config: ResolvedConfig;
36
+ path: string;
37
+ source: {
38
+ line: number;
39
+ column?: number;
40
+ endLine?: number;
41
+ endColumn?: number;
42
+ };
43
+ text: string;
44
+ kind?: string;
45
+ name?: string;
46
+ blockId?: string;
47
+ cellIndex?: number;
48
+ sourceMode?: boolean;
49
+ };
50
+
51
+ export type SourceBlockTextEdit = {
52
+ blockId?: string;
53
+ path: string;
54
+ requestedPath: string;
55
+ file: string;
56
+ line: number;
57
+ column: number;
58
+ endLine: number;
59
+ endColumn: number;
60
+ before: string;
61
+ after: string;
62
+ text: string;
63
+ cellIndex?: number;
64
+ };
65
+
66
+ export function searchSourceText(options: {
67
+ config: ResolvedConfig;
68
+ query: string;
69
+ scope?: SourceTextScope;
70
+ caseSensitive?: boolean;
71
+ }): Promise<SourceSearchReport>;
72
+
73
+ export function applySourceBlockTextEdit(options: SourceBlockTextEditInput): Promise<SourceBlockTextEdit>;
74
+
75
+ export function applySourceBlockTextEditToText(documentText: string, options: Omit<SourceBlockTextEditInput, "config" | "path">): {
76
+ text: string;
77
+ edit: Omit<SourceBlockTextEdit, "path" | "requestedPath" | "file">;
78
+ };
79
+
80
+ export function readSourceBlockText(options: Pick<SourceBlockTextEditInput, "config" | "path" | "source">): Promise<{
81
+ path: string;
82
+ requestedPath: string;
83
+ file: string;
84
+ text: string;
85
+ }>;
86
+
87
+ export function readSourceBlockTextFromText(documentText: string, options: Pick<SourceBlockTextEditInput, "source">): string;
88
+
89
+ export function applySourceBlockSourceEditToText(documentText: string, options: Pick<SourceBlockTextEditInput, "source" | "text" | "blockId">): {
90
+ text: string;
91
+ edit: Omit<SourceBlockTextEdit, "path" | "requestedPath" | "file">;
92
+ };
93
+
94
+ export function collectSourceTextFiles(config: ResolvedConfig, options?: {
95
+ scope?: SourceTextScope;
96
+ }): Promise<Array<{
97
+ scope: string;
98
+ name: string;
99
+ absolutePath: string;
100
+ relativePath: string;
101
+ text: string;
102
+ }>>;