@open-press/cli 0.8.0 → 1.1.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 (250) hide show
  1. package/README.md +33 -23
  2. package/dist/cli.js +320 -252
  3. package/package.json +9 -8
  4. package/template/core/AGENTS.md +0 -126
  5. package/template/core/CHANGELOG.md +0 -215
  6. package/template/core/README.md +0 -40
  7. package/template/core/engine/cli.mjs +0 -96
  8. package/template/core/engine/commands/_shared.mjs +0 -177
  9. package/template/core/engine/commands/deploy.mjs +0 -31
  10. package/template/core/engine/commands/dev.mjs +0 -49
  11. package/template/core/engine/commands/doctor.mjs +0 -229
  12. package/template/core/engine/commands/export.mjs +0 -8
  13. package/template/core/engine/commands/init.mjs +0 -24
  14. package/template/core/engine/commands/inspect.mjs +0 -35
  15. package/template/core/engine/commands/pdf.mjs +0 -26
  16. package/template/core/engine/commands/preview.mjs +0 -26
  17. package/template/core/engine/commands/render.mjs +0 -17
  18. package/template/core/engine/commands/replace.mjs +0 -41
  19. package/template/core/engine/commands/search.mjs +0 -33
  20. package/template/core/engine/commands/typecheck.mjs +0 -5
  21. package/template/core/engine/commands/upgrade.mjs +0 -159
  22. package/template/core/engine/commands/validate.mjs +0 -17
  23. package/template/core/engine/document-export.mjs +0 -15
  24. package/template/core/engine/init.mjs +0 -90
  25. package/template/core/engine/output/chrome-pdf.d.mts +0 -34
  26. package/template/core/engine/output/chrome-pdf.mjs +0 -358
  27. package/template/core/engine/output/deploy-sync.mjs +0 -15
  28. package/template/core/engine/output/fonts.mjs +0 -62
  29. package/template/core/engine/output/katex-assets.mjs +0 -45
  30. package/template/core/engine/output/page-block.mjs +0 -30
  31. package/template/core/engine/output/pdf-media.mjs +0 -45
  32. package/template/core/engine/output/public-assets.mjs +0 -19
  33. package/template/core/engine/output/static-server.mjs +0 -532
  34. package/template/core/engine/react/caption-numbering.mjs +0 -73
  35. package/template/core/engine/react/comment-endpoint.d.mts +0 -11
  36. package/template/core/engine/react/comment-endpoint.mjs +0 -102
  37. package/template/core/engine/react/comment-marker.mjs +0 -374
  38. package/template/core/engine/react/document-entry.mjs +0 -324
  39. package/template/core/engine/react/document-export.mjs +0 -373
  40. package/template/core/engine/react/http-json.mjs +0 -24
  41. package/template/core/engine/react/mdx-compile.mjs +0 -599
  42. package/template/core/engine/react/measurement-css.mjs +0 -136
  43. package/template/core/engine/react/object-entities.mjs +0 -119
  44. package/template/core/engine/react/pagination/allocator.mjs +0 -122
  45. package/template/core/engine/react/pagination/regions.mjs +0 -81
  46. package/template/core/engine/react/pagination-constants.mjs +0 -3
  47. package/template/core/engine/react/pagination.mjs +0 -9
  48. package/template/core/engine/react/pipeline/allocate.mjs +0 -251
  49. package/template/core/engine/react/pipeline/final-render.mjs +0 -94
  50. package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -302
  51. package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
  52. package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
  53. package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
  54. package/template/core/engine/react/section-css.mjs +0 -56
  55. package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
  56. package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
  57. package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
  58. package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
  59. package/template/core/engine/react/style-discovery.mjs +0 -142
  60. package/template/core/engine/runtime/config.d.mts +0 -40
  61. package/template/core/engine/runtime/config.mjs +0 -175
  62. package/template/core/engine/runtime/file-utils.mjs +0 -106
  63. package/template/core/engine/runtime/file-walk.mjs +0 -22
  64. package/template/core/engine/runtime/inspection.mjs +0 -328
  65. package/template/core/engine/runtime/issue-report.mjs +0 -44
  66. package/template/core/engine/runtime/path-utils.mjs +0 -20
  67. package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
  68. package/template/core/engine/runtime/source-text-tools.mjs +0 -832
  69. package/template/core/engine/runtime/source-workspace.mjs +0 -159
  70. package/template/core/engine/runtime/validation.mjs +0 -174
  71. package/template/core/index.html +0 -13
  72. package/template/core/openpress.config.mjs +0 -12
  73. package/template/core/package.json +0 -91
  74. package/template/core/src/main.tsx +0 -16
  75. package/template/core/src/openpress/app/OpenPressApp.tsx +0 -140
  76. package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -94
  77. package/template/core/src/openpress/app/index.ts +0 -2
  78. package/template/core/src/openpress/core/Frame.tsx +0 -78
  79. package/template/core/src/openpress/core/FrameContext.tsx +0 -24
  80. package/template/core/src/openpress/core/MdxArea.tsx +0 -34
  81. package/template/core/src/openpress/core/Press.tsx +0 -34
  82. package/template/core/src/openpress/core/cn.ts +0 -4
  83. package/template/core/src/openpress/core/index.tsx +0 -40
  84. package/template/core/src/openpress/core/primitives.tsx +0 -44
  85. package/template/core/src/openpress/core/types.ts +0 -191
  86. package/template/core/src/openpress/core/useSource.ts +0 -28
  87. package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
  88. package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
  89. package/template/core/src/openpress/document-model/documentTypes.ts +0 -138
  90. package/template/core/src/openpress/document-model/index.ts +0 -6
  91. package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -51
  92. package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
  93. package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
  94. package/template/core/src/openpress/manuscript/index.tsx +0 -238
  95. package/template/core/src/openpress/mdx/index.ts +0 -88
  96. package/template/core/src/openpress/numbering/index.ts +0 -294
  97. package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
  98. package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
  99. package/template/core/src/openpress/reader/index.ts +0 -10
  100. package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
  101. package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
  102. package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
  103. package/template/core/src/openpress/reader/readerScroll.ts +0 -92
  104. package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
  105. package/template/core/src/openpress/reader/readerTypes.ts +0 -4
  106. package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
  107. package/template/core/src/openpress/reader/usePanelState.ts +0 -56
  108. package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
  109. package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
  110. package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
  111. package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
  112. package/template/core/src/openpress/shared/Panel.tsx +0 -77
  113. package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
  114. package/template/core/src/openpress/shared/index.ts +0 -4
  115. package/template/core/src/openpress/shared/numberUtils.ts +0 -3
  116. package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
  117. package/template/core/src/openpress/workbench/Workbench.tsx +0 -407
  118. package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
  119. package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
  120. package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
  121. package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
  122. package/template/core/src/openpress/workbench/actions/index.ts +0 -5
  123. package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
  124. package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
  125. package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
  126. package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
  127. package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
  128. package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
  129. package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
  130. package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
  131. package/template/core/src/openpress/workbench/document/index.ts +0 -10
  132. package/template/core/src/openpress/workbench/index.ts +0 -2
  133. package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
  134. package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
  135. package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
  136. package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
  137. package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
  138. package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -248
  139. package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
  140. package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
  141. package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
  142. package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
  143. package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -76
  144. package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
  145. package/template/core/src/openpress/workbench/panels/index.ts +0 -3
  146. package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -523
  147. package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
  148. package/template/core/src/openpress/workbench/project/index.ts +0 -2
  149. package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
  150. package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
  151. package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
  152. package/template/core/src/openpress/workbench/shell/index.ts +0 -1
  153. package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
  154. package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
  155. package/template/core/src/styles/openpress/app-shell.css +0 -251
  156. package/template/core/src/styles/openpress/media-workspace.css +0 -230
  157. package/template/core/src/styles/openpress/print-route.css +0 -184
  158. package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
  159. package/template/core/src/styles/openpress/public-viewer.css +0 -688
  160. package/template/core/src/styles/openpress/reader-runtime.css +0 -980
  161. package/template/core/src/styles/openpress/responsive.css +0 -245
  162. package/template/core/src/styles/openpress/workbench-panels.css +0 -594
  163. package/template/core/src/styles/openpress/workbench.css +0 -1255
  164. package/template/core/src/styles/openpress.css +0 -14
  165. package/template/core/src/vite-env.d.ts +0 -9
  166. package/template/core/tsconfig.json +0 -40
  167. package/template/core/vite.config.ts +0 -584
  168. package/template/packs/academic-paper/document/chapters/01-introduction/content/01-introduction.mdx +0 -35
  169. package/template/packs/academic-paper/document/chapters/02-methods/content/01-methods.mdx +0 -50
  170. package/template/packs/academic-paper/document/chapters/03-results-and-discussion/content/01-results.mdx +0 -47
  171. package/template/packs/academic-paper/document/chapters/04-acknowledgment/content/01-acknowledgment.mdx +0 -26
  172. package/template/packs/academic-paper/document/chapters/05-references/content/01-references.mdx +0 -32
  173. package/template/packs/academic-paper/document/components/ChapterOpenerVisual/index.tsx +0 -76
  174. package/template/packs/academic-paper/document/components/Page.tsx +0 -60
  175. package/template/packs/academic-paper/document/components/TokenSwatchGrid/index.tsx +0 -46
  176. package/template/packs/academic-paper/document/components/TokenSwatchGrid/style.css +0 -63
  177. package/template/packs/academic-paper/document/components/TypeSpecimen/index.tsx +0 -38
  178. package/template/packs/academic-paper/document/components/TypeSpecimen/style.css +0 -111
  179. package/template/packs/academic-paper/document/design.md +0 -279
  180. package/template/packs/academic-paper/document/index.tsx +0 -123
  181. package/template/packs/academic-paper/document/media/README.md +0 -13
  182. package/template/packs/academic-paper/document/media/figure-placeholder.svg +0 -9
  183. package/template/packs/academic-paper/document/openpress.config.mjs +0 -26
  184. package/template/packs/academic-paper/document/theme/README.md +0 -11
  185. package/template/packs/academic-paper/document/theme/base/page-contract.css +0 -522
  186. package/template/packs/academic-paper/document/theme/base/print.css +0 -93
  187. package/template/packs/academic-paper/document/theme/base/typography.css +0 -333
  188. package/template/packs/academic-paper/document/theme/fonts.css +0 -3
  189. package/template/packs/academic-paper/document/theme/page-surfaces/back-cover.css +0 -43
  190. package/template/packs/academic-paper/document/theme/page-surfaces/chapter-opener.css +0 -205
  191. package/template/packs/academic-paper/document/theme/page-surfaces/cover.css +0 -294
  192. package/template/packs/academic-paper/document/theme/page-surfaces/toc.css +0 -149
  193. package/template/packs/academic-paper/document/theme/patterns/_chart-frame.css +0 -49
  194. package/template/packs/academic-paper/document/theme/patterns/figure-grid.css +0 -68
  195. package/template/packs/academic-paper/document/theme/patterns/table-utilities.css +0 -66
  196. package/template/packs/academic-paper/document/theme/shell/reader-controls.css +0 -761
  197. package/template/packs/academic-paper/document/theme/tokens.css +0 -80
  198. package/template/packs/academic-paper/openpress.config.mjs +0 -5
  199. package/template/packs/claude-document/document/chapters/01-document-shape/content/01-document-shape.mdx +0 -51
  200. package/template/packs/claude-document/document/chapters/02-review-loop/content/01-review-loop.mdx +0 -31
  201. package/template/packs/claude-document/document/components/ChapterOpenerVisual.tsx +0 -96
  202. package/template/packs/claude-document/document/components/Page.tsx +0 -37
  203. package/template/packs/claude-document/document/design.md +0 -142
  204. package/template/packs/claude-document/document/index.tsx +0 -94
  205. package/template/packs/claude-document/document/media/README.md +0 -13
  206. package/template/packs/claude-document/document/openpress.config.mjs +0 -26
  207. package/template/packs/claude-document/document/theme/README.md +0 -15
  208. package/template/packs/claude-document/document/theme/base/page-contract.css +0 -525
  209. package/template/packs/claude-document/document/theme/base/print.css +0 -93
  210. package/template/packs/claude-document/document/theme/base/typography.css +0 -612
  211. package/template/packs/claude-document/document/theme/fonts.css +0 -4
  212. package/template/packs/claude-document/document/theme/page-surfaces/back-cover.css +0 -72
  213. package/template/packs/claude-document/document/theme/page-surfaces/chapter-opener.css +0 -236
  214. package/template/packs/claude-document/document/theme/page-surfaces/cover.css +0 -309
  215. package/template/packs/claude-document/document/theme/page-surfaces/toc.css +0 -225
  216. package/template/packs/claude-document/document/theme/patterns/_chart-frame.css +0 -53
  217. package/template/packs/claude-document/document/theme/patterns/figure-grid.css +0 -68
  218. package/template/packs/claude-document/document/theme/patterns/table-utilities.css +0 -66
  219. package/template/packs/claude-document/document/theme/shell/reader-controls.css +0 -789
  220. package/template/packs/claude-document/document/theme/tokens.css +0 -89
  221. package/template/packs/claude-document/openpress.config.mjs +0 -5
  222. package/template/packs/editorial-monograph/document/chapters/01-product-and-use-cases/content/01-product-and-use-cases.mdx +0 -31
  223. package/template/packs/editorial-monograph/document/chapters/02-workflow/content/01-workflow.mdx +0 -89
  224. package/template/packs/editorial-monograph/document/chapters/03-agent-skills-contributors/content/01-agent-skills-contributors.mdx +0 -51
  225. package/template/packs/editorial-monograph/document/chapters/04-validation-deploy/content/01-validation-deploy.mdx +0 -39
  226. package/template/packs/editorial-monograph/document/components/ChapterOpenerVisual/index.tsx +0 -76
  227. package/template/packs/editorial-monograph/document/components/Page.tsx +0 -37
  228. package/template/packs/editorial-monograph/document/components/TokenSwatchGrid/index.tsx +0 -46
  229. package/template/packs/editorial-monograph/document/components/TokenSwatchGrid/style.css +0 -63
  230. package/template/packs/editorial-monograph/document/components/TypeSpecimen/index.tsx +0 -38
  231. package/template/packs/editorial-monograph/document/components/TypeSpecimen/style.css +0 -111
  232. package/template/packs/editorial-monograph/document/design.md +0 -279
  233. package/template/packs/editorial-monograph/document/index.tsx +0 -97
  234. package/template/packs/editorial-monograph/document/media/README.md +0 -13
  235. package/template/packs/editorial-monograph/document/openpress.config.mjs +0 -26
  236. package/template/packs/editorial-monograph/document/theme/README.md +0 -11
  237. package/template/packs/editorial-monograph/document/theme/base/page-contract.css +0 -505
  238. package/template/packs/editorial-monograph/document/theme/base/print.css +0 -93
  239. package/template/packs/editorial-monograph/document/theme/base/typography.css +0 -336
  240. package/template/packs/editorial-monograph/document/theme/fonts.css +0 -3
  241. package/template/packs/editorial-monograph/document/theme/page-surfaces/back-cover.css +0 -43
  242. package/template/packs/editorial-monograph/document/theme/page-surfaces/chapter-opener.css +0 -205
  243. package/template/packs/editorial-monograph/document/theme/page-surfaces/cover.css +0 -147
  244. package/template/packs/editorial-monograph/document/theme/page-surfaces/toc.css +0 -149
  245. package/template/packs/editorial-monograph/document/theme/patterns/_chart-frame.css +0 -49
  246. package/template/packs/editorial-monograph/document/theme/patterns/figure-grid.css +0 -68
  247. package/template/packs/editorial-monograph/document/theme/patterns/table-utilities.css +0 -66
  248. package/template/packs/editorial-monograph/document/theme/shell/reader-controls.css +0 -761
  249. package/template/packs/editorial-monograph/document/theme/tokens.css +0 -80
  250. package/template/packs/editorial-monograph/openpress.config.mjs +0 -5
@@ -1,832 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { walkFiles } from "./file-walk.mjs";
4
- import { resolveActiveSourceWorkspace } from "./source-workspace.mjs";
5
-
6
- const MARKDOWN_EXTENSIONS = new Set([".md"]);
7
- const ALL_SOURCE_EXTENSIONS = new Set([".css", ".html", ".js", ".json", ".md", ".mdx", ".mjs", ".ts", ".tsx"]);
8
- const REACT_IMPLEMENTATION_EXTENSIONS = new Set([".css", ".html", ".js", ".json", ".mjs", ".ts", ".tsx"]);
9
-
10
- export async function searchSourceText({ config, query, scope = "content", caseSensitive = false }) {
11
- const files = await collectSourceTextFiles(config, { scope });
12
- const rawMatches = [];
13
- for (const file of files) {
14
- rawMatches.push(...findLiteralMatches(file.text, query, { caseSensitive }).map((match) => ({
15
- ...match,
16
- scope: file.scope,
17
- file: file.name,
18
- path: file.relativePath,
19
- })));
20
- }
21
- const matches = rawMatches.map((match, index) => ({
22
- ...match,
23
- id: `match-${String(index + 1).padStart(4, "0")}`,
24
- }));
25
-
26
- return {
27
- kind: "search",
28
- query,
29
- scope,
30
- caseSensitive,
31
- matchCount: matches.length,
32
- files: summarizeFiles(matches),
33
- matches,
34
- };
35
- }
36
-
37
- export async function replaceSourceText({
38
- config,
39
- from,
40
- to,
41
- scope = "content",
42
- caseSensitive = false,
43
- includeCode = false,
44
- apply = false,
45
- }) {
46
- const files = await collectSourceTextFiles(config, { scope });
47
- const changes = [];
48
- let matchCount = 0;
49
-
50
- for (const file of files) {
51
- const result = replaceLiteralMatches(file.text, from, to, { caseSensitive, includeCode });
52
- if (result.replacements.length === 0) continue;
53
- matchCount += result.replacements.length;
54
- changes.push({
55
- scope: file.scope,
56
- file: file.name,
57
- path: file.relativePath,
58
- absolutePath: file.absolutePath,
59
- replacements: result.replacements.map((replacement, index) => ({
60
- ...replacement,
61
- id: `replace-${String(matchCount - result.replacements.length + index + 1).padStart(4, "0")}`,
62
- })),
63
- });
64
- if (apply) {
65
- await fs.writeFile(file.absolutePath, result.text, "utf8");
66
- }
67
- }
68
-
69
- return {
70
- kind: "replace",
71
- from,
72
- to,
73
- scope,
74
- caseSensitive,
75
- includeCode,
76
- applied: apply,
77
- matchCount,
78
- fileCount: changes.length,
79
- changes,
80
- };
81
- }
82
-
83
- export async function applySourceBlockTextEdit({
84
- config,
85
- path: sourcePath,
86
- source,
87
- text,
88
- kind,
89
- name,
90
- blockId,
91
- cellIndex,
92
- sourceMode = false,
93
- }) {
94
- const requestedPath = stringValue(sourcePath);
95
- if (!requestedPath) throw new Error("Source edit requires a source path.");
96
- const files = await collectSourceTextFiles(config, { scope: "content" });
97
- const file = files.find((candidate) => sourceTextPathMatches(candidate.relativePath, requestedPath));
98
- if (!file) throw new Error(`Editable source file not found: ${requestedPath}`);
99
-
100
- const result = sourceMode
101
- ? applySourceBlockSourceEditToText(file.text, { source, text, blockId })
102
- : applySourceBlockTextEditToText(file.text, {
103
- source,
104
- text,
105
- kind,
106
- name,
107
- blockId,
108
- cellIndex,
109
- });
110
- await fs.writeFile(file.absolutePath, result.text, "utf8");
111
-
112
- return {
113
- ...result.edit,
114
- path: file.relativePath,
115
- requestedPath,
116
- file: file.name,
117
- };
118
- }
119
-
120
- export async function readSourceBlockText({ config, path: sourcePath, source }) {
121
- const requestedPath = stringValue(sourcePath);
122
- if (!requestedPath) throw new Error("Source read requires a source path.");
123
- const files = await collectSourceTextFiles(config, { scope: "content" });
124
- const file = files.find((candidate) => sourceTextPathMatches(candidate.relativePath, requestedPath));
125
- if (!file) throw new Error(`Editable source file not found: ${requestedPath}`);
126
- return {
127
- path: file.relativePath,
128
- requestedPath,
129
- file: file.name,
130
- text: readSourceBlockTextFromText(file.text, { source }),
131
- };
132
- }
133
-
134
- export function readSourceBlockTextFromText(documentText, { source } = {}) {
135
- const sourceRange = normalizeSourceRange(source);
136
- const lines = splitTextLines(documentText);
137
- const startIndex = sourceRange.line - 1;
138
- const endIndex = sourceRange.endLine - 1;
139
- if (!lines[startIndex]) throw new Error(`Source read line ${sourceRange.line} is outside the source file.`);
140
- if (!lines[endIndex]) throw new Error(`Source read end line ${sourceRange.endLine} is outside the source file.`);
141
- return lines.slice(startIndex, endIndex + 1).map((line) => line.line).join("\n");
142
- }
143
-
144
- export function applySourceBlockSourceEditToText(documentText, {
145
- source,
146
- text,
147
- blockId,
148
- } = {}) {
149
- const sourceRange = normalizeSourceRange(source);
150
- const replacementText = normalizeRawSourceText(text);
151
- const lines = splitTextLines(documentText);
152
- const startIndex = sourceRange.line - 1;
153
- const endIndex = sourceRange.endLine - 1;
154
- if (!lines[startIndex]) throw new Error(`Source edit line ${sourceRange.line} is outside the source file.`);
155
- if (!lines[endIndex]) throw new Error(`Source edit end line ${sourceRange.endLine} is outside the source file.`);
156
-
157
- const selectedLines = lines.slice(startIndex, endIndex + 1);
158
- const replacementLines = replacementText.split("\n");
159
- const ending = selectedLines[selectedLines.length - 1].ending;
160
- const nextLines = [
161
- ...lines.slice(0, startIndex),
162
- ...replacementLines.map((line, index) => ({
163
- line,
164
- ending: index === replacementLines.length - 1 ? ending : "\n",
165
- })),
166
- ...lines.slice(endIndex + 1),
167
- ];
168
-
169
- return {
170
- text: joinTextLines(nextLines),
171
- edit: {
172
- blockId,
173
- line: sourceRange.line,
174
- column: sourceRange.column,
175
- endLine: sourceRange.endLine,
176
- endColumn: sourceRange.endColumn,
177
- before: selectedLines.map((line) => line.line).join("\n"),
178
- after: replacementText,
179
- text: replacementText,
180
- },
181
- };
182
- }
183
-
184
- export function applySourceBlockTextEditToText(documentText, {
185
- source,
186
- text,
187
- kind,
188
- name,
189
- blockId,
190
- cellIndex,
191
- } = {}) {
192
- if (kind === "table-cell") {
193
- return applySourceBlockTableCellEditToText(documentText, {
194
- source,
195
- text,
196
- blockId,
197
- cellIndex,
198
- });
199
- }
200
- if (kind === "component-caption") {
201
- return applySourceBlockComponentCaptionEditToText(documentText, {
202
- source,
203
- text,
204
- blockId,
205
- name,
206
- });
207
- }
208
- if (kind === "element" && name === "pre") {
209
- return applySourceBlockCodeEditToText(documentText, {
210
- source,
211
- text,
212
- blockId,
213
- });
214
- }
215
- if (kind === "element" && (name === "caption" || name === "figcaption")) {
216
- return applySourceBlockCaptionEditToText(documentText, {
217
- source,
218
- text,
219
- blockId,
220
- name,
221
- });
222
- }
223
-
224
- assertEditableSourceBlock({ kind, name, blockId });
225
- const sourceRange = normalizeSourceRange(source);
226
- const replacementText = normalizeEditedText(text);
227
- const lines = splitTextLines(documentText);
228
- const startIndex = sourceRange.line - 1;
229
- const endIndex = sourceRange.endLine - 1;
230
-
231
- if (!lines[startIndex]) {
232
- throw new Error(`Source edit line ${sourceRange.line} is outside the source file.`);
233
- }
234
- if (!lines[endIndex]) {
235
- throw new Error(`Source edit end line ${sourceRange.endLine} is outside the source file.`);
236
- }
237
-
238
- const selectedLines = lines.slice(startIndex, endIndex + 1);
239
- const firstLine = selectedLines[0].line;
240
- if (!isEditableMarkdownLine(firstLine, { kind, name })) {
241
- throw new Error("Only rendered text blocks can be edited from the document surface.");
242
- }
243
-
244
- const prefix = markdownTextPrefix(firstLine, { kind, name });
245
- const after = `${prefix}${replacementText}`;
246
- const ending = selectedLines[selectedLines.length - 1].ending;
247
- const nextLines = [
248
- ...lines.slice(0, startIndex),
249
- { line: after, ending },
250
- ...lines.slice(endIndex + 1),
251
- ];
252
-
253
- return {
254
- text: joinTextLines(nextLines),
255
- edit: {
256
- blockId,
257
- line: sourceRange.line,
258
- column: sourceRange.column,
259
- endLine: sourceRange.endLine,
260
- endColumn: sourceRange.endColumn,
261
- before: selectedLines.map((line) => line.line).join("\n"),
262
- after,
263
- text: replacementText,
264
- },
265
- };
266
- }
267
-
268
- function applySourceBlockCodeEditToText(documentText, {
269
- source,
270
- text,
271
- blockId,
272
- } = {}) {
273
- const sourceRange = normalizeSourceRange(source);
274
- const replacementText = normalizeCodeBlockText(text);
275
- const lines = splitTextLines(documentText);
276
- const startIndex = sourceRange.line - 1;
277
- const endIndex = sourceRange.endLine - 1;
278
-
279
- if (!lines[startIndex]) throw new Error(`Source edit line ${sourceRange.line} is outside the source file.`);
280
- if (!lines[endIndex]) throw new Error(`Source edit end line ${sourceRange.endLine} is outside the source file.`);
281
-
282
- const selectedLines = lines.slice(startIndex, endIndex + 1);
283
- const openingFence = selectedLines[0]?.line ?? "";
284
- const closingFence = selectedLines.at(-1)?.line ?? "";
285
- if (selectedLines.length < 2 || !isFenceLine(openingFence) || !isFenceLine(closingFence)) {
286
- throw new Error("Code block edits require a fenced markdown code block source range.");
287
- }
288
-
289
- const replacementLines = replacementText ? replacementText.split("\n") : [];
290
- const afterLines = [openingFence, ...replacementLines, closingFence];
291
- const ending = selectedLines[selectedLines.length - 1].ending;
292
- const nextLines = [
293
- ...lines.slice(0, startIndex),
294
- ...afterLines.map((line, index) => ({
295
- line,
296
- ending: index === afterLines.length - 1 ? ending : "\n",
297
- })),
298
- ...lines.slice(endIndex + 1),
299
- ];
300
- const after = afterLines.join("\n");
301
-
302
- return {
303
- text: joinTextLines(nextLines),
304
- edit: {
305
- blockId,
306
- line: sourceRange.line,
307
- column: sourceRange.column,
308
- endLine: sourceRange.endLine,
309
- endColumn: sourceRange.endColumn,
310
- before: selectedLines.map((line) => line.line).join("\n"),
311
- after,
312
- text: replacementText,
313
- },
314
- };
315
- }
316
-
317
- function applySourceBlockCaptionEditToText(documentText, {
318
- source,
319
- text,
320
- blockId,
321
- name,
322
- } = {}) {
323
- const sourceRange = normalizeSourceRange(source);
324
- const replacementText = normalizeEditedText(text);
325
- const lines = splitTextLines(documentText);
326
- const startIndex = sourceRange.line - 1;
327
- const endIndex = sourceRange.endLine - 1;
328
-
329
- if (!lines[startIndex]) throw new Error(`Source edit line ${sourceRange.line} is outside the source file.`);
330
- if (!lines[endIndex]) throw new Error(`Source edit end line ${sourceRange.endLine} is outside the source file.`);
331
-
332
- const selectedLines = lines.slice(startIndex, endIndex + 1);
333
- const before = selectedLines.map((line) => line.line).join("\n");
334
- const after = replaceCaptionText(before, replacementText, name);
335
- const replacementLines = after.split("\n");
336
- const ending = selectedLines[selectedLines.length - 1].ending;
337
- const nextLines = [
338
- ...lines.slice(0, startIndex),
339
- ...replacementLines.map((line, index) => ({
340
- line,
341
- ending: index === replacementLines.length - 1 ? ending : "\n",
342
- })),
343
- ...lines.slice(endIndex + 1),
344
- ];
345
-
346
- return {
347
- text: joinTextLines(nextLines),
348
- edit: {
349
- blockId,
350
- line: sourceRange.line,
351
- column: sourceRange.column,
352
- endLine: sourceRange.endLine,
353
- endColumn: sourceRange.endColumn,
354
- before,
355
- after,
356
- text: replacementText,
357
- },
358
- };
359
- }
360
-
361
- function applySourceBlockTableCellEditToText(documentText, {
362
- source,
363
- text,
364
- blockId,
365
- cellIndex,
366
- } = {}) {
367
- const sourceRange = normalizeSourceRange(source);
368
- if (sourceRange.endLine !== sourceRange.line) {
369
- throw new Error("Table cell edits must target a single markdown table row.");
370
- }
371
- const replacementText = normalizeEditedText(text);
372
- const targetCellIndex = nonNegativeInteger(cellIndex, "cellIndex");
373
- const lines = splitTextLines(documentText);
374
- const rowIndex = sourceRange.line - 1;
375
- const row = lines[rowIndex];
376
- if (!row) throw new Error(`Source edit line ${sourceRange.line} is outside the source file.`);
377
- if (!isMarkdownTableRow(row.line)) {
378
- throw new Error("Table cell edits require a markdown table row source line.");
379
- }
380
-
381
- const after = replaceMarkdownTableCell(row.line, targetCellIndex, replacementText);
382
- const nextLines = [
383
- ...lines.slice(0, rowIndex),
384
- { line: after, ending: row.ending },
385
- ...lines.slice(rowIndex + 1),
386
- ];
387
-
388
- return {
389
- text: joinTextLines(nextLines),
390
- edit: {
391
- blockId,
392
- line: sourceRange.line,
393
- column: sourceRange.column,
394
- endLine: sourceRange.endLine,
395
- endColumn: sourceRange.endColumn,
396
- before: row.line,
397
- after,
398
- text: replacementText,
399
- cellIndex: targetCellIndex,
400
- },
401
- };
402
- }
403
-
404
- export async function collectSourceTextFiles(config, { scope = "content" } = {}) {
405
- const roots = await sourceRoots(config, scope);
406
- const files = [];
407
- for (const rootInfo of roots) {
408
- const visit = async (absolutePath) => {
409
- const extension = path.extname(absolutePath);
410
- if (!rootInfo.extensions.has(extension)) return;
411
- const relativePath = path.relative(config.root, absolutePath).replaceAll("\\", "/");
412
- files.push({
413
- scope: rootInfo.scope,
414
- name: path.basename(absolutePath),
415
- absolutePath,
416
- relativePath,
417
- text: await fs.readFile(absolutePath, "utf8"),
418
- });
419
- };
420
- if (rootInfo.kind === "file") {
421
- try {
422
- await fs.access(rootInfo.absolutePath);
423
- await visit(rootInfo.absolutePath);
424
- } catch (error) {
425
- if (error?.code !== "ENOENT") throw error;
426
- }
427
- } else {
428
- await walkFiles(rootInfo.absolutePath, visit);
429
- }
430
- }
431
- files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
432
- return files;
433
- }
434
-
435
- export function findLiteralMatches(text, query, { caseSensitive = false } = {}) {
436
- if (!query) return [];
437
- const matches = [];
438
- forEachLine(text, ({ line, lineNumber, lineOffset }) => {
439
- for (const range of findLineMatches(line, query, { caseSensitive })) {
440
- matches.push({
441
- line: lineNumber,
442
- column: range.start + 1,
443
- index: lineOffset + range.start,
444
- text: line.slice(range.start, range.end),
445
- preview: previewLine(line, range.start, range.end),
446
- });
447
- }
448
- });
449
- return matches;
450
- }
451
-
452
- export function replaceLiteralMatches(text, from, to, { caseSensitive = false, includeCode = false } = {}) {
453
- if (!from) return { text, replacements: [] };
454
- let output = "";
455
- const replacements = [];
456
- let inFence = false;
457
-
458
- forEachLine(text, ({ line, ending, lineNumber, lineOffset }) => {
459
- const fenceLine = isFenceLine(line);
460
- const replaceLine = includeCode || (!inFence && !fenceLine);
461
- if (!replaceLine) {
462
- output += line + ending;
463
- if (fenceLine) inFence = !inFence;
464
- return;
465
- }
466
-
467
- const ranges = findLineMatches(line, from, { caseSensitive });
468
- if (ranges.length === 0) {
469
- output += line + ending;
470
- if (fenceLine) inFence = !inFence;
471
- return;
472
- }
473
-
474
- let nextLine = "";
475
- let cursor = 0;
476
- for (const range of ranges) {
477
- nextLine += line.slice(cursor, range.start) + to;
478
- replacements.push({
479
- line: lineNumber,
480
- column: range.start + 1,
481
- index: lineOffset + range.start,
482
- before: line,
483
- after: replaceRangesInLine(line, ranges, to),
484
- text: line.slice(range.start, range.end),
485
- replacement: to,
486
- previewBefore: previewLine(line, range.start, range.end),
487
- previewAfter: previewLine(replaceRangesInLine(line, ranges, to), range.start, range.start + to.length),
488
- });
489
- cursor = range.end;
490
- }
491
- nextLine += line.slice(cursor);
492
- output += nextLine + ending;
493
- if (fenceLine) inFence = !inFence;
494
- });
495
-
496
- return { text: output, replacements };
497
- }
498
-
499
- async function sourceRoots(config, scope) {
500
- const sourceWorkspace = await resolveActiveSourceWorkspace(config);
501
- const sourceConfig = sourceWorkspace.config;
502
- const contentRoots = (sourceWorkspace.contentRoots ?? [{ kind: "dir", absolutePath: sourceWorkspace.sourceDir }]).map((root) => ({
503
- scope: "content",
504
- kind: root.kind,
505
- absolutePath: root.absolutePath,
506
- extensions: sourceWorkspace.contentExtensions,
507
- }));
508
-
509
- if (scope === "all") {
510
- const roots = [
511
- ...contentRoots,
512
- { scope: "design-doc", kind: "file", absolutePath: sourceConfig.paths.designDoc, extensions: MARKDOWN_EXTENSIONS },
513
- { scope: "components", kind: "dir", absolutePath: sourceConfig.paths.componentsDir, extensions: ALL_SOURCE_EXTENSIONS },
514
- { scope: "document-entry", kind: "file", absolutePath: sourceWorkspace.entryPath, extensions: REACT_IMPLEMENTATION_EXTENSIONS },
515
- ...implementationRoots(sourceWorkspace),
516
- ];
517
- return roots;
518
- }
519
- return contentRoots;
520
- }
521
-
522
- function implementationRoots(sourceWorkspace) {
523
- const roots = [];
524
- const seen = new Set();
525
- for (const root of sourceWorkspace.contentRoots ?? [{ kind: "dir", absolutePath: sourceWorkspace.sourceDir }]) {
526
- const absolutePath = root.kind === "dir" ? root.absolutePath : path.dirname(root.absolutePath);
527
- if (seen.has(absolutePath)) continue;
528
- seen.add(absolutePath);
529
- roots.push({
530
- scope: "source-implementation",
531
- kind: "dir",
532
- absolutePath,
533
- extensions: REACT_IMPLEMENTATION_EXTENSIONS,
534
- });
535
- }
536
- return roots;
537
- }
538
-
539
- function forEachLine(text, visit) {
540
- const lineRe = /([^\r\n]*)(\r\n|\n|\r|$)/g;
541
- let lineNumber = 1;
542
- let offset = 0;
543
- let match;
544
- while ((match = lineRe.exec(text))) {
545
- const [full, line, ending] = match;
546
- if (full === "") break;
547
- visit({ line, ending, lineNumber, lineOffset: offset });
548
- offset += full.length;
549
- lineNumber += 1;
550
- }
551
- }
552
-
553
- function findLineMatches(line, query, { caseSensitive }) {
554
- const haystack = caseSensitive ? line : line.toLowerCase();
555
- const needle = caseSensitive ? query : query.toLowerCase();
556
- const ranges = [];
557
- let cursor = 0;
558
- while (needle && cursor <= haystack.length) {
559
- const start = haystack.indexOf(needle, cursor);
560
- if (start < 0) break;
561
- const end = start + needle.length;
562
- ranges.push({ start, end });
563
- cursor = end;
564
- }
565
- return ranges;
566
- }
567
-
568
- function replaceRangesInLine(line, ranges, replacement) {
569
- let output = "";
570
- let cursor = 0;
571
- for (const range of ranges) {
572
- output += line.slice(cursor, range.start) + replacement;
573
- cursor = range.end;
574
- }
575
- return output + line.slice(cursor);
576
- }
577
-
578
- function previewLine(line, start, end) {
579
- const previewStart = Math.max(0, start - 40);
580
- const previewEnd = Math.min(line.length, end + 40);
581
- const prefix = previewStart > 0 ? "..." : "";
582
- const suffix = previewEnd < line.length ? "..." : "";
583
- return `${prefix}${line.slice(previewStart, previewEnd)}${suffix}`;
584
- }
585
-
586
- function summarizeFiles(matches) {
587
- const summaries = new Map();
588
- for (const match of matches) {
589
- const current = summaries.get(match.path) ?? {
590
- scope: match.scope,
591
- file: match.file,
592
- path: match.path,
593
- matchCount: 0,
594
- };
595
- current.matchCount += 1;
596
- summaries.set(match.path, current);
597
- }
598
- return Array.from(summaries.values());
599
- }
600
-
601
- function isFenceLine(line) {
602
- return /^\s*(```|~~~)/.test(line);
603
- }
604
-
605
- function assertEditableSourceBlock({ kind, name, blockId }) {
606
- if (kind === "list-item") return;
607
- if (kind === "element" && isEditableElementName(name)) return;
608
- throw new Error(`Only rendered text blocks can be edited from the document surface${blockId ? `: ${blockId}` : ""}.`);
609
- }
610
-
611
- function isEditableElementName(name) {
612
- return typeof name === "string" && /^(h[1-6]|p|blockquote)$/.test(name);
613
- }
614
-
615
- function normalizeSourceRange(source) {
616
- const line = positiveInteger(source?.line, "line");
617
- const column = positiveInteger(source?.column ?? 1, "column");
618
- const endLine = positiveInteger(source?.endLine ?? line, "endLine");
619
- const endColumn = positiveInteger(source?.endColumn ?? column, "endColumn");
620
- if (endLine < line) throw new Error("Source edit endLine must be greater than or equal to line.");
621
- return { line, column, endLine, endColumn };
622
- }
623
-
624
- function positiveInteger(value, label) {
625
- const number = Number(value);
626
- if (!Number.isInteger(number) || number < 1) {
627
- throw new Error(`Source edit ${label} must be a positive integer.`);
628
- }
629
- return number;
630
- }
631
-
632
- function nonNegativeInteger(value, label) {
633
- const number = Number(value);
634
- if (!Number.isInteger(number) || number < 0) {
635
- throw new Error(`Source edit ${label} must be a non-negative integer.`);
636
- }
637
- return number;
638
- }
639
-
640
- function normalizeEditedText(value) {
641
- if (typeof value !== "string") throw new Error("Source edit text must be a string.");
642
- return value.replace(/\s*\r?\n\s*/g, " ").trim();
643
- }
644
-
645
- function normalizeCodeBlockText(value) {
646
- if (typeof value !== "string") throw new Error("Source edit text must be a string.");
647
- return value.replace(/\r\n?/g, "\n").replace(/^\n+|\n+$/g, "");
648
- }
649
-
650
- function normalizeRawSourceText(value) {
651
- if (typeof value !== "string") throw new Error("Source edit text must be a string.");
652
- const normalized = value.replace(/\r\n?/g, "\n").trim();
653
- if (!normalized) throw new Error("Source edit text must not be empty.");
654
- return normalized;
655
- }
656
-
657
- function splitTextLines(text) {
658
- const rows = [];
659
- const lineRe = /([^\r\n]*)(\r\n|\n|\r|$)/g;
660
- let match;
661
- while ((match = lineRe.exec(text))) {
662
- const [full, line, ending] = match;
663
- if (full === "") break;
664
- rows.push({ line, ending });
665
- }
666
- return rows.length > 0 ? rows : [{ line: "", ending: "" }];
667
- }
668
-
669
- function joinTextLines(lines) {
670
- return lines.map(({ line, ending }) => `${line}${ending}`).join("");
671
- }
672
-
673
- function isEditableMarkdownLine(line, { kind, name }) {
674
- if (isFenceLine(line)) return false;
675
- if (/^\s*(?:import|export)\b/.test(line)) return false;
676
- if (/^\s*[<{}]/.test(line)) return false;
677
- if (/^\s*\|/.test(line)) return false;
678
- if (kind === "list-item") return /^(\s*(?:[-*+]|\d+[.)])\s+)/.test(line);
679
- if (name && /^h[1-6]$/.test(name)) return /^(\s{0,3}#{1,6}\s+)/.test(line);
680
- return true;
681
- }
682
-
683
- function isMarkdownTableRow(line) {
684
- return /^\s*\|.*\|\s*$/.test(line);
685
- }
686
-
687
- function replaceMarkdownTableCell(line, cellIndex, replacementText) {
688
- const parts = splitMarkdownTableRow(line);
689
- const firstCellPartIndex = parts[0] === "" ? 1 : 0;
690
- const lastCellPartIndex = parts.at(-1) === "" ? parts.length - 2 : parts.length - 1;
691
- const targetPartIndex = firstCellPartIndex + cellIndex;
692
- if (targetPartIndex > lastCellPartIndex) {
693
- throw new Error(`Markdown table row does not contain cell index ${cellIndex}.`);
694
- }
695
- parts[targetPartIndex] = replaceTableCellContent(parts[targetPartIndex], replacementText);
696
- return parts.join("|");
697
- }
698
-
699
- function splitMarkdownTableRow(line) {
700
- const parts = [];
701
- let current = "";
702
- let escaped = false;
703
- for (const char of line) {
704
- if (char === "|" && !escaped) {
705
- parts.push(current);
706
- current = "";
707
- continue;
708
- }
709
- current += char;
710
- escaped = char === "\\" && !escaped;
711
- if (char !== "\\") escaped = false;
712
- }
713
- parts.push(current);
714
- return parts;
715
- }
716
-
717
- function replaceTableCellContent(cell, replacementText) {
718
- const match = cell.match(/^(\s*)(.*?)(\s*)$/);
719
- if (!match) return replacementText;
720
- const leading = match[1] || " ";
721
- const trailing = match[3] || " ";
722
- return `${leading}${replacementText}${trailing}`;
723
- }
724
-
725
- function markdownTextPrefix(line, { kind, name }) {
726
- if (kind === "list-item") return line.match(/^(\s*(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?)/)?.[1] ?? "- ";
727
- if (name && /^h[1-6]$/.test(name)) return line.match(/^(\s{0,3}#{1,6}\s+)/)?.[1] ?? `${"#".repeat(Number(name.slice(1)))} `;
728
- if (name === "blockquote") return line.match(/^(\s{0,3}>\s*)/)?.[1] ?? "> ";
729
- return "";
730
- }
731
-
732
- function replaceCaptionText(sourceText, replacementText, name) {
733
- const componentMatch = sourceText.match(/^(\s*<TableCaption(?:\s[^>]*)?>)([\s\S]*?)(<\/TableCaption>\s*)$/);
734
- if (componentMatch) return `${componentMatch[1]}${replacementText}${componentMatch[3]}`;
735
-
736
- const tagName = name === "figcaption" ? "figcaption" : "caption";
737
- const tagRe = new RegExp(`^(\\s*<${tagName}(?:\\s[^>]*)?>)([\\s\\S]*?)(<\\/${tagName}>\\s*)$`);
738
- const tagMatch = sourceText.match(tagRe);
739
- if (tagMatch) return `${tagMatch[1]}${replacementText}${tagMatch[3]}`;
740
-
741
- throw new Error("Caption edits require a source-mapped TableCaption or caption element.");
742
- }
743
-
744
- function applySourceBlockComponentCaptionEditToText(documentText, {
745
- source,
746
- text,
747
- blockId,
748
- name,
749
- } = {}) {
750
- assertEditableComponentCaption({ name, blockId });
751
- const sourceRange = normalizeSourceRange(source);
752
- const replacementText = normalizeEditedText(text);
753
- const lines = splitTextLines(documentText);
754
- const startIndex = sourceRange.line - 1;
755
- const endIndex = sourceRange.endLine - 1;
756
-
757
- if (!lines[startIndex]) {
758
- throw new Error(`Source edit line ${sourceRange.line} is outside the source file.`);
759
- }
760
- if (!lines[endIndex]) {
761
- throw new Error(`Source edit end line ${sourceRange.endLine} is outside the source file.`);
762
- }
763
-
764
- const selectedLines = lines.slice(startIndex, endIndex + 1);
765
- const before = selectedLines.map((line) => line.line).join("\n");
766
- const after = replaceComponentCaptionProp(before, replacementText);
767
- const replacementLines = after.split("\n");
768
- const ending = selectedLines[selectedLines.length - 1].ending;
769
- const nextLines = [
770
- ...lines.slice(0, startIndex),
771
- ...replacementLines.map((line, index) => ({
772
- line,
773
- ending: index === replacementLines.length - 1 ? ending : "\n",
774
- })),
775
- ...lines.slice(endIndex + 1),
776
- ];
777
-
778
- return {
779
- text: joinTextLines(nextLines),
780
- edit: {
781
- blockId,
782
- line: sourceRange.line,
783
- column: sourceRange.column,
784
- endLine: sourceRange.endLine,
785
- endColumn: sourceRange.endColumn,
786
- before,
787
- after,
788
- text: replacementText,
789
- },
790
- };
791
- }
792
-
793
- function assertEditableComponentCaption({ name, blockId }) {
794
- if (name === "MediaFigure" || name === "ImageFigure") return;
795
- throw new Error(`Only MediaFigure and ImageFigure caption props can be edited inline${blockId ? `: ${blockId}` : ""}.`);
796
- }
797
-
798
- function replaceComponentCaptionProp(sourceText, replacementText) {
799
- const attrRe = /(\bcaption\s*=\s*)(["'])([\s\S]*?)(\2)/m;
800
- const attrMatch = sourceText.match(attrRe);
801
- if (!attrMatch) {
802
- throw new Error("Figure caption edits require a quoted caption prop.");
803
- }
804
- const quote = attrMatch[2];
805
- const escapedReplacement = escapeJsxAttributeValue(replacementText, quote);
806
- return sourceText.replace(attrRe, `$1${quote}${escapedReplacement}${quote}`);
807
- }
808
-
809
- function escapeJsxAttributeValue(value, quote) {
810
- const quoted = String(value ?? "")
811
- .replaceAll("&", "&amp;")
812
- .replaceAll("<", "&lt;")
813
- .replaceAll(">", "&gt;");
814
- return quote === '"'
815
- ? quoted.replaceAll('"', "&quot;")
816
- : quoted.replaceAll("'", "&#39;");
817
- }
818
-
819
- function sourceTextPathMatches(candidatePath, requestedPath) {
820
- return normalizeSourceTextPath(candidatePath) === normalizeSourceTextPath(requestedPath);
821
- }
822
-
823
- function normalizeSourceTextPath(value) {
824
- return String(value ?? "")
825
- .replaceAll("\\", "/")
826
- .replace(/^\.\//, "")
827
- .replace(/^document\//, "");
828
- }
829
-
830
- function stringValue(value) {
831
- return typeof value === "string" ? value.trim() : "";
832
- }