@xyd-js/plugin-docs 0.1.0-xyd.2 → 0.1.0-xyd.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/TODO.md +1 -0
- package/package.json +7 -17
- package/src/const.ts +7 -0
- package/src/declarations.d.ts +23 -0
- package/src/index.ts +305 -0
- package/src/pages/context.tsx +9 -0
- package/src/pages/layout.tsx +263 -0
- package/src/pages/metatags.ts +96 -0
- package/src/pages/page.tsx +363 -0
- package/src/presets/docs/index.ts +232 -0
- package/src/presets/docs/settings.ts +75 -0
- package/src/presets/graphql/index.ts +69 -0
- package/src/presets/openapi/index.ts +66 -0
- package/src/presets/sources/index.ts +74 -0
- package/src/presets/uniform/index.ts +751 -0
- package/src/types.ts +38 -0
- package/src/utils.ts +19 -0
- package/tsconfig.json +51 -0
- package/tsup.config.ts +74 -0
- package/dist/index.d.ts +0 -42
- package/dist/index.js +0 -4307
- package/dist/index.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @xyd-js/plugin-docs
|
|
2
|
+
|
|
3
|
+
## 0.1.0-xyd.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- update
|
|
8
|
+
|
|
9
|
+
## 0.1.0-xyd.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- documan + plugin-docs
|
|
14
|
+
|
|
15
|
+
## 0.1.0-xyd.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- test
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @xyd-js/components@0.1.0-xyd.13
|
|
22
|
+
- @xyd-js/composer@0.1.0-xyd.2
|
|
23
|
+
- @xyd-js/content@0.1.0-xyd.16
|
|
24
|
+
- @xyd-js/core@0.1.0-xyd.15
|
|
25
|
+
- @xyd-js/framework@0.1.0-xyd.34
|
|
26
|
+
- @xyd-js/gql@0.1.0-xyd.15
|
|
27
|
+
- @xyd-js/openapi@0.1.0-xyd.13
|
|
28
|
+
- @xyd-js/sources@0.1.1-xyd.5
|
|
29
|
+
- @xyd-js/themes@0.1.1-xyd.5
|
|
30
|
+
- @xyd-js/uniform@0.1.0-xyd.17
|
|
31
|
+
|
|
32
|
+
## 0.1.0-xyd.1
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- update packages
|
|
37
|
+
- Updated dependencies
|
|
38
|
+
- @xyd-js/components@0.1.0-xyd.12
|
|
39
|
+
- @xyd-js/composer@0.1.0-xyd.1
|
|
40
|
+
- @xyd-js/content@0.1.0-xyd.15
|
|
41
|
+
- @xyd-js/core@0.1.0-xyd.14
|
|
42
|
+
- @xyd-js/framework@0.1.0-xyd.33
|
|
43
|
+
- @xyd-js/gql@0.1.0-xyd.14
|
|
44
|
+
- @xyd-js/openapi@0.1.0-xyd.12
|
|
45
|
+
- @xyd-js/sources@0.1.1-xyd.4
|
|
46
|
+
- @xyd-js/themes@0.1.1-xyd.4
|
|
47
|
+
- @xyd-js/uniform@0.1.0-xyd.16
|
package/TODO.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1. better API
|
package/package.json
CHANGED
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xyd-js/plugin-docs",
|
|
3
|
-
"version": "0.1.0-xyd.
|
|
3
|
+
"version": "0.1.0-xyd.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
"./package.json": "./package.json",
|
|
10
|
-
".": "./dist/index.js"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist",
|
|
14
|
-
"package.json"
|
|
15
|
-
],
|
|
16
6
|
"dependencies": {
|
|
17
7
|
"codehike": "^1.0.3",
|
|
18
8
|
"@code-hike/lighter": "^1.0.3",
|
|
19
|
-
"@xyd-js/openapi": "0.1.0-xyd.13",
|
|
20
|
-
"@xyd-js/uniform": "0.1.0-xyd.17",
|
|
21
9
|
"@xyd-js/gql": "0.1.0-xyd.15",
|
|
22
|
-
"@xyd-js/
|
|
10
|
+
"@xyd-js/uniform": "0.1.0-xyd.17",
|
|
11
|
+
"@xyd-js/sources": "0.1.1-xyd.5",
|
|
12
|
+
"@xyd-js/openapi": "0.1.0-xyd.13"
|
|
23
13
|
},
|
|
24
14
|
"peerDependencies": {
|
|
25
15
|
"@react-router/dev": "^7.5.0",
|
|
@@ -30,10 +20,10 @@
|
|
|
30
20
|
"react-router": "^7.5.0",
|
|
31
21
|
"@xyd-js/framework": "0.1.0-xyd.34",
|
|
32
22
|
"@xyd-js/composer": "0.1.0-xyd.2",
|
|
33
|
-
"@xyd-js/themes": "0.1.1-xyd.5",
|
|
34
23
|
"@xyd-js/content": "0.1.0-xyd.16",
|
|
35
|
-
"@xyd-js/
|
|
36
|
-
"@xyd-js/
|
|
24
|
+
"@xyd-js/core": "0.1.0-xyd.15",
|
|
25
|
+
"@xyd-js/themes": "0.1.1-xyd.5",
|
|
26
|
+
"@xyd-js/components": "0.1.0-xyd.13"
|
|
37
27
|
},
|
|
38
28
|
"devDependencies": {
|
|
39
29
|
"@types/react-dom": "^19.0.0",
|
package/src/const.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import {ThemePresetName} from "@xyd-js/core";
|
|
2
|
+
|
|
3
|
+
export const THEME_CONFIG_FOLDER = ".docs/theme" // TODO: in the future shared internal const like `.xyd` and `.docs` ?
|
|
4
|
+
// TODO: SHARED
|
|
5
|
+
export const VIRTUAL_CONTENT_FOLDER = ".xyd/.cache/.content"
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_THEME: ThemePresetName = "poetry"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// declarations.d.ts
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
declare module 'virtual:xyd-settings' {
|
|
5
|
+
import type {Settings} from "@xyd-js/core";
|
|
6
|
+
|
|
7
|
+
const settings: Settings;
|
|
8
|
+
export default settings;
|
|
9
|
+
export {
|
|
10
|
+
settings as Settings
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare module 'virtual:xyd-theme' {
|
|
15
|
+
import {BaseTheme} from "@xyd-js/themes";
|
|
16
|
+
import {Theme as ThemeSettings} from "@xyd-js/core";
|
|
17
|
+
|
|
18
|
+
// Export a concrete theme class that extends BaseTheme
|
|
19
|
+
class ConcreteTheme extends BaseTheme {
|
|
20
|
+
constructor();
|
|
21
|
+
}
|
|
22
|
+
export default ConcreteTheme;
|
|
23
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import {Navigation, Settings} from "@xyd-js/core";
|
|
2
|
+
import type {Plugin as VitePlugin} from "vite";
|
|
3
|
+
import {RouteConfigEntry} from "@react-router/dev/routes";
|
|
4
|
+
import type {PageURL, Sidebar, SidebarRoute} from "@xyd-js/core";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
|
|
8
|
+
import {docsPreset} from "./presets/docs";
|
|
9
|
+
import {graphqlPreset} from "./presets/graphql";
|
|
10
|
+
import {openapiPreset} from "./presets/openapi";
|
|
11
|
+
import {sourcesPreset} from "./presets/sources";
|
|
12
|
+
|
|
13
|
+
import type {PluginOutput, Plugin} from "./types";
|
|
14
|
+
import {ensureAndCleanupVirtualFolder} from "./presets/uniform";
|
|
15
|
+
|
|
16
|
+
export {readSettings} from "./presets/docs/settings"
|
|
17
|
+
|
|
18
|
+
export interface PluginDocsOptions {
|
|
19
|
+
disableAPIGeneration?: boolean
|
|
20
|
+
disableFSWrite?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// TODO: better plugin runner
|
|
24
|
+
// TODO: REFACTOR
|
|
25
|
+
export async function pluginDocs(options?: PluginDocsOptions): Promise<PluginOutput | null> {
|
|
26
|
+
let settings: Settings | null = null
|
|
27
|
+
const vitePlugins: VitePlugin[] = []
|
|
28
|
+
const routes: RouteConfigEntry[] = []
|
|
29
|
+
let basePath = ""
|
|
30
|
+
|
|
31
|
+
// base docs preset setup
|
|
32
|
+
{
|
|
33
|
+
const options = {
|
|
34
|
+
urlPrefix: "" // TODO: configurable
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const docs = docsPreset(undefined, options)
|
|
38
|
+
docs.preinstall = docs.preinstall || []
|
|
39
|
+
|
|
40
|
+
let preinstallMerge = {}
|
|
41
|
+
|
|
42
|
+
for (const preinstall of docs.preinstall) {
|
|
43
|
+
const resp = await preinstall()({}, {
|
|
44
|
+
routes: docs.routes
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
if (resp && typeof resp === 'object') {
|
|
48
|
+
preinstallMerge = {
|
|
49
|
+
...preinstallMerge,
|
|
50
|
+
...resp
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
docs.vitePlugins = docs.vitePlugins || []
|
|
56
|
+
for (const vitePlugin of docs.vitePlugins) {
|
|
57
|
+
const vitePlug = await vitePlugin()({
|
|
58
|
+
preinstall: preinstallMerge
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
vitePlugins.push(vitePlug)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ("settings" in preinstallMerge) {
|
|
65
|
+
settings = preinstallMerge.settings as Settings
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
docs.routes = docs.routes || []
|
|
69
|
+
routes.push(...docs.routes)
|
|
70
|
+
basePath = docs.basePath
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!settings) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await ensureAndCleanupVirtualFolder()
|
|
78
|
+
|
|
79
|
+
// graphql preset setup
|
|
80
|
+
if (!options?.disableAPIGeneration && settings?.api?.graphql) {
|
|
81
|
+
const opt = {
|
|
82
|
+
disableFSWrite: options?.disableFSWrite
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const gql = graphqlPreset(settings, opt)
|
|
86
|
+
gql.preinstall = gql.preinstall || []
|
|
87
|
+
|
|
88
|
+
let preinstallMerge = {}
|
|
89
|
+
|
|
90
|
+
for (const preinstall of gql.preinstall) {
|
|
91
|
+
const resp = await preinstall(opt)(settings, {
|
|
92
|
+
routes: gql.routes,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
if (resp && typeof resp === 'object') {
|
|
96
|
+
preinstallMerge = {
|
|
97
|
+
...preinstallMerge,
|
|
98
|
+
...resp
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
gql.vitePlugins = gql.vitePlugins || []
|
|
104
|
+
for (const vitePlugin of gql.vitePlugins) {
|
|
105
|
+
const vitePlug = await vitePlugin()({
|
|
106
|
+
preinstall: preinstallMerge
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
vitePlugins.push(vitePlug)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
gql.routes = gql.routes || []
|
|
113
|
+
routes.push(...gql.routes)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// openapi preset setup
|
|
117
|
+
if (!options?.disableAPIGeneration && settings?.api?.openapi) {
|
|
118
|
+
const opt = {
|
|
119
|
+
disableFSWrite: options?.disableFSWrite
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const oap = openapiPreset(settings, opt)
|
|
123
|
+
oap.preinstall = oap.preinstall || []
|
|
124
|
+
|
|
125
|
+
let preinstallMerge = {}
|
|
126
|
+
|
|
127
|
+
for (const preinstall of oap.preinstall) {
|
|
128
|
+
const resp = await preinstall(opt)(settings, {
|
|
129
|
+
routes: oap.routes,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
if (resp && typeof resp === 'object') {
|
|
133
|
+
preinstallMerge = {
|
|
134
|
+
...preinstallMerge,
|
|
135
|
+
...resp
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
oap.vitePlugins = oap.vitePlugins || []
|
|
141
|
+
for (const vitePlugin of oap.vitePlugins) {
|
|
142
|
+
const vitePlug = await vitePlugin()({
|
|
143
|
+
preinstall: preinstallMerge
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
vitePlugins.push(vitePlug)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
oap.routes = oap.routes || []
|
|
150
|
+
routes.push(...oap.routes)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!options?.disableAPIGeneration && settings?.api?.sources) {
|
|
154
|
+
const opt = {
|
|
155
|
+
disableFSWrite: options?.disableFSWrite
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const src = sourcesPreset(settings, opt)
|
|
159
|
+
src.preinstall = src.preinstall || []
|
|
160
|
+
|
|
161
|
+
let preinstallMerge = {}
|
|
162
|
+
|
|
163
|
+
for (const preinstall of src.preinstall) {
|
|
164
|
+
const resp = await preinstall(opt)(settings, {
|
|
165
|
+
routes: src.routes,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
if (resp && typeof resp === 'object') {
|
|
169
|
+
preinstallMerge = {
|
|
170
|
+
...preinstallMerge,
|
|
171
|
+
...resp
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
src.vitePlugins = src.vitePlugins || []
|
|
177
|
+
for (const vitePlugin of src.vitePlugins) {
|
|
178
|
+
const vitePlug = await vitePlugin()({
|
|
179
|
+
preinstall: preinstallMerge
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
vitePlugins.push(vitePlug)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
src.routes = src.routes || []
|
|
186
|
+
routes.push(...src.routes)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let pagePathMapping: Record<string, string> = {}
|
|
190
|
+
|
|
191
|
+
if (settings?.navigation) {
|
|
192
|
+
pagePathMapping = mapNavigationToPagePathMapping(settings?.navigation)
|
|
193
|
+
} else {
|
|
194
|
+
console.warn("No navigation found in settings")
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
sortSidebarGroups(settings?.navigation?.sidebar || [])
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
vitePlugins,
|
|
201
|
+
settings,
|
|
202
|
+
routes,
|
|
203
|
+
basePath,
|
|
204
|
+
pagePathMapping
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function sortSidebarGroups(sidebar: (SidebarRoute | Sidebar | string)[]) {
|
|
209
|
+
// Sort items within each SidebarRoute
|
|
210
|
+
for (const group of sidebar) {
|
|
211
|
+
if (typeof group === 'string') {
|
|
212
|
+
continue // Skip string entries
|
|
213
|
+
}
|
|
214
|
+
if ('pages' in group && Array.isArray(group.pages)) {
|
|
215
|
+
group.pages.sort((a, b) => {
|
|
216
|
+
// If both have numeric sort values, compare them
|
|
217
|
+
if (typeof a.sort === 'number' && typeof b.sort === 'number') {
|
|
218
|
+
return a.sort - b.sort
|
|
219
|
+
}
|
|
220
|
+
// If only a has numeric sort, it comes first
|
|
221
|
+
if (typeof a.sort === 'number') {
|
|
222
|
+
return -1
|
|
223
|
+
}
|
|
224
|
+
// If only b has numeric sort, it comes first
|
|
225
|
+
if (typeof b.sort === 'number') {
|
|
226
|
+
return 1
|
|
227
|
+
}
|
|
228
|
+
// If neither has numeric sort, maintain original order
|
|
229
|
+
return 0
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// TODO: in the future better algorithm - we should be .md/.mdx faster than checking fs here
|
|
236
|
+
function mapNavigationToPagePathMapping(navigation: Navigation) {
|
|
237
|
+
const mapping: Record<string, string> = {}
|
|
238
|
+
|
|
239
|
+
function getExistingFilePath(basePath: string): string | null {
|
|
240
|
+
const mdPath = `${basePath}.md`
|
|
241
|
+
const mdxPath = `${basePath}.mdx`
|
|
242
|
+
|
|
243
|
+
if (fs.existsSync(mdPath)) {
|
|
244
|
+
return mdPath
|
|
245
|
+
}
|
|
246
|
+
if (fs.existsSync(mdxPath)) {
|
|
247
|
+
return mdxPath
|
|
248
|
+
}
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function processPages(pages: PageURL[]) {
|
|
253
|
+
for (const page of pages) {
|
|
254
|
+
if (typeof page === 'string') {
|
|
255
|
+
// Handle regular page
|
|
256
|
+
const existingPath = getExistingFilePath(page)
|
|
257
|
+
if (existingPath) {
|
|
258
|
+
mapping[page] = existingPath
|
|
259
|
+
}
|
|
260
|
+
} else if (typeof page === 'object' && 'virtual' in page) {
|
|
261
|
+
// Handle virtual page
|
|
262
|
+
const virtualPath = page.virtual
|
|
263
|
+
const pagePath = page.page
|
|
264
|
+
const existingPath = getExistingFilePath(virtualPath)
|
|
265
|
+
if (existingPath) {
|
|
266
|
+
mapping[pagePath] = existingPath
|
|
267
|
+
}
|
|
268
|
+
} else if (typeof page === 'object' && 'pages' in page) {
|
|
269
|
+
// Handle nested sidebar
|
|
270
|
+
processPages(page.pages || [])
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let sidebarFlatOnly = false
|
|
276
|
+
// Process each sidebar route
|
|
277
|
+
for (const sidebar of navigation.sidebar) {
|
|
278
|
+
if (typeof sidebar === 'string') {
|
|
279
|
+
sidebarFlatOnly = true
|
|
280
|
+
break
|
|
281
|
+
} else if ('pages' in sidebar && "route" in sidebar) {
|
|
282
|
+
// Handle SidebarRoute
|
|
283
|
+
for (const item of sidebar.pages) {
|
|
284
|
+
if (item?.pages) {
|
|
285
|
+
processPages(item.pages)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} else if ('pages' in sidebar) {
|
|
289
|
+
// Handle Sidebar
|
|
290
|
+
processPages(sidebar.pages || [])
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (sidebarFlatOnly) {
|
|
295
|
+
const sidebar = navigation.sidebar as string[]
|
|
296
|
+
processPages(sidebar)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return mapping
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export type {
|
|
303
|
+
Plugin,
|
|
304
|
+
PluginOutput
|
|
305
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Outlet,
|
|
4
|
+
useLoaderData,
|
|
5
|
+
useLocation,
|
|
6
|
+
useNavigate,
|
|
7
|
+
useNavigation,
|
|
8
|
+
useMatches
|
|
9
|
+
} from "react-router";
|
|
10
|
+
|
|
11
|
+
import { mapSettingsToProps } from "@xyd-js/framework/hydration";
|
|
12
|
+
|
|
13
|
+
import type { Metadata, MetadataMap, Theme as ThemeSettings } from "@xyd-js/core";
|
|
14
|
+
import type { INavLinks, IBreadcrumb } from "@xyd-js/ui";
|
|
15
|
+
import { Framework, FwLink, useSettings, type FwSidebarGroupProps } from "@xyd-js/framework/react";
|
|
16
|
+
import { ReactContent } from "@xyd-js/components/content";
|
|
17
|
+
import { Atlas, AtlasContext, type VariantToggleConfig } from "@xyd-js/atlas";
|
|
18
|
+
import AtlasXydPlugin from "@xyd-js/atlas/xydPlugin";
|
|
19
|
+
|
|
20
|
+
import { Surfaces } from "@xyd-js/framework/react";
|
|
21
|
+
import { Composer } from "@xyd-js/composer";
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
import { iconSet } from 'virtual:xyd-icon-set';
|
|
24
|
+
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
import virtualSettings from "virtual:xyd-settings";
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
const { settings: getSettings } = virtualSettings
|
|
29
|
+
// const settings = globalThis.__xydSettings
|
|
30
|
+
import Theme from "virtual:xyd-theme";
|
|
31
|
+
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
import "virtual:xyd-theme/index.css"
|
|
34
|
+
import "virtual:xyd-theme-override/index.css"
|
|
35
|
+
|
|
36
|
+
import { PageContext } from "./context";
|
|
37
|
+
import React from "react";
|
|
38
|
+
import { markdownPlugins } from "@xyd-js/content/md";
|
|
39
|
+
import { ContentFS } from "@xyd-js/content";
|
|
40
|
+
import { IconProvider } from "@xyd-js/components/writer";
|
|
41
|
+
import { CoderProvider } from "@xyd-js/components/coder";
|
|
42
|
+
|
|
43
|
+
globalThis.__xydSettings = getSettings
|
|
44
|
+
|
|
45
|
+
new Composer() // TODO: better API
|
|
46
|
+
const settings = globalThis.__xydSettings
|
|
47
|
+
|
|
48
|
+
const surfaces = new Surfaces()
|
|
49
|
+
const atlasXyd = AtlasXydPlugin()(settings) // TODO: in the future via standard plugin API
|
|
50
|
+
const SidebarItemRight = atlasXyd?.customComponents?.["AtlasSidebarItemRight"]
|
|
51
|
+
|
|
52
|
+
if (SidebarItemRight) {
|
|
53
|
+
surfaces.define(
|
|
54
|
+
SidebarItemRight.surface,
|
|
55
|
+
SidebarItemRight.component,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const reactContent = new ReactContent(settings, {
|
|
60
|
+
Link: FwLink,
|
|
61
|
+
components: {
|
|
62
|
+
Atlas,
|
|
63
|
+
},
|
|
64
|
+
useLocation, // // TODO: !!!! BETTER API !!!!!
|
|
65
|
+
useNavigate,
|
|
66
|
+
useNavigation
|
|
67
|
+
})
|
|
68
|
+
globalThis.__xydThemeSettings = settings?.theme
|
|
69
|
+
globalThis.__xydReactContent = reactContent
|
|
70
|
+
globalThis.__xydSurfaces = surfaces
|
|
71
|
+
|
|
72
|
+
const theme = new Theme()
|
|
73
|
+
|
|
74
|
+
const { Layout: BaseThemeLayout } = theme
|
|
75
|
+
|
|
76
|
+
interface LoaderData {
|
|
77
|
+
sidebarGroups: FwSidebarGroupProps[]
|
|
78
|
+
breadcrumbs: IBreadcrumb[],
|
|
79
|
+
toc: MetadataMap,
|
|
80
|
+
slug: string
|
|
81
|
+
metadata: Metadata | null
|
|
82
|
+
navlinks?: INavLinks,
|
|
83
|
+
bannerContentCode?: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function loader({ request }: { request: any }) {
|
|
87
|
+
const slug = getPathname(request.url || "index") || "index"
|
|
88
|
+
|
|
89
|
+
const {
|
|
90
|
+
groups: sidebarGroups,
|
|
91
|
+
breadcrumbs,
|
|
92
|
+
navlinks,
|
|
93
|
+
metadata
|
|
94
|
+
} = await mapSettingsToProps(
|
|
95
|
+
settings,
|
|
96
|
+
globalThis.__xydPagePathMapping,
|
|
97
|
+
slug,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
let bannerContentCode = ""
|
|
101
|
+
|
|
102
|
+
const mdPlugins = markdownPlugins({
|
|
103
|
+
maxDepth: metadata?.maxTocDepth || settings?.theme?.maxTocDepth || 2,
|
|
104
|
+
}, settings)
|
|
105
|
+
const contentFs = new ContentFS(settings, mdPlugins.remarkPlugins, mdPlugins.rehypePlugins)
|
|
106
|
+
|
|
107
|
+
if (settings?.theme?.banner?.content && typeof settings?.theme?.banner?.content === "string") {
|
|
108
|
+
bannerContentCode = await contentFs.compileContent(
|
|
109
|
+
settings?.theme?.banner?.content,
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
sidebarGroups,
|
|
115
|
+
breadcrumbs,
|
|
116
|
+
navlinks,
|
|
117
|
+
slug,
|
|
118
|
+
metadata,
|
|
119
|
+
bannerContentCode
|
|
120
|
+
} as LoaderData
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default function Layout() {
|
|
124
|
+
const loaderData = useLoaderData<LoaderData>()
|
|
125
|
+
const matches = useMatches()
|
|
126
|
+
|
|
127
|
+
const lastMatchId = matches[matches.length - 1]?.id || null
|
|
128
|
+
|
|
129
|
+
let atlasVariantToggles: VariantToggleConfig[] = [];
|
|
130
|
+
|
|
131
|
+
// TODO: BETTER HANDLE THAT
|
|
132
|
+
if (loaderData.metadata?.openapi) {
|
|
133
|
+
atlasVariantToggles = [
|
|
134
|
+
{ key: "status", defaultValue: "200" },
|
|
135
|
+
{ key: "contentType", defaultValue: "application/json" }
|
|
136
|
+
];
|
|
137
|
+
} else {
|
|
138
|
+
atlasVariantToggles = [
|
|
139
|
+
{ key: "symbolName", defaultValue: "" }
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let bannerContent: any = null
|
|
144
|
+
// TODO: !!!! BETTER API !!!!
|
|
145
|
+
if (loaderData.bannerContentCode) {
|
|
146
|
+
const content = mdxContent(loaderData.bannerContentCode)
|
|
147
|
+
const BannerContent = MemoMDXComponent(content.component)
|
|
148
|
+
|
|
149
|
+
bannerContent = function () {
|
|
150
|
+
return <BannerContent components={theme.reactContentComponents()} />
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return <>
|
|
155
|
+
<IconProvider value={{
|
|
156
|
+
iconSet: iconSet
|
|
157
|
+
}}>
|
|
158
|
+
<Framework
|
|
159
|
+
settings={settings || globalThis.__xydSettings}
|
|
160
|
+
sidebarGroups={loaderData.sidebarGroups || []}
|
|
161
|
+
metadata={loaderData.metadata || {}}
|
|
162
|
+
surfaces={surfaces}
|
|
163
|
+
BannerContent={bannerContent}
|
|
164
|
+
>
|
|
165
|
+
<AtlasContext
|
|
166
|
+
value={{
|
|
167
|
+
syntaxHighlight: settings?.theme?.coder?.syntaxHighlight || null,
|
|
168
|
+
baseMatch: lastMatchId || "",
|
|
169
|
+
variantToggles: atlasVariantToggles
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<CoderProvider lines={settings?.theme?.coder?.lines} scroll={settings?.theme?.coder?.scroll}>
|
|
173
|
+
<BaseThemeLayout>
|
|
174
|
+
<PageContext value={{ theme }}>
|
|
175
|
+
<Outlet />
|
|
176
|
+
</PageContext>
|
|
177
|
+
</BaseThemeLayout>
|
|
178
|
+
</CoderProvider>
|
|
179
|
+
</AtlasContext>
|
|
180
|
+
</Framework>
|
|
181
|
+
</IconProvider>
|
|
182
|
+
</>
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getPathname(url: string) {
|
|
186
|
+
const parsedUrl = new URL(url);
|
|
187
|
+
return parsedUrl.pathname.replace(/^\//, '');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
// TODO: move to content?
|
|
192
|
+
function mdxExport(code: string) {
|
|
193
|
+
// Create a wrapper around React.createElement that adds keys to elements in lists
|
|
194
|
+
const scope = {
|
|
195
|
+
Fragment: React.Fragment,
|
|
196
|
+
jsxs: createElementWithKeys,
|
|
197
|
+
jsx: createElementWithKeys,
|
|
198
|
+
jsxDEV: createElementWithKeys,
|
|
199
|
+
}
|
|
200
|
+
const fn = new Function(...Object.keys(scope), code)
|
|
201
|
+
|
|
202
|
+
return fn(scope)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
// // TODO: move to content?
|
|
207
|
+
function mdxContent(code: string) {
|
|
208
|
+
const content = mdxExport(code) // TODO: fix any
|
|
209
|
+
if (!mdxExport) {
|
|
210
|
+
return {}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
component: content?.default,
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const createElementWithKeys = (type: any, props: any) => {
|
|
219
|
+
// Process children to add keys to all elements
|
|
220
|
+
const processChildren = (childrenArray: any[]): any[] => {
|
|
221
|
+
return childrenArray.map((child, index) => {
|
|
222
|
+
// If the child is a React element and doesn't have a key, add one
|
|
223
|
+
if (React.isValidElement(child) && !child.key) {
|
|
224
|
+
return React.cloneElement(child, { key: `mdx-${index}` });
|
|
225
|
+
}
|
|
226
|
+
// If the child is an array, process it recursively
|
|
227
|
+
if (Array.isArray(child)) {
|
|
228
|
+
return processChildren(child);
|
|
229
|
+
}
|
|
230
|
+
return child;
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Handle both cases: children as separate args or as props.children
|
|
235
|
+
let processedChildren;
|
|
236
|
+
|
|
237
|
+
if (props && props.children) {
|
|
238
|
+
if (Array.isArray(props.children)) {
|
|
239
|
+
processedChildren = processChildren(props.children);
|
|
240
|
+
} else if (React.isValidElement(props.children) && !props.children.key) {
|
|
241
|
+
// Single child without key
|
|
242
|
+
processedChildren = React.cloneElement(props.children, { key: 'mdx-child' });
|
|
243
|
+
} else {
|
|
244
|
+
// Single child with key or non-React element
|
|
245
|
+
processedChildren = props.children;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
processedChildren = [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Create the element with processed children
|
|
252
|
+
return React.createElement(type, {
|
|
253
|
+
...props,
|
|
254
|
+
children: processedChildren
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
function MemoMDXComponent(codeComponent: any) {
|
|
259
|
+
return useMemo(
|
|
260
|
+
() => codeComponent ? codeComponent : null,
|
|
261
|
+
[codeComponent]
|
|
262
|
+
)
|
|
263
|
+
}
|