[Devlog 1] Code-highlighting, Copy-paste
Nathan Luong | March 12, 2023 |5
Expanding Blogs Functionalities
Add light mode for blogs/*
On the bottom right-hand side, above the back-to-top button, there is an option to toggle light mode in the blog reader. At the time, this mode will only be useable on the blogs/* domain.
Adding light mode on the site is somewhat difficult since I'm using a mix of CSS-modules and tailwind-css on this particular page.
To get around that, I used some ugly fixes that I'm not so proud of.
- First, I refactor the
mdBlogs.moudule.cssso that it contains different attributes for dark or light mode.
.postLight a {
color: #018fbd;
}
.postLight a:hover {
color: #00c1ff;
}
.postDark a {
color: #018fbd;
}
.postDark a:hover {
color: #00c1ff;
}
- In the
[slug].tsxfile where the blog gets rendered, I need to manually grab the theme from theuse-themehook and conditionally add the className into the components with some additional tailwind styling. This led to an ugly solution.
<ReactMarkdown
className={
theme === "dark"
? `${styles.postDark} ${styles.post} w-full z-10 text-lighttext`
: `${styles.postLight} ${styles.post} w-full z-10 text-darktext`
}
>
...
</ReactMarkdown>
Mapping Markdown code into React components
-
Light theme code syntax highlighting: As seen for all code blocks on the page, after being parsed from a markdown file, it got converted to HTML via
react-markdownand syntax-highlighted with therehypeHighlightplugin. -
Wrapper for code blocks: With each code block rendered by
react-markdown, it got mapped into a custom React component wrapper that provides additional functionality (ie: copy-pasting) and states to handle UI interactions (ie:setTimeout()).
pre: (element: { children?: any; node?: any }) => {
const [copied, setCopied] = useState(false);
return (
<div>
<CopyPasteButton .../>
<pre className="rounded-md code-block">{element.children}</pre>
</div>
)
}
- The copy-paste functionality requires recursively traversing through a React component tree where a node can contain a list of nodes. The
flattenDeepfunction fromlodashcan help with this problem.
// Traverse through the tree recursively
// retrun a multi-dimentional array containing the code.
const preProcess = (data: preProcessProps[]) => {
const result: DeepArray<String> = [];
data.forEach((item) => {
if (typeof item === "object") {
result.push(preProcess(item.props.children));
} else {
result.push(item);
}
});
return result;
};
// Copy to clipboard the string representing the flattened array
pre: (element: { children?: any; node?: any }) => {
// ...
navigator.clipboard.writeText(
flattenDeep(
preProcess(element.children[0].props.children)
).join("")
);
// ...
}
- To support such an operation, type
DeepArrayneeds to be recursive:
type DeepArray<T> = T | Array<DeepArray<T>>;
type dataProps = {
props: {
children: dataProps[];
};
};
type preProcessProps = string | dataProps;
Plans for the future
- Make blogs RSS-compatible
- Light mode on
/portfoliopage and/linktree
What I learned
- Introduction to
lodash.js - Recursive types in TypeScript
- Theme customization with tailwindCSS