@xyd-js/cli 0.1.0-xyd.28 → 0.1.0-xyd.29

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.
@@ -0,0 +1,312 @@
1
+ import path from "path";
2
+ import {promises as fs} from "fs";
3
+
4
+ import React, {} from "react";
5
+ import {redirect} from "react-router";
6
+ import remarkFrontmatter from "remark-frontmatter";
7
+ import remarkMdxFrontmatter from "remark-mdx-frontmatter";
8
+ import remarkGfm from "remark-gfm";
9
+ import {parse} from "codehike";
10
+ import {visit} from "unist-util-visit";
11
+ import {recmaCodeHike, remarkCodeHike} from "codehike/mdx";
12
+ import {compile as mdxCompile} from "@mdx-js/mdx";
13
+
14
+ import {PageFrontMatter} from "@xyd-js/core";
15
+ import getContentComponents from "@xyd-js/components/content";
16
+ import {mapSettingsToProps} from "@xyd-js/framework/hydration";
17
+ import {Framework, type FwSidebarGroupProps} from "@xyd-js/framework/react";
18
+ import {AtlasIndex} from "@xyd-js/atlas/atlas-index";
19
+ import type {IBreadcrumb, INavLinks} from "@xyd-js/ui";
20
+
21
+ import Theme from "virtual:xyd-theme" // TODO: for some reasons this cannot be hydrated by react-router
22
+ import settings from 'virtual:xyd-settings';
23
+
24
+ import "virtual:xyd-theme/index.css"
25
+ import "virtual:xyd-theme-override/index.css"
26
+
27
+ interface loaderData {
28
+ sidebarGroups: FwSidebarGroupProps[]
29
+ breadcrumbs: IBreadcrumb[],
30
+ navlinks?: INavLinks,
31
+ toc: PageFrontMatter
32
+ slug: string
33
+ code: string
34
+ }
35
+
36
+ const contentComponents = getContentComponents()
37
+
38
+ // since unist does not support heading level > 6, we need to normalize them
39
+ function normalizeCustomHeadings() {
40
+ return (tree: any) => {
41
+ visit(tree, 'paragraph', (node, index, parent) => {
42
+ if (!node.children[0].value) {
43
+ return
44
+ }
45
+ const match = node.children[0] && node.children[0].value.match(/^(#+)\s+(.*)/);
46
+ if (match) {
47
+ const level = match[1].length;
48
+ const text = match[2];
49
+ if (level > 6) {
50
+ // Create a new heading node with depth 6
51
+ const headingNode = {
52
+ type: 'heading',
53
+ depth: level,
54
+ children: [{type: 'text', value: text}]
55
+ };
56
+ // Replace the paragraph node with the new heading node
57
+ //@ts-ignore
58
+ parent.children[index] = headingNode;
59
+ }
60
+ }
61
+ });
62
+ };
63
+ }
64
+
65
+ const codeHikeOptions = {
66
+ lineNumbers: true,
67
+ showCopyButton: true,
68
+ autoImport: true,
69
+ components: {},
70
+ // syntaxHighlighting: { // TODO: !!! FROM SETTINGS !!! wait for rr7 rsc ??
71
+ // theme: "github-dark",
72
+ // },
73
+ };
74
+
75
+ const compiledBySlug = {}
76
+
77
+ // TODO: map every file and merge them or load via client-side ?
78
+ async function compileBySlug(slug: string) {
79
+ if (compiledBySlug[slug]) {
80
+ return compiledBySlug[slug]
81
+ }
82
+ console.time("api-reference compileBySlug")
83
+ // TODO: cwd ?
84
+ let filePath = path.join(process.cwd(), `${slug}.md`)
85
+
86
+ try {
87
+ await fs.access(filePath)
88
+ } catch (_) {
89
+ filePath = path.join(process.cwd(), `${slug}.mdx`)
90
+
91
+ await fs.access(filePath)
92
+ }
93
+
94
+ console.time("api-reference readFile")
95
+ const content = await fs.readFile(filePath, "utf-8");
96
+ console.timeEnd("api-reference readFile")
97
+
98
+ console.time("api-reference compile")
99
+ const resp = await compile(content)
100
+ console.timeEnd("api-reference compile")
101
+
102
+ console.timeEnd("api-reference compileBySlug")
103
+ compiledBySlug[slug] = resp
104
+ return resp
105
+ }
106
+
107
+ async function compile(content: string): Promise<string> {
108
+ const compiled = await mdxCompile(content, {
109
+ remarkPlugins: [
110
+ normalizeCustomHeadings,
111
+ [
112
+ remarkCodeHike,
113
+ codeHikeOptions
114
+ ],
115
+ remarkFrontmatter,
116
+ remarkMdxFrontmatter,
117
+ remarkGfm
118
+ ],
119
+ rehypePlugins: [],
120
+ recmaPlugins: [
121
+ [
122
+ recmaCodeHike,
123
+ codeHikeOptions
124
+ ]
125
+ ],
126
+ outputFormat: 'function-body',
127
+ development: false,
128
+ });
129
+
130
+ return String(compiled)
131
+ }
132
+
133
+ interface loaderData {
134
+ sidebarGroups: FwSidebarGroupProps[]
135
+ toc: PageFrontMatter
136
+ slug: string
137
+ code: string
138
+ }
139
+
140
+ function getPathname(url: string) {
141
+ const parsedUrl = new URL(url);
142
+ return parsedUrl.pathname.replace(/^\//, '');
143
+ }
144
+
145
+ // TODO: fix any
146
+ function findFirstUrl(items: any): string {
147
+ const queue = [...items];
148
+
149
+ while (queue.length > 0) {
150
+ const item = queue.shift();
151
+
152
+ if (item.href) {
153
+ return item.href;
154
+ }
155
+
156
+ if (item.items) {
157
+ queue.push(...item.items);
158
+ }
159
+ }
160
+
161
+ return "";
162
+ }
163
+
164
+ interface data {
165
+ groups: FwSidebarGroupProps[],
166
+ breadcrumbs: IBreadcrumb[]
167
+ navlinks?: INavLinks
168
+ }
169
+
170
+ const mapSettingsToPropsMap: { [key: string]: data } = {}
171
+
172
+ // TODO: fix any
173
+ export async function loader({request}: { request: any }) {
174
+ console.time("api-reference loader")
175
+ const slug = getPathname(request.url);
176
+
177
+ let code = "";
178
+ let error: any;
179
+
180
+ try {
181
+ code = await compileBySlug(slug);
182
+ } catch (e) {
183
+ error = e;
184
+ }
185
+ let data: data
186
+
187
+ if (!mapSettingsToPropsMap[slug]) {
188
+ data = await mapSettingsToProps(
189
+ settings,
190
+ slug
191
+ );
192
+ mapSettingsToPropsMap[slug] = data
193
+ } else {
194
+ data = mapSettingsToPropsMap[slug]
195
+ }
196
+
197
+ const {groups: sidebarGroups, breadcrumbs, navlinks} = data;
198
+
199
+ if (error) {
200
+ if (sidebarGroups && error.code === "ENOENT") {
201
+ const firstItem = findFirstUrl(sidebarGroups?.[0]?.items);
202
+
203
+ if (firstItem) {
204
+ return redirect(firstItem);
205
+ }
206
+ }
207
+
208
+ console.error(error);
209
+ }
210
+
211
+ console.timeEnd("api-reference loader")
212
+ return {
213
+ sidebarGroups,
214
+ breadcrumbs,
215
+ navlinks,
216
+ slug,
217
+ code,
218
+ } as loaderData;
219
+ }
220
+
221
+ function mdxExport(code: string) {
222
+ const scope = {
223
+ Fragment: React.Fragment,
224
+ jsxs: React.createElement,
225
+ jsx: React.createElement,
226
+ jsxDEV: React.createElement,
227
+ }
228
+ const fn = new Function(...Object.keys(scope), code)
229
+ return fn(scope)
230
+ }
231
+
232
+ function MemoMDXComponent(codeComponent: any) {
233
+ return React.useMemo(
234
+ () => codeComponent ? codeComponent : null,
235
+ [codeComponent]
236
+ )
237
+ }
238
+
239
+ // // TODO: move to content?
240
+ function mdxContent(code: string) {
241
+ const content = mdxExport(code) // TODO: fix any
242
+ if (!mdxExport) {
243
+ return {}
244
+ }
245
+
246
+ return {
247
+ component: content?.default,
248
+ }
249
+ }
250
+
251
+ // TODO: below is a concept only
252
+ // class MyThemeSettings extends BaseThemeSettings {
253
+ // private constructor() {
254
+ // super();
255
+ //
256
+ // this.toc.hide(true)
257
+ // this.sidebar.clientSideRouting(true)
258
+ // this.layout.size("large")
259
+ // }
260
+ //
261
+ // override abc() {
262
+ //
263
+ // }
264
+ //
265
+ // static new() {
266
+ // return new MyThemeSettings()
267
+ // }
268
+ // }
269
+
270
+
271
+ // TODO: below is a concept only
272
+ // const themeSettings = new ThemeSettings()
273
+ // .toc.hide()
274
+ // .sidebar({
275
+ // clientSideRouting: true
276
+ // })
277
+ // .layout.size("large")
278
+
279
+ // TODO: in the future more smoother loader - first fast server render then move to ideal position of client and then replace and 3 items at start
280
+ export default function APIReference({loaderData}: { loaderData: loaderData }) {
281
+ const content = mdxContent(loaderData.code)
282
+ const serverComponent = content ? parse(content.component, {
283
+ components: contentComponents
284
+ }) : null
285
+
286
+ const memoizedServerComponent = MemoMDXComponent(serverComponent)
287
+
288
+ console.log("memoizedServerComponent", memoizedServerComponent)
289
+
290
+ return <Framework
291
+ settings={settings}
292
+ sidebarGroups={loaderData.sidebarGroups || []}
293
+ breadcrumbs={loaderData.breadcrumbs || []}
294
+ navlinks={loaderData.navlinks}
295
+ >
296
+ <Theme
297
+ themeSettings={{
298
+ hideToc: true,
299
+ sidebar: {
300
+ clientSideRouting: true
301
+ },
302
+ layout: {
303
+ size: "large"
304
+ }
305
+ }}
306
+ >
307
+ <AtlasIndex
308
+ data={memoizedServerComponent?.references[0]}
309
+ />
310
+ </Theme>
311
+ </Framework>
312
+ }
@@ -26,7 +26,22 @@ interface loaderData {
26
26
  code: string
27
27
  }
28
28
 
29
- const contentComponents = getContentComponents()
29
+ const contentComponents = {
30
+ ...getContentComponents(),
31
+
32
+ HomePage: (props) => <HomePage
33
+ {...props}
34
+ // TODO: get props from theme about nav (middle etc)
35
+ // TODO: footer
36
+ // TODO: style
37
+ header={<div style={{marginLeft: "var(--xyd-global-page-gutter)"}}>
38
+ <FwNav kind="middle"/>
39
+ </div>}
40
+
41
+ >
42
+ {props.children}
43
+ </HomePage>
44
+ }
30
45
 
31
46
  const supportedExtensions = {
32
47
  ".mdx": true,
@@ -142,7 +157,32 @@ export function MemoMDXComponent(codeComponent: any) {
142
157
 
143
158
  export default function CustomPage({loaderData, ...rest}: { loaderData: loaderData }) {
144
159
  const content = mdxContent(loaderData.code)
145
- const Component = MemoMDXComponent(content.component)
160
+ const Content = MemoMDXComponent(content.component)
161
+
162
+ if (!Content) {
163
+ console.error("Content not found")
164
+ return null
165
+ }
166
+
167
+ let component: React.JSX.Element
168
+
169
+ if (content.page) {
170
+ component = <Content
171
+ components={{
172
+ ...contentComponents,
173
+ }}
174
+ />
175
+ } else {
176
+ component = <Theme
177
+ themeSettings={content.themeSettings}
178
+ >
179
+ <Content
180
+ components={{
181
+ ...contentComponents,
182
+ }}
183
+ />
184
+ </Theme>
185
+ }
146
186
 
147
187
  return <Framework
148
188
  settings={settings}
@@ -151,25 +191,6 @@ export default function CustomPage({loaderData, ...rest}: { loaderData: loaderDa
151
191
  breadcrumbs={loaderData.breadcrumbs || []}
152
192
  navlinks={loaderData.navlinks}
153
193
  >
154
- {content?.page ? <Component components={{
155
- ...contentComponents,
156
- // TODO: another page components
157
- HomePage: (props) => <HomePage
158
- {...props}
159
- // TODO: get props from theme about nav (middle etc)
160
- // TODO: footer
161
- // TODO: style
162
- header={<div style={{marginLeft: "var(--xyd-global-page-gutter)"}}>
163
- <FwNav kind="middle"/>
164
- </div>}
165
-
166
- >
167
- {props.children}
168
- </HomePage>,
169
- }}/> : <Theme
170
- themeSettings={content.themeSettings}
171
- >
172
- {Component ? <Component components={contentComponents}/> : <></>}
173
- </Theme>}
194
+ {component}
174
195
  </Framework>
175
196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xyd-js/cli",
3
- "version": "0.1.0-xyd.28",
3
+ "version": "0.1.0-xyd.29",
4
4
  "keywords": [],
5
5
  "author": "",
6
6
  "description": "",
@@ -62,7 +62,7 @@
62
62
  "colors": "^1.4.0",
63
63
  "semver": "^7.6.3",
64
64
  "vite-tsconfig-paths": "^5.1.4",
65
- "@xyd-js/documan": "0.1.0-xyd.23"
65
+ "@xyd-js/documan": "0.1.0-xyd.24"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsup",