diff --git a/contentlayer.config.ts b/contentlayer.config.ts index 4861ccd..2c86b48 100644 --- a/contentlayer.config.ts +++ b/contentlayer.config.ts @@ -7,6 +7,7 @@ import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' import remarkToc from 'remark-toc' import remarkMermaid from 'remark-mermaidjs' +import { preProcess, postProcess } from './src/lib/rehype-utils' const Post = defineDocumentType(() => ({ name: 'Post', @@ -81,25 +82,27 @@ export default makeSource({ remarkMermaid as any ], rehypePlugins: [ + preProcess, rehypeSlug, rehypeKatex as any, [rehypeAutolinkHeadings, 'after'], [rehypePrettyCode, { theme: 'solarized-light', - onVisitLine(node: { children: string | any[] }) { - // Prevent lines from collapsing in `display: grid` mode, and allow empty - // lines to be copy/pasted - if (node.children.length === 0) { - node.children = [{ type: "text", value: " " }] - } - }, - onVisitHighlightedLine(node: { properties: { className: string[] } }) { - node.properties.className.push("line--highlighted") - }, - onVisitHighlightedWord(node: { properties: { className: string[] } }) { - node.properties.className = ["word--highlighted"] - }, + // onVisitLine(node: { children: string | any[] }) { + // // Prevent lines from collapsing in `display: grid` mode, and allow empty + // // lines to be copy/pasted + // if (node.children.length === 0) { + // node.children = [{ type: "text", value: " " }] + // } + // }, + // onVisitHighlightedLine(node: { properties: { className: string[] } }) { + // node.properties.className.push("line--highlighted") + // }, + // onVisitHighlightedWord(node: { properties: { className: string[] } }) { + // node.properties.className = ["word--highlighted"] + // }, }], + postProcess ] } }) \ No newline at end of file diff --git a/src/app/_components/clipboard-copy.css b/src/app/_components/clipboard-copy.css new file mode 100644 index 0000000..0328033 --- /dev/null +++ b/src/app/_components/clipboard-copy.css @@ -0,0 +1,23 @@ +.copyCode { + position: relative; + button { + z-index: 1; + position: absolute; + top: 13px; + right: -10px; + background-color: var(--code-highlight); + border-radius: 5px; + text-transform: uppercase; + font-size: 13px; + padding: .1rem .4rem .2rem; + color: var(--color-bg); + } +} + +button.copyCode::after { + content: "📋"; +} + +.active button.copyCode::after { + content: "☑️"; +} \ No newline at end of file diff --git a/src/app/_components/clipboard-copy.tsx b/src/app/_components/clipboard-copy.tsx new file mode 100644 index 0000000..e56e900 --- /dev/null +++ b/src/app/_components/clipboard-copy.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { useState } from "react"; + +function ClipboardCopy({ copyText }) { + const [isCopied, setIsCopied] = useState(false); + console.info('copyText passed in:', copyText); + + // This is the function we wrote earlier + async function copyTextToClipboard(text) { + if ('clipboard' in navigator) { + return await navigator.clipboard.writeText(text); + } else { + return document.execCommand('copy', true, text); + } + } + + // onClick handler function for the copy button + const handleCopyClick = () => { + // Asynchronously call copyTextToClipboard + copyTextToClipboard(copyText) + .then(() => { + console.info('Copied successfully: ', copyText); + // If successful, update the isCopied state value + setIsCopied(true); + setTimeout(() => { + setIsCopied(false); + }, 1500); + }) + .catch((err) => { + console.log(err); + }); + } + + return ( +
+ {/* Bind our handler function to the onClick button property */} + +
+ ); +} + +export default ClipboardCopy; \ No newline at end of file diff --git a/src/app/_components/code-block.tsx b/src/app/_components/code-block.tsx new file mode 100644 index 0000000..767f93f --- /dev/null +++ b/src/app/_components/code-block.tsx @@ -0,0 +1,21 @@ +'use client'; + +import clsx from "clsx"; +import ClipboardCopy from "./clipboard-copy"; +// import './clipboard-copy.css' + +const CodeBlock = ({ className, children, raw, ...props }) => { + console.log('raw: ', raw); + return ( + <> +
+
+                    {children}
+                    
+                
+
+ + ) +} + +export default CodeBlock; \ No newline at end of file diff --git a/src/app/_components/markdown-post.tsx b/src/app/_components/markdown-post.tsx index e1390d8..3b41a9b 100644 --- a/src/app/_components/markdown-post.tsx +++ b/src/app/_components/markdown-post.tsx @@ -1,6 +1,6 @@ import { useMDXComponent } from "next-contentlayer/hooks"; import { MDXComponents } from "mdx/types"; -import clsx from "clsx"; +import CodeBlock from "./code-block"; const components: MDXComponents = { // Allows customizing built-in components, e.g. to add styling. @@ -12,24 +12,18 @@ const components: MDXComponents = { blockquote: ({ children }) =>
{children}
, ul: ({ children }) => , li: ({ children }) =>
  • {children}
  • , - pre: ({ className, children, ...props }) => { - return ( -
    -                {children}
    -            
    - ) - }, - code: ({children}) => {children}, + pre: ({ className, children, raw, ...props}) => , + code: ({ children }) => {children}, lead: ({ children }) =>

    {children}

    , a: ({ children, href }) => ( {children} ), - table: ({children}) => {children}
    , - tbody: ({children}) => {children}, - thead: ({children}) => {children}, - th: ({children}) => {children}, - td: ({children}) => {children}, - tr: ({children}) => {children} + table: ({ children }) => {children}
    , + tbody: ({ children }) => {children}, + thead: ({ children }) => {children}, + th: ({ children }) => {children}, + td: ({ children }) => {children}, + tr: ({ children }) => {children} }; interface MarkdownPostProps { diff --git a/src/lib/rehype-utils.ts b/src/lib/rehype-utils.ts new file mode 100644 index 0000000..212c5ed --- /dev/null +++ b/src/lib/rehype-utils.ts @@ -0,0 +1,32 @@ +import { visit } from 'unist-util-visit' + +export const preProcess = () => (tree) => { + visit(tree, (node) => { + if (node?.type === 'element' && node?.tagName === 'pre') { + const [codeEl] = node.children + + if (codeEl.tagName !== 'code') return + + node.raw = codeEl.children?.[0].value + // console.info('node preProcessed: ', node); + } + }) +} + +export const postProcess = () => (tree) => { + visit(tree, (node) => { + if (node?.type === 'element' && node?.tagName === 'figure') { + if (!("data-rehype-pretty-code-figure" in node.properties)) { + return; + } + + for (const child of node.children) { + if (child.tagName === "pre") { + child.properties["raw"] = node.raw; + } + } + node.properties['raw'] = node.raw + console.log('PostProcessed: ', node); // here to see if you're getting the raw text + } + }) +}