@open-press/cli 1.0.0 → 1.1.1

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 (175) hide show
  1. package/README.md +11 -12
  2. package/dist/cli.js +298 -79
  3. package/package.json +9 -7
  4. package/template/core/AGENTS.md +0 -130
  5. package/template/core/CHANGELOG.md +0 -218
  6. package/template/core/README.md +0 -43
  7. package/template/core/engine/cli.mjs +0 -96
  8. package/template/core/engine/commands/_shared.mjs +0 -199
  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/image.mjs +0 -29
  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/skills-sync.mjs +0 -71
  21. package/template/core/engine/commands/typecheck.mjs +0 -67
  22. package/template/core/engine/commands/upgrade.mjs +0 -159
  23. package/template/core/engine/commands/validate.mjs +0 -17
  24. package/template/core/engine/document-export.mjs +0 -15
  25. package/template/core/engine/output/chrome-pdf.d.mts +0 -34
  26. package/template/core/engine/output/chrome-pdf.mjs +0 -450
  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 -571
  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 -331
  39. package/template/core/engine/react/document-export.mjs +0 -512
  40. package/template/core/engine/react/http-json.mjs +0 -24
  41. package/template/core/engine/react/mdx-compile.mjs +0 -629
  42. package/template/core/engine/react/measurement-css.mjs +0 -157
  43. package/template/core/engine/react/object-entities.mjs +0 -204
  44. package/template/core/engine/react/pagination/allocator.mjs +0 -167
  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 -217
  49. package/template/core/engine/react/pipeline/final-render.mjs +0 -94
  50. package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -306
  51. package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
  52. package/template/core/engine/react/press-tree-inspection.mjs +0 -172
  53. package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
  54. package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
  55. package/template/core/engine/react/section-css.mjs +0 -56
  56. package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
  57. package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
  58. package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
  59. package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
  60. package/template/core/engine/react/style-discovery.mjs +0 -160
  61. package/template/core/engine/runtime/config.d.mts +0 -48
  62. package/template/core/engine/runtime/config.mjs +0 -172
  63. package/template/core/engine/runtime/file-utils.mjs +0 -114
  64. package/template/core/engine/runtime/file-walk.mjs +0 -22
  65. package/template/core/engine/runtime/inspection.mjs +0 -328
  66. package/template/core/engine/runtime/issue-report.mjs +0 -44
  67. package/template/core/engine/runtime/page-geometry.mjs +0 -131
  68. package/template/core/engine/runtime/path-utils.mjs +0 -20
  69. package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
  70. package/template/core/engine/runtime/source-text-tools.mjs +0 -832
  71. package/template/core/engine/runtime/source-workspace.mjs +0 -168
  72. package/template/core/engine/runtime/validation.mjs +0 -183
  73. package/template/core/index.html +0 -13
  74. package/template/core/openpress.config.mjs +0 -8
  75. package/template/core/package.json +0 -89
  76. package/template/core/src/main.tsx +0 -16
  77. package/template/core/src/openpress/app/OpenPressApp.tsx +0 -296
  78. package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -102
  79. package/template/core/src/openpress/app/WorkspaceGalleryPage.tsx +0 -219
  80. package/template/core/src/openpress/app/index.ts +0 -2
  81. package/template/core/src/openpress/core/Frame.tsx +0 -91
  82. package/template/core/src/openpress/core/FrameContext.tsx +0 -26
  83. package/template/core/src/openpress/core/MdxArea.tsx +0 -34
  84. package/template/core/src/openpress/core/Press.tsx +0 -55
  85. package/template/core/src/openpress/core/Workspace.tsx +0 -36
  86. package/template/core/src/openpress/core/cn.ts +0 -4
  87. package/template/core/src/openpress/core/index.tsx +0 -47
  88. package/template/core/src/openpress/core/primitives.tsx +0 -91
  89. package/template/core/src/openpress/core/types.ts +0 -236
  90. package/template/core/src/openpress/core/useSource.ts +0 -28
  91. package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
  92. package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
  93. package/template/core/src/openpress/document-model/documentTypes.ts +0 -147
  94. package/template/core/src/openpress/document-model/index.ts +0 -7
  95. package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -55
  96. package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
  97. package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
  98. package/template/core/src/openpress/document-model/workspaceManifestModel.ts +0 -57
  99. package/template/core/src/openpress/manuscript/index.tsx +0 -238
  100. package/template/core/src/openpress/mdx/index.ts +0 -96
  101. package/template/core/src/openpress/numbering/index.ts +0 -294
  102. package/template/core/src/openpress/reader/PageThumbnailsPanel.tsx +0 -168
  103. package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
  104. package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
  105. package/template/core/src/openpress/reader/index.ts +0 -11
  106. package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
  107. package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
  108. package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
  109. package/template/core/src/openpress/reader/readerScroll.ts +0 -92
  110. package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
  111. package/template/core/src/openpress/reader/readerTypes.ts +0 -4
  112. package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
  113. package/template/core/src/openpress/reader/usePanelState.ts +0 -56
  114. package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
  115. package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
  116. package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
  117. package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
  118. package/template/core/src/openpress/shared/Panel.tsx +0 -77
  119. package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
  120. package/template/core/src/openpress/shared/index.ts +0 -4
  121. package/template/core/src/openpress/shared/numberUtils.ts +0 -3
  122. package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
  123. package/template/core/src/openpress/workbench/Workbench.tsx +0 -506
  124. package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
  125. package/template/core/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
  126. package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
  127. package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
  128. package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
  129. package/template/core/src/openpress/workbench/actions/index.ts +0 -6
  130. package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
  131. package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
  132. package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
  133. package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
  134. package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
  135. package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
  136. package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
  137. package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
  138. package/template/core/src/openpress/workbench/document/index.ts +0 -10
  139. package/template/core/src/openpress/workbench/index.ts +0 -2
  140. package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
  141. package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
  142. package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
  143. package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
  144. package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
  145. package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -254
  146. package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
  147. package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
  148. package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
  149. package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
  150. package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -80
  151. package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
  152. package/template/core/src/openpress/workbench/panels/index.ts +0 -3
  153. package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -525
  154. package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
  155. package/template/core/src/openpress/workbench/project/index.ts +0 -2
  156. package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
  157. package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
  158. package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
  159. package/template/core/src/openpress/workbench/shell/index.ts +0 -1
  160. package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
  161. package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
  162. package/template/core/src/styles/openpress/app-shell.css +0 -251
  163. package/template/core/src/styles/openpress/media-workspace.css +0 -230
  164. package/template/core/src/styles/openpress/print-route.css +0 -184
  165. package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
  166. package/template/core/src/styles/openpress/public-viewer.css +0 -688
  167. package/template/core/src/styles/openpress/reader-runtime.css +0 -989
  168. package/template/core/src/styles/openpress/responsive.css +0 -245
  169. package/template/core/src/styles/openpress/workbench-panels.css +0 -707
  170. package/template/core/src/styles/openpress/workbench.css +0 -1255
  171. package/template/core/src/styles/openpress/workspace-gallery.css +0 -300
  172. package/template/core/src/styles/openpress.css +0 -15
  173. package/template/core/src/vite-env.d.ts +0 -9
  174. package/template/core/tsconfig.json +0 -40
  175. package/template/core/vite.config.ts +0 -584
@@ -1,238 +0,0 @@
1
- // @open-press/core/manuscript — long-form section-flow helpers.
2
- //
3
- // Manuscript helpers cover the "paper / report / book / monograph" pattern:
4
- // a source resolves into a sequence of sections, each section becomes one
5
- // content frame (with overflow auto-cloning across pages), and a TOC frame
6
- // summarizes the outline.
7
- //
8
- // These helpers are conventions only. Core never knows about them. Any
9
- // document type that wants section flow imports from here; documents that
10
- // do not (slides, folios, calendars) skip this module entirely.
11
-
12
- import { Fragment, useContext, type ComponentType, type ReactNode } from "react";
13
- import { Frame, FrameContext, MdxArea, PressContext, useSource } from "../core";
14
- import type { MdxAreaOverflow, ResolvedSource } from "../core";
15
- import { createMdxAreaObjectEntityId } from "../document-model/objectEntityModel";
16
-
17
- // ---------------------------------------------------------------------------
18
- // <Sections>
19
- // ---------------------------------------------------------------------------
20
-
21
- export interface SectionsPageProps {
22
- frameKey: string;
23
- chainId: string;
24
- pageIndex: number;
25
- totalPages: number;
26
- sectionSlug: string;
27
- sectionTitle: string;
28
- sectionTone?: string;
29
- sectionMeta: Record<string, unknown>;
30
- }
31
-
32
- export interface SectionsOpenerProps {
33
- frameKey: string;
34
- sectionSlug: string;
35
- sectionTitle: string;
36
- sectionIndex: number;
37
- sectionMeta: Record<string, unknown>;
38
- }
39
-
40
- export interface SectionsProps {
41
- source: string;
42
- page?: ComponentType<SectionsPageProps>;
43
- opener?: ComponentType<SectionsOpenerProps>;
44
- }
45
-
46
- export function Sections({ source: sourceId, page: Page = DefaultSectionPage, opener: Opener }: SectionsProps) {
47
- const source = useSource(sourceId);
48
- const press = useContext(PressContext);
49
- const hints = press?.hints ?? null;
50
- return (
51
- <Fragment>
52
- {source.tree.map((section, index) => {
53
- const chainId = `${sourceId}:${section.slug}`;
54
- const meta = (section.meta ?? {}) as Record<string, unknown>;
55
- const tone = typeof meta.tone === "string" ? meta.tone : undefined;
56
- const totalPages = Math.max(1, hints?.totalPagesPerChain?.[chainId] ?? 1);
57
- const pages: ReactNode[] = [];
58
- for (let i = 0; i < totalPages; i++) {
59
- pages.push(
60
- <Page
61
- key={i}
62
- frameKey={`${sourceId}:${section.slug}:content:${i}`}
63
- chainId={chainId}
64
- pageIndex={i}
65
- totalPages={totalPages}
66
- sectionSlug={section.slug}
67
- sectionTitle={section.title}
68
- sectionTone={tone}
69
- sectionMeta={meta}
70
- />,
71
- );
72
- }
73
- return (
74
- <Fragment key={section.slug}>
75
- {Opener ? (
76
- <Opener
77
- frameKey={`${sourceId}:${section.slug}:opener`}
78
- sectionSlug={section.slug}
79
- sectionTitle={section.title}
80
- sectionIndex={index}
81
- sectionMeta={meta}
82
- />
83
- ) : null}
84
- {pages}
85
- </Fragment>
86
- );
87
- })}
88
- </Fragment>
89
- );
90
- }
91
-
92
- // Compatibility alias for chapter vocabulary.
93
- export const Chapters = Sections;
94
- export type ChaptersProps = SectionsProps;
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
-
131
- // ---------------------------------------------------------------------------
132
- // <Toc>
133
- // ---------------------------------------------------------------------------
134
-
135
- export interface TocProps {
136
- source: string;
137
- className?: string;
138
- heading?: ReactNode;
139
- frameKey?: string;
140
- maxLevel?: 2 | 3;
141
- overflow?: MdxAreaOverflow;
142
- page?: React.ComponentType<TocPageProps>;
143
- }
144
-
145
- export interface TocPageProps {
146
- frameKey: string;
147
- chainId: string;
148
- pageIndex: number;
149
- totalPages: number;
150
- sourceId: string;
151
- heading?: ReactNode;
152
- className?: string;
153
- maxLevel?: 2 | 3;
154
- overflow?: MdxAreaOverflow;
155
- }
156
-
157
- export interface TocAreaProps {
158
- chainId: string;
159
- maxLevel?: 2 | 3;
160
- overflow?: MdxAreaOverflow;
161
- className?: string;
162
- }
163
-
164
- export function Toc({ source: sourceId, className, heading, frameKey = "toc", maxLevel = 3, overflow = "extend", page: Page = DefaultTocPage }: TocProps) {
165
- useSource(sourceId) as ResolvedSource;
166
- const press = useContext(PressContext);
167
- const chainId = maxLevel <= 2 ? `toc:${sourceId}:h2` : `toc:${sourceId}`;
168
- const totalPages = Math.max(1, press?.hints?.totalPagesPerChain?.[chainId] ?? 1);
169
- const pages: ReactNode[] = [];
170
- for (let i = 0; i < totalPages; i++) {
171
- pages.push(
172
- <Page
173
- key={i}
174
- frameKey={i === 0 ? frameKey : `${frameKey}:page:${i}`}
175
- chainId={chainId}
176
- pageIndex={i}
177
- totalPages={totalPages}
178
- sourceId={sourceId}
179
- heading={heading}
180
- className={className}
181
- maxLevel={maxLevel}
182
- overflow={overflow}
183
- />,
184
- );
185
- }
186
- return <Fragment>{pages}</Fragment>;
187
- }
188
-
189
- function DefaultTocPage({ frameKey, chainId, pageIndex, totalPages, heading, className, maxLevel, overflow }: TocPageProps) {
190
- const isContinuation = pageIndex > 0;
191
- const tocClassName = ["reader-page--toc", isContinuation ? "toc-continuation" : null, className].filter(Boolean).join(" ") || undefined;
192
- return (
193
- <Frame
194
- frameKey={frameKey}
195
- role="manuscript.toc"
196
- chrome={false}
197
- className={tocClassName}
198
- >
199
- <div className="page-frame">
200
- <header className="page-header toc-header">
201
- {heading ?? (
202
- <h2 className={isContinuation ? "toc-heading toc-heading--continuation" : "toc-heading"} id={isContinuation ? `${frameKey}-title` : "toc-title"}>
203
- {isContinuation ? "目錄續" : "目錄"}
204
- </h2>
205
- )}
206
- </header>
207
- <main className="page-body">
208
- <TocArea chainId={chainId} maxLevel={maxLevel} overflow={overflow} />
209
- </main>
210
- </div>
211
- </Frame>
212
- );
213
- }
214
-
215
- export function TocArea({ chainId, maxLevel, overflow = "extend", className }: TocAreaProps) {
216
- const frame = useContext(FrameContext);
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;
222
- return (
223
- <div
224
- className="openpress-mdx-area openpress-toc-area"
225
- data-openpress-mdx-area="true"
226
- data-openpress-mdx-area-chain={chainId}
227
- data-openpress-mdx-area-index={consumed?.indexInFrame}
228
- data-openpress-object-id={objectId}
229
- data-openpress-toc-max-level={maxLevel}
230
- data-openpress-mdx-area-overflow={overflow}
231
- data-openpress-mdx-area-empty={blocks == null ? "true" : "false"}
232
- >
233
- <ol className={["toc-list", className].filter(Boolean).join(" ") || undefined}>
234
- {blocks}
235
- </ol>
236
- </div>
237
- );
238
- }
@@ -1,96 +0,0 @@
1
- // @open-press/core/mdx — pure source descriptor factories.
2
- //
3
- // These factories MUST stay pure: they construct descriptor objects only.
4
- // They must not touch the filesystem, fetch the network, or execute
5
- // workspace logic at module load. Resolution happens in Layer 1 inside the
6
- // engine, where IO is allowed.
7
-
8
- import type {
9
- MdxSourceDescriptor,
10
- MdxSourceDescriptorFileList,
11
- MdxSourceDescriptorSectionFiles,
12
- MdxSourceDescriptorSectionFolders,
13
- } from "../core/types";
14
-
15
- export type {
16
- MdxSourceDescriptor,
17
- MdxSourceDescriptorFileList,
18
- MdxSourceDescriptorSectionFiles,
19
- MdxSourceDescriptorSectionFolders,
20
- } from "../core/types";
21
-
22
- export type {
23
- OutlineItem,
24
- ResolvedSource,
25
- SourceBlock,
26
- SourceFileRecord,
27
- SourceNode,
28
- } from "../core/types";
29
-
30
- // All presets accept an optional `id` for the 1.0 contract where sources
31
- // are an array passed via <Press sources>. In v0.x the id came from the
32
- // record key in `export const sources = { story: mdxSource(...) }`.
33
- type MdxSourceOptions =
34
- | { id?: string; preset: "section-folders"; root?: string }
35
- | { id?: string; preset: "section-files"; root?: string }
36
- | { id?: string; preset: "file-list"; files: string[] };
37
-
38
- const VALID_PRESETS = new Set(["section-folders", "section-files", "file-list"]);
39
-
40
- export function mdxSource(options: MdxSourceOptions): MdxSourceDescriptor & { id?: string } {
41
- if (!options || typeof options !== "object") {
42
- throw new Error("mdxSource() requires an options object.");
43
- }
44
- if (!VALID_PRESETS.has(options.preset)) {
45
- throw new Error(
46
- `mdxSource() preset must be one of: section-folders, section-files, file-list. Got "${
47
- (options as { preset?: unknown }).preset
48
- }".`,
49
- );
50
- }
51
-
52
- const id = typeof options.id === "string" && options.id.trim() ? options.id.trim() : undefined;
53
-
54
- if (options.preset === "section-folders") {
55
- const desc = normalizeRooted("section-folders", options.root, "chapters") as MdxSourceDescriptorSectionFolders;
56
- return id ? { ...desc, id } : desc;
57
- }
58
- if (options.preset === "section-files") {
59
- const desc = normalizeRooted("section-files", options.root, "content") as MdxSourceDescriptorSectionFiles;
60
- return id ? { ...desc, id } : desc;
61
- }
62
-
63
- // file-list
64
- if (!Array.isArray(options.files)) {
65
- throw new Error('mdxSource({ preset: "file-list" }) requires `files: string[]`.');
66
- }
67
- const files: string[] = [];
68
- for (const raw of options.files) {
69
- if (typeof raw !== "string") {
70
- throw new Error('mdxSource({ preset: "file-list" }) `files` entries must be strings.');
71
- }
72
- const trimmed = raw.trim();
73
- if (!trimmed) continue;
74
- files.push(trimmed);
75
- }
76
- if (files.length === 0) {
77
- throw new Error('mdxSource({ preset: "file-list" }) requires at least one file.');
78
- }
79
- const desc: MdxSourceDescriptor = { type: "mdx", preset: "file-list", files };
80
- return id ? { ...desc, id } : desc;
81
- }
82
-
83
- function normalizeRooted(
84
- preset: "section-folders" | "section-files",
85
- root: string | undefined,
86
- defaultRoot: string,
87
- ): MdxSourceDescriptor {
88
- if (root !== undefined && typeof root !== "string") {
89
- throw new Error(`mdxSource() \`root\` must be a string if provided. Got ${typeof root}.`);
90
- }
91
- return {
92
- type: "mdx",
93
- preset,
94
- root: (root ?? defaultRoot).trim() || defaultRoot,
95
- };
96
- }
@@ -1,294 +0,0 @@
1
- // @open-press/core/numbering — label formatters for AI authoring layer.
2
- //
3
- // Core stays neutral about how chapter / section / topic numbers should look.
4
- // Source resolution exposes raw integers via outline items
5
- // (`chapterNumber`, `sectionIndex`, `topicIndex`). This module is a toolbox
6
- // of pure functions that turn those integers into human-readable labels —
7
- // Chinese numerals, Roman numerals, alphabet letters, padded decimals, etc.
8
- //
9
- // Workspaces import what they need and compose them inside their own
10
- // `<Page>` / `<Toc>` components. The core engine never calls these.
11
-
12
- // ---------------------------------------------------------------------------
13
- // CJK numeral systems
14
- // ---------------------------------------------------------------------------
15
-
16
- const CJK_INFORMAL = ["〇", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
17
- const CJK_FORMAL = ["零", "壹", "貳", "參", "肆", "伍", "陸", "柒", "捌", "玖"];
18
- const CJK_HEAVENLY_STEMS = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
19
- const CJK_EARTHLY_BRANCHES = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"];
20
-
21
- /**
22
- * Informal CJK numerals (一二三...). Handles 0 to 9999.
23
- *
24
- * toCjk(1) → "一"
25
- * toCjk(10) → "十"
26
- * toCjk(15) → "十五"
27
- * toCjk(23) → "二十三"
28
- * toCjk(100) → "一百"
29
- * toCjk(108) → "一百零八"
30
- * toCjk(2024) → "二千零二十四"
31
- */
32
- export function toCjk(n: number): string {
33
- if (!Number.isFinite(n) || n < 0) return String(n);
34
- return formatCjkDigits(Math.floor(n), CJK_INFORMAL);
35
- }
36
-
37
- /**
38
- * Formal CJK numerals (壹貳參...). Used in legal / financial contexts.
39
- *
40
- * toCjkFormal(1) → "壹"
41
- * toCjkFormal(100) → "壹佰"
42
- */
43
- export function toCjkFormal(n: number): string {
44
- if (!Number.isFinite(n) || n < 0) return String(n);
45
- return formatCjkDigits(Math.floor(n), CJK_FORMAL, { formal: true });
46
- }
47
-
48
- /**
49
- * Heavenly Stems cycle (甲乙丙丁戊己庚辛壬癸 — 10 only).
50
- * Cycles for n > 10; n=11 → 甲 again.
51
- *
52
- * toCjkHeavenlyStem(1) → "甲"
53
- * toCjkHeavenlyStem(10) → "癸"
54
- * toCjkHeavenlyStem(11) → "甲"
55
- */
56
- export function toCjkHeavenlyStem(n: number): string {
57
- if (!Number.isFinite(n) || n < 1) return String(n);
58
- return CJK_HEAVENLY_STEMS[(Math.floor(n) - 1) % 10]!;
59
- }
60
-
61
- /**
62
- * Earthly Branches cycle (子丑寅卯辰巳午未申酉戌亥 — 12 only).
63
- * Cycles for n > 12.
64
- */
65
- export function toCjkEarthlyBranch(n: number): string {
66
- if (!Number.isFinite(n) || n < 1) return String(n);
67
- return CJK_EARTHLY_BRANCHES[(Math.floor(n) - 1) % 12]!;
68
- }
69
-
70
- // ---------------------------------------------------------------------------
71
- // Roman numerals
72
- // ---------------------------------------------------------------------------
73
-
74
- const ROMAN_TABLE: Array<[number, string]> = [
75
- [1000, "M"], [900, "CM"], [500, "D"], [400, "CD"],
76
- [100, "C"], [90, "XC"], [50, "L"], [40, "XL"],
77
- [10, "X"], [9, "IX"], [5, "V"], [4, "IV"],
78
- [1, "I"],
79
- ];
80
-
81
- /**
82
- * Roman numerals (I, II, III, …, MMXX). Uppercase by default.
83
- * Pass `{ upper: false }` for lowercase (typical front-matter page numbering).
84
- *
85
- * toRoman(1) → "I"
86
- * toRoman(4) → "IV"
87
- * toRoman(2024) → "MMXXIV"
88
- * toRoman(3, { upper: false }) → "iii"
89
- */
90
- export function toRoman(n: number, opts: { upper?: boolean } = {}): string {
91
- if (!Number.isFinite(n) || n < 1) return String(n);
92
- let value = Math.floor(n);
93
- let out = "";
94
- for (const [num, sym] of ROMAN_TABLE) {
95
- while (value >= num) {
96
- out += sym;
97
- value -= num;
98
- }
99
- }
100
- return opts.upper === false ? out.toLowerCase() : out;
101
- }
102
-
103
- // ---------------------------------------------------------------------------
104
- // Latin alphabet (a, b, c, …, z, aa, ab, …)
105
- // ---------------------------------------------------------------------------
106
-
107
- /**
108
- * Spreadsheet-column-style alphabet labels.
109
- *
110
- * toAlpha(1) → "a"
111
- * toAlpha(26) → "z"
112
- * toAlpha(27) → "aa"
113
- * toAlpha(53) → "ba"
114
- * toAlpha(1, { upper: true }) → "A"
115
- */
116
- export function toAlpha(n: number, opts: { upper?: boolean } = {}): string {
117
- if (!Number.isFinite(n) || n < 1) return String(n);
118
- let value = Math.floor(n);
119
- let out = "";
120
- while (value > 0) {
121
- const rem = (value - 1) % 26;
122
- out = String.fromCharCode(97 + rem) + out;
123
- value = Math.floor((value - 1) / 26);
124
- }
125
- return opts.upper ? out.toUpperCase() : out;
126
- }
127
-
128
- // ---------------------------------------------------------------------------
129
- // Zero-padded decimal
130
- // ---------------------------------------------------------------------------
131
-
132
- /**
133
- * Zero-padded decimal — `width` is the minimum number of digits.
134
- *
135
- * toPadded(1) → "01"
136
- * toPadded(1, { width: 3 }) → "001"
137
- * toPadded(123) → "123"
138
- */
139
- export function toPadded(n: number, opts: { width?: number } = {}): string {
140
- if (!Number.isFinite(n)) return String(n);
141
- const width = Math.max(1, opts.width ?? 2);
142
- return String(Math.floor(n)).padStart(width, "0");
143
- }
144
-
145
- // ---------------------------------------------------------------------------
146
- // Composable label templates
147
- // ---------------------------------------------------------------------------
148
-
149
- export type NumberFormat =
150
- | "decimal"
151
- | "decimal-padded"
152
- | "cjk"
153
- | "cjk-formal"
154
- | "cjk-heavenly-stem"
155
- | "cjk-earthly-branch"
156
- | "roman"
157
- | "roman-lower"
158
- | "alpha"
159
- | "alpha-upper";
160
-
161
- export interface ChapterLabelOptions {
162
- format?: NumberFormat;
163
- prefix?: string;
164
- suffix?: string;
165
- width?: number;
166
- }
167
-
168
- /**
169
- * Compose a chapter label like "第一章" / "Chapter 1" / "01" / "Part IV".
170
- *
171
- * chapterLabel(1)
172
- * → "第一章" (default: cjk informal, prefix 第, suffix 章)
173
- * chapterLabel(1, { format: "decimal-padded", prefix: "Chapter " })
174
- * → "Chapter 01"
175
- * chapterLabel(4, { format: "roman", prefix: "Part " })
176
- * → "Part IV"
177
- * chapterLabel(2, { prefix: "", suffix: "" })
178
- * → "二"
179
- */
180
- export function chapterLabel(n: number, opts: ChapterLabelOptions = {}): string {
181
- const { format = "cjk", prefix = "第", suffix = "章", width } = opts;
182
- return `${prefix}${formatNumber(n, format, width)}${suffix}`;
183
- }
184
-
185
- export interface SectionLabelOptions {
186
- format?: NumberFormat;
187
- separator?: string;
188
- prefix?: string;
189
- suffix?: string;
190
- }
191
-
192
- /**
193
- * Compose a section label like "1.1" / "1.1.2" / "一-1".
194
- *
195
- * Accepts variadic counters for nesting:
196
- * sectionLabel(1, 2)
197
- * → "1.2"
198
- * sectionLabel(1, 2, 3)
199
- * → "1.2.3"
200
- * sectionLabel(1, 2, { format: "cjk", separator: "之" })
201
- * ⚠ note: pass options as last argument when using variadic counters;
202
- * prefer `sectionLabelOf([1, 2], { … })` below for clarity.
203
- */
204
- export function sectionLabel(...counters: number[]): string {
205
- return sectionLabelOf(counters);
206
- }
207
-
208
- /**
209
- * Explicit-array variant of `sectionLabel` so options can be passed cleanly.
210
- *
211
- * sectionLabelOf([1, 2], { separator: "-" }) → "1-2"
212
- * sectionLabelOf([1, 2, 3], { format: "alpha" }) → "a.b.c"
213
- * sectionLabelOf([1, 1], { format: "cjk", separator: "-" }) → "一-一"
214
- */
215
- export function sectionLabelOf(counters: number[], opts: SectionLabelOptions = {}): string {
216
- const { format = "decimal", separator = ".", prefix = "", suffix = "" } = opts;
217
- const parts = counters.map((c) => formatNumber(c, format));
218
- return `${prefix}${parts.join(separator)}${suffix}`;
219
- }
220
-
221
- // ---------------------------------------------------------------------------
222
- // Generic dispatch — `format` -> formatter
223
- // ---------------------------------------------------------------------------
224
-
225
- /**
226
- * Format a single number using one of the named formats. Useful when the
227
- * caller already knows which format string to use (e.g. from config).
228
- *
229
- * formatNumber(7, "cjk") → "七"
230
- * formatNumber(7, "roman-lower") → "vii"
231
- * formatNumber(3, "decimal-padded", 3) → "003"
232
- */
233
- export function formatNumber(n: number, format: NumberFormat, width?: number): string {
234
- switch (format) {
235
- case "decimal": return String(Math.floor(n));
236
- case "decimal-padded": return toPadded(n, { width });
237
- case "cjk": return toCjk(n);
238
- case "cjk-formal": return toCjkFormal(n);
239
- case "cjk-heavenly-stem": return toCjkHeavenlyStem(n);
240
- case "cjk-earthly-branch": return toCjkEarthlyBranch(n);
241
- case "roman": return toRoman(n, { upper: true });
242
- case "roman-lower": return toRoman(n, { upper: false });
243
- case "alpha": return toAlpha(n);
244
- case "alpha-upper": return toAlpha(n, { upper: true });
245
- default: return String(n);
246
- }
247
- }
248
-
249
- // ---------------------------------------------------------------------------
250
- // CJK digit formatter (internal)
251
- // ---------------------------------------------------------------------------
252
-
253
- const CJK_UNITS = ["", "十", "百", "千"];
254
- const CJK_UNITS_FORMAL = ["", "拾", "佰", "仟"];
255
-
256
- // Gap-zero indicator used between non-zero digits when reading aloud
257
- // (e.g. 一百零八). Distinct from 〇/零 as a literal zero digit.
258
- const CJK_GAP_ZERO = "零";
259
-
260
- function formatCjkDigits(n: number, digits: string[], opts: { formal?: boolean } = {}): string {
261
- if (n === 0) return digits[0]!;
262
- if (n < 0) return `負${formatCjkDigits(-n, digits, opts)}`;
263
- if (n >= 10000) {
264
- const wan = Math.floor(n / 10000);
265
- const rest = n % 10000;
266
- if (rest === 0) return `${formatCjkDigits(wan, digits, opts)}萬`;
267
- const restStr = formatCjkDigits(rest, digits, opts);
268
- const padded = rest < 1000 ? `${CJK_GAP_ZERO}${restStr}` : restStr;
269
- return `${formatCjkDigits(wan, digits, opts)}萬${padded}`;
270
- }
271
- const units = opts.formal ? CJK_UNITS_FORMAL : CJK_UNITS;
272
- const str = String(n);
273
- let out = "";
274
- let zeroPending = false;
275
- for (let i = 0; i < str.length; i++) {
276
- const digit = Number(str[i]);
277
- const unit = str.length - 1 - i;
278
- if (digit === 0) {
279
- zeroPending = true;
280
- continue;
281
- }
282
- if (zeroPending && out.length > 0) {
283
- out += CJK_GAP_ZERO;
284
- zeroPending = false;
285
- }
286
- // Special case: leading "一十" usually written as just "十" in informal CJK
287
- if (!opts.formal && digit === 1 && unit === 1 && i === 0) {
288
- out += units[unit];
289
- } else {
290
- out += digits[digit] + units[unit];
291
- }
292
- }
293
- return out;
294
- }