Repository navigation
Expand file tree
/
Copy pathroot-wrapper.js
More file actions
142 lines (131 loc) · 4.5 KB
/
Copy pathroot-wrapper.js
File metadata and controls
142 lines (131 loc) · 4.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import React from "react";
import { MDXProvider } from "@mdx-js/react";
import Code from "./src/components/CodeBlock";
import CTA_ImageOnly from "./src/components/Call-To-Actions/CTA_ImageOnly";
import CTA_FullWidth from "./src/components/Call-To-Actions/CTA_FullWidth";
import CTA_Bottom from "./src/components/Call-To-Actions/CTA_Bottom";
import { ContextWrapper } from "./context-wrapper";
import { IoIosCopy } from "@react-icons/all-files/io/IoIosCopy";
import { IoIosCheckmark } from "@react-icons/all-files/io/IoIosCheckmark";
import SimpleReactLightbox from "simple-react-lightbox";
// Custom image component for better CLS scores.
// The wrapper is a <span> rather than a <div>: Markdown images render inside a
// <p>, and a block-level wrapper there is invalid HTML that the browser's
// parser re-shapes, which breaks hydration.
const OptimizedImage = (props) => {
return (
<span style={{ display: "block", width: "100%", height: "auto" }}>
<img
{...props}
width={props.width || "100%"}
height={props.height || "auto"}
style={{
objectFit: props.objectFit || "contain",
margin: "20px 0px",
...props.style,
}}
loading="lazy"
alt={props.alt || "Blog content image"}
/>
</span>
);
};
// Returns true only for commands like `npm install`, `npx skill install`
// Returns false for paths like `~/.claude/skills/`, flags like `-sfn`
const isCommand = (text) => {
const str = String(text).trim();
return !/^[~/.]/.test(str);
};
// Inline code component with copy-on-hover button
const InlineCode = ({ children }) => {
const [copied, setCopied] = React.useState(false);
const [hovered, setHovered] = React.useState(false);
const codeRef = React.useRef(null);
const showCopyButton = isCommand(children);
const handleCopy = () => {
const text = codeRef.current?.textContent || children;
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<span
style={{
position: "relative",
display: "inline-flex",
alignItems: "center",
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<code ref={codeRef}>{children}</code>
{showCopyButton && (
<span
style={{
display: "inline-block",
width: hovered ? "22px" : "0px",
marginLeft: hovered ? "4px" : "0px",
overflow: "hidden",
transition: "width 0.2s ease, margin 0.2s ease",
verticalAlign: "middle",
}}
>
<button
onClick={handleCopy}
title="Copy to clipboard"
style={{
background: "transparent",
border: "none",
cursor: "pointer",
padding: "2px 2px",
color: "inherit",
opacity: hovered ? 1 : 0,
transition: "opacity 0.2s ease",
borderRadius: "3px",
display: "flex",
alignItems: "center",
}}
aria-label="Copy to clipboard"
>
{copied ? <IoIosCheckmark size={16} /> : <IoIosCopy size={14} />}
</button>
</span>
)}
</span>
);
};
// A fenced code block compiles to <pre><code className="language-x">text</code></pre>.
// MDX v1 tagged that child with `mdxType: "code"`; MDX v2+ does not, so matching
// on it rendered every fenced block as nothing. Detect the shape instead, and
// fall back to a plain <pre> rather than returning undefined.
const FencedCode = ({ children, ...preProps }) => {
const child = React.Children.toArray(children)[0];
const code = React.isValidElement(child) ? child.props.children : undefined;
if (typeof code !== "string") {
return <pre {...preProps}>{children}</pre>;
}
// Drop only the newline the MDX compiler appends; trim() would also strip
// meaningful indentation on the first line (e.g. a nested YAML snippet).
const language = child.props.className?.replace(/^language-/, "");
return (
<Code
codeString={code.replace(/\r?\n$/, "")}
language={language || undefined}
/>
);
};
const components = {
pre: FencedCode,
img: OptimizedImage,
code: InlineCode,
CTA_ImageOnly,
CTA_FullWidth,
CTA_Bottom,
};
export const wrapRootElement = ({ element }) => (
<ContextWrapper>
<SimpleReactLightbox>
<MDXProvider components={components}>{element}</MDXProvider>
</SimpleReactLightbox>
</ContextWrapper>
);