/ src / components / Mdx.tsx
Mdx.tsx
  1  import {
  2  	Match,
  3  	Switch,
  4  	children,
  5  	createSignal,
  6  	onMount,
  7  	splitProps,
  8  	type ParentProps,
  9  } from "solid-js";
 10  import { A } from "@solidjs/router";
 11  import { QuickLinks, type QuickLinksProps } from "../ingredients/quick-link";
 12  import { Emph, type EmphProps } from "../ingredients/emph";
 13  import cfg from "../constant";
 14  import Reveal from "~/ingredients/rand-reveal";
 15  import { twMerge } from "tailwind-merge";
 16  import Comment from "~/ingredients/comment";
 17  
 18  const cstomLink = (props: ParentProps & { href: string }) => {
 19  	const [, rest] = splitProps(props, ["children"]);
 20  	const resolved = children(() => props.children);
 21  
 22  	const [childRef, setChildRef] = createSignal<HTMLDivElement>();
 23  	const [inlineAClass, setInlineAClass] = createSignal("");
 24  
 25  	onMount(() => {
 26  		if (childRef && childRef()?.parentElement) {
 27  			if (childRef()!.parentElement?.tagName.startsWith("H")) {
 28  				setInlineAClass(`anchor no-underline`);
 29  			} else {
 30  				setInlineAClass(`anchor decoration-dotted underline-offset-4`);
 31  			}
 32  		}
 33  	});
 34  
 35  	if (props.href.startsWith("#")) {
 36  
 37  		return (
 38  			<A ref={setChildRef} class={inlineAClass()} {...rest} noScroll={true}>
 39  				{resolved()}
 40  			</A>
 41  		);
 42  	}
 43  
 44  	return (
 45  		<A
 46  			class="underline-offset-4 decoration-2 hover:underline-offset-2 transition-all inline items-center space-x-px group break-all pr-0.5 font-normal"
 47  			target="_blank"
 48  			rel="noopener noreferrer nofollow"
 49  			{...rest}
 50  		>
 51  			<span>{resolved()}</span>
 52  		</A>
 53  	);
 54  };
 55  
 56  const imgContent = (
 57  	props: ParentProps & {
 58  		class: string;
 59  		src: string;
 60  		alt: string;
 61  		title: string;
 62  		ref: (el: HTMLVideoElement) => void | undefined;
 63  	},
 64  ) => (
 65  	<Switch
 66  		fallback={
 67  			<img
 68  				class={`w-full ${props.class}`}
 69  				src={props.src}
 70  				alt={props.alt}
 71  				loading="lazy"
 72  			/>
 73  		}
 74  	>
 75  		<Match when={!props.src.startsWith("http")}>
 76  			<img
 77  				class={`w-full ${props.class}`}
 78  				src={cfg.obj_store + "/" + props.src}
 79  				alt={props.alt}
 80  				loading="lazy"
 81  			/>
 82  		</Match>
 83  
 84  		<Match when={props.src.endsWith(".webm")}>
 85  			<video
 86  				ref={props.ref}
 87  				src={props.src}
 88  				class={`w-full squircle rounded-md ${props.class}`}
 89  				controls={props.title === "controls"}
 90  				muted={props.title !== "controls"}
 91  				autoplay={props.title !== "controls"}
 92  				loop={props.title !== "controls"}
 93  				preload="metadata"
 94  				playsinline
 95  			>
 96  				<source src={props.src} type="video/webm" />
 97  			</video>
 98  		</Match>
 99  	</Switch>
100  );
101  
102  const components = {
103  	p: (props: ParentProps) => <p {...props} class="leading-relaxed font-sans text-md">{props.children}</p>,
104  	nav: (props: ParentProps) => <nav {...props}>{props.children}</nav>,
105  	TesterComponent: () => (
106  		<p>
107  			Remove This Now!!! If you see this it means that markdown custom
108  			components does work
109  		</p>
110  	),
111  	hr: (props: ParentProps) => {
112  		return <hr {...props} class="bg-sprout-100 rounded-xl h-0.5 w-full" />;
113  	},
114  	pre: (props: ParentProps) => {
115  		const [codeBlockRef, setCodeBlockRef] = createSignal<
116  			HTMLDivElement | undefined
117  		>();
118  		const [copied, setCopied] = createSignal(false);
119  		const copyToClipboard = () => {
120  			if (codeBlockRef()) {
121  				const codeContent = codeBlockRef()!.innerText; // loaded
122  
123  				if (codeContent) {
124  					navigator.clipboard.writeText(codeContent).then(() => {
125  						setCopied(true);
126  						setTimeout(() => setCopied(false), 2000);
127  					});
128  				}
129  			}
130  		};
131  		return (
132  			<div class="relative group w-full">
133  				<pre
134  					{...props}
135  					class="w-full border bg-[#f9f9f9] px-4 py-2.5 overflow-auto scrollbar scrollbar-rounded"
136  					ref={setCodeBlockRef}
137  				>
138  					{props.children}
139  				</pre>
140  				<button
141  					class="absolute bg-transparent right-2 top-2 h-8 w-8 justify-center items-center flex rounded-md hover:bg-sprout-100 transition-all"
142  					onClick={copyToClipboard}
143  				>
144  					<div
145  						class={twMerge(
146  							"transition-all duration-400 group-hover:text-sprout-500",
147  							copied() ? "group-hover:i-ci:check" : "group-hover:i-ci:copy",
148  						)}
149  					/>
150  				</button>
151  			</div>
152  		);
153  	},
154  	code: (props: ParentProps) => {
155  		const [childRef, setChildRef] = createSignal<HTMLDivElement>();
156  		const [inlineCodeClass, setInlineCodeClass] = createSignal("");
157  
158  		onMount(() => {
159  			if (childRef && childRef()?.parentElement) {
160  				// console.log('Parent tag name:', childRef()!.parentElement?.tagName);
161  				if (childRef()!.parentElement?.tagName != "PRE") {
162  					setInlineCodeClass("bg-sprout-100 px-1 text-sprout-950 rounded-sm inline flex-none font-medium leading-tight max-w-full whitespace-normal break-normal");
163  				}
164  			}
165  		});
166  
167  		return <code ref={setChildRef} class={inlineCodeClass()}>{props.children}</code>;
168  	},
169  
170  	response: (props: ParentProps) => {
171  		return <span>{props.children}</span>;
172  	},
173  	void: (props: ParentProps) => {
174  		return <span>{props.children}</span>;
175  	},
176  	unknown: (props: ParentProps) => {
177  		return <span>{props.children}</span>;
178  	},
179  
180  	QuickLinks: (props: QuickLinksProps) => (
181  		<QuickLinks {...props}>{props.children}</QuickLinks>
182  	),
183  
184  	Emph: (props: EmphProps) => <Emph type={props.type}>{props.children}</Emph>,
185  
186  	Reveal: (props: ParentProps) => <Reveal>{props.children}</Reveal>,
187  
188  	C: (props: ParentProps) => <span class="inline-flex px-0.5">
189  		<Comment>
190  			{props.children}
191  		</Comment>
192  	</span>,
193  
194  	h1: (props: ParentProps) => (
195  		<div>
196  			<h1 {...props} class="prose-h1 flex justify-start items-center font-sans">
197  				<div class="rounded-sm bg-sprout-300 w-5 h-5 mr-2 shadow-md" />
198  				{props.children}
199  			</h1>
200  		</div>
201  	),
202  	h2: (props: ParentProps) => {
203  		return (
204  			<>
205  				<h2 {...props} class="prose-h2 flex justify-start items-center font-sans">
206  					<div class="rounded-sm bg-sprout-300 w-4.5 h-4.5 mr-2 mb-0.5 shadow-md" />
207  					{props.children}
208  				</h2>
209  			</>
210  		);
211  	},
212  	h3: (props: ParentProps) => {
213  		return (
214  			<h3 {...props} class="prose-h3 flex justify-start items-center font-sans">
215  				<div class="rounded-sm bg-sprout-300 w-4 h-4 mr-1.5 mb-px shadow-md" />
216  				{props.children}
217  			</h3>
218  		);
219  	},
220  	h4: (props: ParentProps) => {
221  		return (
222  			<h4 {...props} class="prose-h4 flex justify-start items-center font-sans">
223  				<div class="rounded-sm bg-sprout-300 w-3.5 h-3.5 mr-1.5 mb-px shadow-sm" />
224  				{props.children}
225  			</h4>
226  		);
227  	},
228  	h5: (props: ParentProps) => {
229  		return (
230  			<h5 {...props} class="prose-h5 flex justify-start items-center my-1 font-sans">
231  				<div class="px-2 py-2 mr-1 rounded-sm border border-2 border-sprout-200 shadow-md" />
232  				{props.children}
233  			</h5>
234  		);
235  	},
236  	h6: (props: ParentProps) => (
237  		<h6 {...props} class="prose-h6 flex justify-start items-center my-1 font-sans">
238  			<div class="px-1.5 py-1.5 mr-1 rounded-sm border border-2 border-sprout-200 shadow-md" />
239  			{props.children}
240  		</h6>
241  	),
242  	blockquote: (props: ParentProps) => (
243  		<blockquote class="flex flex-col items-center text-base not-italic font-normal text-zinc-500 my-3">
244  			<div class="flex justify-start h-4 w-full">
245  				<div class="text-3xl text-sprout-400 leading-none">"</div>
246  			</div>
247  			<div class="mx-2.5 not-prose !leading-none">{props.children}</div>
248  			<div class="relative flex justify-end h-4 w-full">
249  				<div class="absolute -top-4 right-4 text-3xl text-sprout-400 leading-none">"</div>
250  			</div>
251  		</blockquote>
252  	),
253  
254  	a: cstomLink,
255  	strong: (props: ParentProps) => <strong class="font-bold" {...props} />,
256  
257  	img: imgContent,
258  	table: (props: ParentProps) => <table>{props.children}</table>,
259  	li: (props: ParentProps) => (
260  		<li {...props} class="mb-2 marker:text-sprout-400 whitespace-normal break-words">
261  			{props.children}
262  		</li>
263  	),
264  	ul: (props: ParentProps) => (
265  		<ul
266  			{...props}
267  			class="pl-6 mb-2 list-disc decoration-sprout-300 marker:text-sprout-400"
268  		>
269  			{props.children}
270  		</ul>
271  	),
272  	ol: (props: ParentProps) => (
273  		<ol {...props}
274  			class="pl-6 mb-2 list-decimal decoration-sprout-300 marker:text-sprout-400">
275  			{props.children}
276  		</ol>
277  	),
278  };
279  
280  export default components;