[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.css
so 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].tsx
file where the blog gets rendered, I need to manually grab the theme from theuse-theme
hook 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-markdown
and syntax-highlighted with therehypeHighlight
plugin. -
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
flattenDeep
function fromlodash
can 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
DeepArray
needs 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
/portfolio
page and/linktree
What I learned
- Introduction to
lodash.js
- Recursive types in TypeScript
- Theme customization with tailwindCSS