@mzebley/mark-down-react 1.2.1 → 1.2.3
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/README.md +18 -16
- package/dist/index.cjs +21 -3
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +21 -3
- package/package.json +5 -2
- package/src/context.tsx +0 -32
- package/src/hooks.ts +0 -43
- package/src/index.ts +0 -3
- package/src/snippet-view.tsx +0 -41
- package/tsconfig.json +0 -8
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# mark↓ React Adapter
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
_(published as `@mzebley/mark-down-react`)_
|
|
3
4
|
|
|
4
5
|
React bindings for the [mark↓ core runtime](../core/README.md). This package exposes context providers, hooks, and ready-to-use components that make it simple to render Markdown snippets safely. For a broader overview of the project, start with the [root README](../../README.md).
|
|
5
6
|
|
|
@@ -29,11 +30,11 @@ Generate a manifest with the [CLI](../cli/README.md) before rendering snippets.
|
|
|
29
30
|
Wrap your app with the `SnippetProvider` so that hooks and components can access a shared client instance:
|
|
30
31
|
|
|
31
32
|
```tsx
|
|
32
|
-
import { SnippetProvider } from
|
|
33
|
+
import { SnippetProvider } from "@mzebley/mark-down-react";
|
|
33
34
|
|
|
34
35
|
export function App({ children }: { children: React.ReactNode }) {
|
|
35
36
|
return (
|
|
36
|
-
<SnippetProvider options={{ manifest:
|
|
37
|
+
<SnippetProvider options={{ manifest: "/snippets-index.json" }}>
|
|
37
38
|
{children}
|
|
38
39
|
</SnippetProvider>
|
|
39
40
|
);
|
|
@@ -49,10 +50,10 @@ export function App({ children }: { children: React.ReactNode }) {
|
|
|
49
50
|
Fetch a single snippet and track loading / error state:
|
|
50
51
|
|
|
51
52
|
```tsx
|
|
52
|
-
import { useSnippet } from
|
|
53
|
+
import { useSnippet } from "@mzebley/mark-down-react";
|
|
53
54
|
|
|
54
55
|
export function Hero() {
|
|
55
|
-
const { snippet, loading, error } = useSnippet(
|
|
56
|
+
const { snippet, loading, error } = useSnippet("getting-started-welcome");
|
|
56
57
|
|
|
57
58
|
if (loading) return <p>Loading…</p>;
|
|
58
59
|
if (error) return <p role="alert">Failed to load snippet.</p>;
|
|
@@ -67,13 +68,13 @@ export function Hero() {
|
|
|
67
68
|
Render snippets declaratively with built-in loading and error fallbacks:
|
|
68
69
|
|
|
69
70
|
```tsx
|
|
70
|
-
import { SnippetView } from
|
|
71
|
+
import { SnippetView } from "@mzebley/mark-down-react";
|
|
71
72
|
|
|
72
73
|
<SnippetView
|
|
73
74
|
slug="components-button"
|
|
74
75
|
loadingFallback={<p>Loading…</p>}
|
|
75
76
|
errorFallback={<p role="alert">Unable to load snippet.</p>}
|
|
76
|
-
onLoaded={(snippet) => console.log(
|
|
77
|
+
onLoaded={(snippet) => console.log("Rendered", snippet.slug)}
|
|
77
78
|
/>;
|
|
78
79
|
```
|
|
79
80
|
|
|
@@ -88,18 +89,19 @@ Features:
|
|
|
88
89
|
When using Next.js, Remix, or another SSR framework, provide a server-safe fetch implementation:
|
|
89
90
|
|
|
90
91
|
```tsx
|
|
91
|
-
import fetch from
|
|
92
|
-
import { SnippetProvider } from
|
|
92
|
+
import fetch from "node-fetch";
|
|
93
|
+
import { SnippetProvider } from "@mzebley/mark-down-react";
|
|
93
94
|
|
|
94
95
|
<SnippetProvider
|
|
95
96
|
options={{
|
|
96
|
-
manifest: () => import(
|
|
97
|
-
fetch: (url) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
manifest: () => import("../snippets-index.json"),
|
|
98
|
+
fetch: (url) =>
|
|
99
|
+
fetch(url).then((response) => {
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw new Error(`Request failed with status ${response.status}`);
|
|
102
|
+
}
|
|
103
|
+
return response;
|
|
104
|
+
}),
|
|
103
105
|
}}
|
|
104
106
|
>
|
|
105
107
|
{children}
|
package/dist/index.cjs
CHANGED
|
@@ -42,7 +42,11 @@ var import_react = require("react");
|
|
|
42
42
|
var import_mark_down = require("@mzebley/mark-down");
|
|
43
43
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
44
44
|
var SnippetClientContext = (0, import_react.createContext)(null);
|
|
45
|
-
function SnippetProvider({
|
|
45
|
+
function SnippetProvider({
|
|
46
|
+
client,
|
|
47
|
+
options,
|
|
48
|
+
children
|
|
49
|
+
}) {
|
|
46
50
|
const value = (0, import_react.useMemo)(() => {
|
|
47
51
|
if (client) {
|
|
48
52
|
return client;
|
|
@@ -102,7 +106,15 @@ function SnippetView({
|
|
|
102
106
|
onLoaded
|
|
103
107
|
}) {
|
|
104
108
|
const state = useSnippet(slug);
|
|
105
|
-
const safeHtml = (0, import_react3.useMemo)(() =>
|
|
109
|
+
const safeHtml = (0, import_react3.useMemo)(() => {
|
|
110
|
+
if (!state.snippet) {
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
if (typeof window === "undefined") {
|
|
114
|
+
return state.snippet.html;
|
|
115
|
+
}
|
|
116
|
+
return import_dompurify.default.sanitize(state.snippet.html);
|
|
117
|
+
}, [state.snippet]);
|
|
106
118
|
(0, import_react3.useEffect)(() => {
|
|
107
119
|
onLoaded?.(state.snippet);
|
|
108
120
|
}, [state.snippet, onLoaded]);
|
|
@@ -115,7 +127,13 @@ function SnippetView({
|
|
|
115
127
|
if (!state.snippet) {
|
|
116
128
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, children: "Snippet not found" });
|
|
117
129
|
}
|
|
118
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
131
|
+
"div",
|
|
132
|
+
{
|
|
133
|
+
className,
|
|
134
|
+
dangerouslySetInnerHTML: { __html: safeHtml ?? "" }
|
|
135
|
+
}
|
|
136
|
+
);
|
|
119
137
|
}
|
|
120
138
|
// Annotate the CommonJS export names for ESM import in node:
|
|
121
139
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -7,7 +7,7 @@ interface SnippetProviderProps {
|
|
|
7
7
|
options?: SnippetClientOptions;
|
|
8
8
|
children: ReactNode;
|
|
9
9
|
}
|
|
10
|
-
declare function SnippetProvider({ client, options, children }: SnippetProviderProps): react_jsx_runtime.JSX.Element;
|
|
10
|
+
declare function SnippetProvider({ client, options, children, }: SnippetProviderProps): react_jsx_runtime.JSX.Element;
|
|
11
11
|
declare function useSnippetClient(): SnippetClient;
|
|
12
12
|
|
|
13
13
|
interface UseSnippetResult {
|
|
@@ -24,6 +24,6 @@ interface SnippetViewProps {
|
|
|
24
24
|
errorFallback?: ReactNode;
|
|
25
25
|
onLoaded?: (snippet?: Snippet) => void;
|
|
26
26
|
}
|
|
27
|
-
declare function SnippetView({ slug, className, loadingFallback, errorFallback, onLoaded }: SnippetViewProps): react_jsx_runtime.JSX.Element;
|
|
27
|
+
declare function SnippetView({ slug, className, loadingFallback, errorFallback, onLoaded, }: SnippetViewProps): react_jsx_runtime.JSX.Element;
|
|
28
28
|
|
|
29
29
|
export { SnippetProvider, type SnippetProviderProps, SnippetView, type SnippetViewProps, type UseSnippetResult, useSnippet, useSnippetClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ interface SnippetProviderProps {
|
|
|
7
7
|
options?: SnippetClientOptions;
|
|
8
8
|
children: ReactNode;
|
|
9
9
|
}
|
|
10
|
-
declare function SnippetProvider({ client, options, children }: SnippetProviderProps): react_jsx_runtime.JSX.Element;
|
|
10
|
+
declare function SnippetProvider({ client, options, children, }: SnippetProviderProps): react_jsx_runtime.JSX.Element;
|
|
11
11
|
declare function useSnippetClient(): SnippetClient;
|
|
12
12
|
|
|
13
13
|
interface UseSnippetResult {
|
|
@@ -24,6 +24,6 @@ interface SnippetViewProps {
|
|
|
24
24
|
errorFallback?: ReactNode;
|
|
25
25
|
onLoaded?: (snippet?: Snippet) => void;
|
|
26
26
|
}
|
|
27
|
-
declare function SnippetView({ slug, className, loadingFallback, errorFallback, onLoaded }: SnippetViewProps): react_jsx_runtime.JSX.Element;
|
|
27
|
+
declare function SnippetView({ slug, className, loadingFallback, errorFallback, onLoaded, }: SnippetViewProps): react_jsx_runtime.JSX.Element;
|
|
28
28
|
|
|
29
29
|
export { SnippetProvider, type SnippetProviderProps, SnippetView, type SnippetViewProps, type UseSnippetResult, useSnippet, useSnippetClient };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,11 @@ import { createContext, useContext, useMemo } from "react";
|
|
|
3
3
|
import { SnippetClient } from "@mzebley/mark-down";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
var SnippetClientContext = createContext(null);
|
|
6
|
-
function SnippetProvider({
|
|
6
|
+
function SnippetProvider({
|
|
7
|
+
client,
|
|
8
|
+
options,
|
|
9
|
+
children
|
|
10
|
+
}) {
|
|
7
11
|
const value = useMemo(() => {
|
|
8
12
|
if (client) {
|
|
9
13
|
return client;
|
|
@@ -63,7 +67,15 @@ function SnippetView({
|
|
|
63
67
|
onLoaded
|
|
64
68
|
}) {
|
|
65
69
|
const state = useSnippet(slug);
|
|
66
|
-
const safeHtml = useMemo2(() =>
|
|
70
|
+
const safeHtml = useMemo2(() => {
|
|
71
|
+
if (!state.snippet) {
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
74
|
+
if (typeof window === "undefined") {
|
|
75
|
+
return state.snippet.html;
|
|
76
|
+
}
|
|
77
|
+
return DOMPurify.sanitize(state.snippet.html);
|
|
78
|
+
}, [state.snippet]);
|
|
67
79
|
useEffect2(() => {
|
|
68
80
|
onLoaded?.(state.snippet);
|
|
69
81
|
}, [state.snippet, onLoaded]);
|
|
@@ -76,7 +88,13 @@ function SnippetView({
|
|
|
76
88
|
if (!state.snippet) {
|
|
77
89
|
return /* @__PURE__ */ jsx2("div", { className, children: "Snippet not found" });
|
|
78
90
|
}
|
|
79
|
-
return /* @__PURE__ */ jsx2(
|
|
91
|
+
return /* @__PURE__ */ jsx2(
|
|
92
|
+
"div",
|
|
93
|
+
{
|
|
94
|
+
className,
|
|
95
|
+
dangerouslySetInnerHTML: { __html: safeHtml ?? "" }
|
|
96
|
+
}
|
|
97
|
+
);
|
|
80
98
|
}
|
|
81
99
|
export {
|
|
82
100
|
SnippetProvider,
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mzebley/mark-down-react",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "mark↓ React Adapter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
9
12
|
"publishConfig": {
|
|
10
13
|
"access": "public"
|
|
11
14
|
},
|
|
@@ -13,7 +16,7 @@
|
|
|
13
16
|
"build": "tsup src/index.ts --dts --format esm,cjs"
|
|
14
17
|
},
|
|
15
18
|
"dependencies": {
|
|
16
|
-
"@mzebley/mark-down": "^1.2.
|
|
19
|
+
"@mzebley/mark-down": "^1.2.2",
|
|
17
20
|
"dompurify": "^3.0.5"
|
|
18
21
|
},
|
|
19
22
|
"peerDependencies": {
|
package/src/context.tsx
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { createContext, ReactNode, useContext, useMemo } from "react";
|
|
2
|
-
import { SnippetClient, type SnippetClientOptions } from "@mzebley/mark-down";
|
|
3
|
-
|
|
4
|
-
const SnippetClientContext = createContext<SnippetClient | null>(null);
|
|
5
|
-
|
|
6
|
-
export interface SnippetProviderProps {
|
|
7
|
-
client?: SnippetClient;
|
|
8
|
-
options?: SnippetClientOptions;
|
|
9
|
-
children: ReactNode;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function SnippetProvider({ client, options, children }: SnippetProviderProps) {
|
|
13
|
-
const value = useMemo(() => {
|
|
14
|
-
if (client) {
|
|
15
|
-
return client;
|
|
16
|
-
}
|
|
17
|
-
if (options) {
|
|
18
|
-
return new SnippetClient(options);
|
|
19
|
-
}
|
|
20
|
-
throw new Error("SnippetProvider requires either a client or options");
|
|
21
|
-
}, [client, options]);
|
|
22
|
-
|
|
23
|
-
return <SnippetClientContext.Provider value={value}>{children}</SnippetClientContext.Provider>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function useSnippetClient(): SnippetClient {
|
|
27
|
-
const client = useContext(SnippetClientContext);
|
|
28
|
-
if (!client) {
|
|
29
|
-
throw new Error("useSnippetClient must be used within a SnippetProvider");
|
|
30
|
-
}
|
|
31
|
-
return client;
|
|
32
|
-
}
|
package/src/hooks.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import type { Snippet } from "@mzebley/mark-down";
|
|
3
|
-
import { useSnippetClient } from "./context";
|
|
4
|
-
|
|
5
|
-
export interface UseSnippetResult {
|
|
6
|
-
snippet?: Snippet;
|
|
7
|
-
loading: boolean;
|
|
8
|
-
error?: Error;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function useSnippet(slug?: string | null): UseSnippetResult {
|
|
12
|
-
const client = useSnippetClient();
|
|
13
|
-
const [result, setResult] = useState<UseSnippetResult>(() => ({
|
|
14
|
-
loading: Boolean(slug)
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
if (!slug) {
|
|
19
|
-
setResult({ loading: false });
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let cancelled = false;
|
|
24
|
-
setResult((prev) => ({ ...prev, loading: true, error: undefined }));
|
|
25
|
-
|
|
26
|
-
client
|
|
27
|
-
.get(slug)
|
|
28
|
-
.then((snippet) => {
|
|
29
|
-
if (cancelled) return;
|
|
30
|
-
setResult({ snippet, loading: false });
|
|
31
|
-
})
|
|
32
|
-
.catch((error: Error) => {
|
|
33
|
-
if (cancelled) return;
|
|
34
|
-
setResult({ loading: false, error });
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
return () => {
|
|
38
|
-
cancelled = true;
|
|
39
|
-
};
|
|
40
|
-
}, [client, slug]);
|
|
41
|
-
|
|
42
|
-
return result;
|
|
43
|
-
}
|
package/src/index.ts
DELETED
package/src/snippet-view.tsx
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { ReactNode, useEffect, useMemo } from "react";
|
|
2
|
-
import DOMPurify from "dompurify";
|
|
3
|
-
import type { Snippet } from "@mzebley/mark-down";
|
|
4
|
-
import { useSnippet } from "./hooks";
|
|
5
|
-
|
|
6
|
-
export interface SnippetViewProps {
|
|
7
|
-
slug: string;
|
|
8
|
-
className?: string;
|
|
9
|
-
loadingFallback?: ReactNode;
|
|
10
|
-
errorFallback?: ReactNode;
|
|
11
|
-
onLoaded?: (snippet?: Snippet) => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function SnippetView({
|
|
15
|
-
slug,
|
|
16
|
-
className,
|
|
17
|
-
loadingFallback = "Loading…",
|
|
18
|
-
errorFallback = "Unable to load snippet",
|
|
19
|
-
onLoaded
|
|
20
|
-
}: SnippetViewProps) {
|
|
21
|
-
const state = useSnippet(slug);
|
|
22
|
-
const safeHtml = useMemo(() => (state.snippet ? DOMPurify.sanitize(state.snippet.html) : undefined), [state.snippet]);
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
onLoaded?.(state.snippet);
|
|
26
|
-
}, [state.snippet, onLoaded]);
|
|
27
|
-
|
|
28
|
-
if (state.loading) {
|
|
29
|
-
return <div className={className}>{loadingFallback}</div>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (state.error) {
|
|
33
|
-
return <div className={className}>{errorFallback}</div>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!state.snippet) {
|
|
37
|
-
return <div className={className}>Snippet not found</div>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return <div className={className} dangerouslySetInnerHTML={{ __html: safeHtml ?? "" }} />;
|
|
41
|
-
}
|