@yaakdoc/react 0.0.0-stage → 0.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.
- package/LICENSE +21 -0
- package/README.md +232 -2
- package/dist/client.d.ts +9 -0
- package/dist/client.js +26 -0
- package/dist/color-mode-B-vJCVkP.js +77 -0
- package/dist/index-CoQ7pnuB.d.ts +471 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +551 -0
- package/dist/island-BWyuL4_6.d.ts +29 -0
- package/dist/islands.d.ts +170 -0
- package/dist/islands.js +152 -0
- package/dist/llm-Ddn5hWnS.js +205 -0
- package/dist/run-in-yaak-CMMcDS7S.js +164 -0
- package/dist/server.d.ts +26 -0
- package/dist/server.js +139 -0
- package/dist/site-DyK7ZcqQ.js +371 -0
- package/dist/theme.d.ts +45 -0
- package/dist/theme.js +7 -0
- package/dist/types-BY2lOvlK.d.ts +101 -0
- package/package.json +85 -3
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createContext, createElement, use, useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
//#region src/island.tsx
|
|
4
|
+
/**
|
|
5
|
+
* Provided by the server renderer. Islands are rendered as isolated roots (exactly like they are
|
|
6
|
+
* hydrated on the client), so `useId()` and friends produce matching ids.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
const IslandRendererContext = createContext(null);
|
|
10
|
+
/**
|
|
11
|
+
* Turns a component into an island: it is rendered to HTML on the server like any other component,
|
|
12
|
+
* and hydrated on the client (only this component, not the whole page).
|
|
13
|
+
*
|
|
14
|
+
* Islands are hydrated in isolation: their props must be JSON-serializable and they cannot use
|
|
15
|
+
* yaakdoc context hooks. Register islands on the client with `hydrateIslands()`.
|
|
16
|
+
*/
|
|
17
|
+
function createIsland(name, Component) {
|
|
18
|
+
function Island(props) {
|
|
19
|
+
const renderer = use(IslandRendererContext);
|
|
20
|
+
const common = {
|
|
21
|
+
"data-island": name,
|
|
22
|
+
"data-props": JSON.stringify(props),
|
|
23
|
+
style: { display: "contents" }
|
|
24
|
+
};
|
|
25
|
+
if (renderer) {
|
|
26
|
+
const prefix = renderer.next();
|
|
27
|
+
return /* @__PURE__ */ jsx("yd-island", {
|
|
28
|
+
...common,
|
|
29
|
+
"data-prefix": prefix,
|
|
30
|
+
dangerouslySetInnerHTML: { __html: renderer.render(createElement(Component, props), prefix) }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return /* @__PURE__ */ jsx("yd-island", {
|
|
34
|
+
...common,
|
|
35
|
+
children: /* @__PURE__ */ jsx(Component, { ...props })
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
Island.displayName = `Island(${name})`;
|
|
39
|
+
return Object.assign(Island, {
|
|
40
|
+
islandName: name,
|
|
41
|
+
Component
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/islands/browser.ts
|
|
46
|
+
/** Browser helpers shared by the islands. */
|
|
47
|
+
/** `localStorage` access that never throws (private mode, blocked storage...). */
|
|
48
|
+
const storage = {
|
|
49
|
+
get(key) {
|
|
50
|
+
try {
|
|
51
|
+
return localStorage.getItem(key);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
set(key, value) {
|
|
57
|
+
try {
|
|
58
|
+
localStorage.setItem(key, value);
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/** Resolves a (possibly relative) URL against the current page. */
|
|
63
|
+
const resolveUrl = (url) => new URL(url, window.location.href).href;
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/islands/environment.tsx
|
|
66
|
+
const ENVIRONMENT_STORAGE_KEY = "yaakdoc-environment";
|
|
67
|
+
const ENVIRONMENT_EVENT = "yaakdoc:environment";
|
|
68
|
+
/**
|
|
69
|
+
* Shows the content variants of one environment. Variants are elements with
|
|
70
|
+
* `data-yd-env="<environment id>"`; `<html data-environment>` drives the CSS (see
|
|
71
|
+
* {@link environmentCss}) and the `hidden` attributes are kept in sync for accessibility.
|
|
72
|
+
*/
|
|
73
|
+
function applyEnvironment(id) {
|
|
74
|
+
document.documentElement.dataset.environment = id;
|
|
75
|
+
for (const element of document.querySelectorAll("[data-yd-env]")) element.hidden = element.dataset.ydEnv !== id;
|
|
76
|
+
storage.set(ENVIRONMENT_STORAGE_KEY, id);
|
|
77
|
+
window.dispatchEvent(new CustomEvent(ENVIRONMENT_EVENT, { detail: id }));
|
|
78
|
+
}
|
|
79
|
+
/** Headless environment switcher state, synced across switchers and remembered. */
|
|
80
|
+
function useEnvironment(environments, defaultId) {
|
|
81
|
+
const [selected, setSelected] = useState(defaultId);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const onChange = (event) => setSelected(event.detail);
|
|
84
|
+
window.addEventListener(ENVIRONMENT_EVENT, onChange);
|
|
85
|
+
const current = document.documentElement.dataset.environment;
|
|
86
|
+
if (current && current !== defaultId && environments.some((env) => env.id === current)) applyEnvironment(current);
|
|
87
|
+
return () => window.removeEventListener(ENVIRONMENT_EVENT, onChange);
|
|
88
|
+
}, [environments, defaultId]);
|
|
89
|
+
return {
|
|
90
|
+
selected,
|
|
91
|
+
select: useCallback((id) => {
|
|
92
|
+
if (environments.some((env) => env.id === id)) applyEnvironment(id);
|
|
93
|
+
}, [environments]),
|
|
94
|
+
current: environments.find((env) => env.id === selected)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** Inline <head> script: applies the remembered environment before first paint. */
|
|
98
|
+
function environmentScript(ids) {
|
|
99
|
+
return `(function(){try{var e=localStorage.getItem(${JSON.stringify(ENVIRONMENT_STORAGE_KEY)});if(e&&${JSON.stringify(ids)}.indexOf(e)>=0)document.documentElement.dataset.environment=e}catch(_){}})();`;
|
|
100
|
+
}
|
|
101
|
+
/** CSS that shows the variants of the environment set on <html> (falls back to `hidden`). */
|
|
102
|
+
function environmentCss(ids) {
|
|
103
|
+
return ["[data-yd-env]{display:contents}[data-yd-env][hidden]{display:none}", ...ids.map((id) => {
|
|
104
|
+
const value = JSON.stringify(id);
|
|
105
|
+
return `html[data-environment=${value}] [data-yd-env]:not([data-yd-env=${value}]){display:none}html[data-environment=${value}] [data-yd-env=${value}]{display:contents}`;
|
|
106
|
+
})].join("");
|
|
107
|
+
}
|
|
108
|
+
function EnvironmentSwitcherImpl({ environments, defaultId, className, label = "Environment" }) {
|
|
109
|
+
const { selected, select } = useEnvironment(environments, defaultId);
|
|
110
|
+
return /* @__PURE__ */ jsx("select", {
|
|
111
|
+
"data-yd": "environment-switcher",
|
|
112
|
+
className,
|
|
113
|
+
"aria-label": label,
|
|
114
|
+
value: selected,
|
|
115
|
+
onChange: (event) => select(event.target.value),
|
|
116
|
+
children: environments.map((env) => /* @__PURE__ */ jsx("option", {
|
|
117
|
+
value: env.id,
|
|
118
|
+
children: env.name
|
|
119
|
+
}, env.id))
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** Unstyled environment switcher (native select). */
|
|
123
|
+
const EnvironmentSwitcher = createIsland("yd:environment-switcher", EnvironmentSwitcherImpl);
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/islands/run-in-yaak.tsx
|
|
126
|
+
/** Official "Run in Yaak" badge image. */
|
|
127
|
+
const RUN_IN_YAAK_BADGE = "https://yaak.app/static/button.svg";
|
|
128
|
+
/** Link that opens Yaak and prompts to import the export/spec at `dataUrl` (must be absolute). */
|
|
129
|
+
function runInYaakUrl(name, dataUrl) {
|
|
130
|
+
return `https://yaak.app/button/run?${new URLSearchParams({
|
|
131
|
+
name,
|
|
132
|
+
url: dataUrl
|
|
133
|
+
})}`;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* "Run in Yaak" link for an export URL. Relative URLs (sites without a configured public URL) are
|
|
137
|
+
* made absolute on the client, after hydration.
|
|
138
|
+
*/
|
|
139
|
+
function useRunInYaakHref(name, dataUrl) {
|
|
140
|
+
const [href, setHref] = useState(() => runInYaakUrl(name, dataUrl));
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
setHref(runInYaakUrl(name, resolveUrl(dataUrl)));
|
|
143
|
+
}, [name, dataUrl]);
|
|
144
|
+
return href;
|
|
145
|
+
}
|
|
146
|
+
function RunInYaakLinkImpl({ name, dataUrl, badge = false, label = "Run in Yaak", className }) {
|
|
147
|
+
const href = useRunInYaakHref(name, dataUrl);
|
|
148
|
+
return /* @__PURE__ */ jsx("a", {
|
|
149
|
+
href,
|
|
150
|
+
"data-yd": "run-in-yaak",
|
|
151
|
+
className,
|
|
152
|
+
target: "_blank",
|
|
153
|
+
rel: "noopener",
|
|
154
|
+
children: badge ? /* @__PURE__ */ jsx("img", {
|
|
155
|
+
src: RUN_IN_YAAK_BADGE,
|
|
156
|
+
alt: label,
|
|
157
|
+
height: 32
|
|
158
|
+
}) : label
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/** Unstyled "Run in Yaak" link (or the official badge with `badge`). */
|
|
162
|
+
const RunInYaakLink = createIsland("yd:run-in-yaak", RunInYaakLinkImpl);
|
|
163
|
+
//#endregion
|
|
164
|
+
export { ENVIRONMENT_STORAGE_KEY as a, environmentCss as c, resolveUrl as d, storage as f, useRunInYaakHref as i, environmentScript as l, createIsland as m, RunInYaakLink as n, EnvironmentSwitcher as o, IslandRendererContext as p, runInYaakUrl as r, applyEnvironment as s, RUN_IN_YAAK_BADGE as t, useEnvironment as u };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { _ as ThemeRuntime, l as PageData, p as SiteData, t as Assets } from "./types-BY2lOvlK.js";
|
|
2
|
+
import { PreparedDocument } from "@yaakdoc/core";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
//#region src/document.d.ts
|
|
5
|
+
type DocumentProps = {
|
|
6
|
+
site: SiteData;
|
|
7
|
+
page: PageData;
|
|
8
|
+
assets: Assets;
|
|
9
|
+
ThemeHead?: ThemeRuntime["Head"];
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
/** The HTML document shell: meta tags, color mode script, assets, branding. */
|
|
13
|
+
export declare function Document({ site, page, assets, ThemeHead, children }: DocumentProps): import("react").JSX.Element;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/server.d.ts
|
|
16
|
+
export type RenderPageOptions = {
|
|
17
|
+
theme: ThemeRuntime;
|
|
18
|
+
document: PreparedDocument;
|
|
19
|
+
site: SiteData;
|
|
20
|
+
page: PageData;
|
|
21
|
+
assets: Assets;
|
|
22
|
+
};
|
|
23
|
+
/** Renders a full HTML page (static: no hydration of the page itself, islands hydrate separately). */
|
|
24
|
+
export declare function renderPage({ theme, document, site, page, assets }: RenderPageOptions): Promise<string>;
|
|
25
|
+
//#endregion
|
|
26
|
+
export type { DocumentProps };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { b as useDocument, p as ComponentsProvider, t as BrandingStyles, v as toAbsoluteUrl, y as YaakdocProvider } from "./site-DyK7ZcqQ.js";
|
|
2
|
+
import { c as environmentCss, l as environmentScript, p as IslandRendererContext } from "./run-in-yaak-CMMcDS7S.js";
|
|
3
|
+
import { i as colorModeScript } from "./color-mode-B-vJCVkP.js";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { renderToString } from "react-dom/server";
|
|
6
|
+
import { prerender } from "react-dom/static";
|
|
7
|
+
//#region src/document.tsx
|
|
8
|
+
const absolute = (site, path) => toAbsoluteUrl(site, path) ?? path;
|
|
9
|
+
/** The HTML document shell: meta tags, color mode script, assets, branding. */
|
|
10
|
+
function Document({ site, page, assets, ThemeHead, children }) {
|
|
11
|
+
const title = page.title && page.title !== site.title ? `${page.title} · ${site.title}` : site.title;
|
|
12
|
+
const description = page.description || site.description;
|
|
13
|
+
const environmentIds = useDocument().environments.map((env) => env.id);
|
|
14
|
+
return /* @__PURE__ */ jsxs("html", {
|
|
15
|
+
lang: "en",
|
|
16
|
+
"data-color-mode": site.defaultColorMode,
|
|
17
|
+
suppressHydrationWarning: true,
|
|
18
|
+
children: [/* @__PURE__ */ jsxs("head", { children: [
|
|
19
|
+
/* @__PURE__ */ jsx("meta", { charSet: "utf-8" }),
|
|
20
|
+
/* @__PURE__ */ jsx("meta", {
|
|
21
|
+
name: "viewport",
|
|
22
|
+
content: "width=device-width, initial-scale=1"
|
|
23
|
+
}),
|
|
24
|
+
/* @__PURE__ */ jsx("title", { children: title }),
|
|
25
|
+
description ? /* @__PURE__ */ jsx("meta", {
|
|
26
|
+
name: "description",
|
|
27
|
+
content: description
|
|
28
|
+
}) : null,
|
|
29
|
+
/* @__PURE__ */ jsx("meta", {
|
|
30
|
+
name: "generator",
|
|
31
|
+
content: site.generator
|
|
32
|
+
}),
|
|
33
|
+
/* @__PURE__ */ jsx("meta", {
|
|
34
|
+
property: "og:type",
|
|
35
|
+
content: "website"
|
|
36
|
+
}),
|
|
37
|
+
/* @__PURE__ */ jsx("meta", {
|
|
38
|
+
property: "og:title",
|
|
39
|
+
content: title
|
|
40
|
+
}),
|
|
41
|
+
description ? /* @__PURE__ */ jsx("meta", {
|
|
42
|
+
property: "og:description",
|
|
43
|
+
content: description
|
|
44
|
+
}) : null,
|
|
45
|
+
site.url ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("link", {
|
|
46
|
+
rel: "canonical",
|
|
47
|
+
href: absolute(site, page.path)
|
|
48
|
+
}), /* @__PURE__ */ jsx("meta", {
|
|
49
|
+
property: "og:url",
|
|
50
|
+
content: absolute(site, page.path)
|
|
51
|
+
})] }) : null,
|
|
52
|
+
page.kind === "not-found" ? /* @__PURE__ */ jsx("meta", {
|
|
53
|
+
name: "robots",
|
|
54
|
+
content: "noindex"
|
|
55
|
+
}) : null,
|
|
56
|
+
/* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: colorModeScript(site.defaultColorMode) } }),
|
|
57
|
+
environmentIds.length > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: environmentScript(environmentIds) } }), /* @__PURE__ */ jsx("style", {
|
|
58
|
+
"data-yd": "environments",
|
|
59
|
+
dangerouslySetInnerHTML: { __html: environmentCss(environmentIds) }
|
|
60
|
+
})] }) : null,
|
|
61
|
+
site.favicon ? /* @__PURE__ */ jsx("link", {
|
|
62
|
+
rel: "icon",
|
|
63
|
+
href: site.favicon
|
|
64
|
+
}) : null,
|
|
65
|
+
site.fonts.stylesheet ? /* @__PURE__ */ jsx("link", {
|
|
66
|
+
rel: "stylesheet",
|
|
67
|
+
href: site.fonts.stylesheet
|
|
68
|
+
}) : null,
|
|
69
|
+
page.markdownUrl ? /* @__PURE__ */ jsx("link", {
|
|
70
|
+
rel: "alternate",
|
|
71
|
+
type: "text/markdown",
|
|
72
|
+
href: page.markdownUrl,
|
|
73
|
+
title: "Markdown"
|
|
74
|
+
}) : null,
|
|
75
|
+
site.llms.txtUrl ? /* @__PURE__ */ jsx("link", {
|
|
76
|
+
rel: "alternate",
|
|
77
|
+
type: "text/plain",
|
|
78
|
+
href: site.llms.txtUrl,
|
|
79
|
+
title: "llms.txt"
|
|
80
|
+
}) : null,
|
|
81
|
+
assets.modulePreloads.map((href) => /* @__PURE__ */ jsx("link", {
|
|
82
|
+
rel: "modulepreload",
|
|
83
|
+
href
|
|
84
|
+
}, href)),
|
|
85
|
+
assets.styles.map((href) => /* @__PURE__ */ jsx("link", {
|
|
86
|
+
rel: "stylesheet",
|
|
87
|
+
href
|
|
88
|
+
}, href)),
|
|
89
|
+
assets.inlineStyles.map((style) => /* @__PURE__ */ jsx("style", {
|
|
90
|
+
"data-vite-dev-id": style.id,
|
|
91
|
+
dangerouslySetInnerHTML: { __html: style.css }
|
|
92
|
+
}, style.id)),
|
|
93
|
+
/* @__PURE__ */ jsx(BrandingStyles, {}),
|
|
94
|
+
ThemeHead ? /* @__PURE__ */ jsx(ThemeHead, { page }) : null,
|
|
95
|
+
site.head.map(({ tag: Tag, attrs, content }) => /* @__PURE__ */ jsx(Tag, {
|
|
96
|
+
...attrs,
|
|
97
|
+
...content ? { dangerouslySetInnerHTML: { __html: content } } : {}
|
|
98
|
+
}, `${Tag}:${JSON.stringify(attrs)}:${content ?? ""}`))
|
|
99
|
+
] }), /* @__PURE__ */ jsxs("body", { children: [children, assets.scripts.map((src) => /* @__PURE__ */ jsx("script", {
|
|
100
|
+
type: "module",
|
|
101
|
+
src
|
|
102
|
+
}, src))] })]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/server.tsx
|
|
107
|
+
/** Renders a full HTML page (static: no hydration of the page itself, islands hydrate separately). */
|
|
108
|
+
async function renderPage({ theme, document, site, page, assets }) {
|
|
109
|
+
const { Layout } = theme;
|
|
110
|
+
let counter = 0;
|
|
111
|
+
const element = /* @__PURE__ */ jsx(IslandRendererContext, {
|
|
112
|
+
value: {
|
|
113
|
+
render: (island, identifierPrefix) => renderToString(island, { identifierPrefix }),
|
|
114
|
+
next: () => `i${(counter++).toString(36)}-`
|
|
115
|
+
},
|
|
116
|
+
children: /* @__PURE__ */ jsx(YaakdocProvider, {
|
|
117
|
+
document,
|
|
118
|
+
site,
|
|
119
|
+
page,
|
|
120
|
+
children: /* @__PURE__ */ jsx(ComponentsProvider, {
|
|
121
|
+
components: theme.components,
|
|
122
|
+
children: /* @__PURE__ */ jsx(Document, {
|
|
123
|
+
site,
|
|
124
|
+
page,
|
|
125
|
+
assets,
|
|
126
|
+
ThemeHead: theme.Head,
|
|
127
|
+
children: /* @__PURE__ */ jsx(Layout, { page })
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
const { prelude } = await prerender(element, { onError(error) {
|
|
133
|
+
console.error("[yaakdoc] Render error:", error);
|
|
134
|
+
} });
|
|
135
|
+
const html = await new Response(prelude).text();
|
|
136
|
+
return html.startsWith("<!DOCTYPE") ? html : `<!DOCTYPE html>${html}`;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { Document, renderPage };
|