Merge pull request #13
chore: Sync package version v1.16.1 - Introduce language-specific icons in code blocks using react-icons - Implement custom handleCodeTitles rehype plugin to support data-title attribute - Refactor PreMdx component to display language icons and titles - Add comprehensive styling for code blocks including header, actions, and syntax highlighting - Update dependencies to include react-icons and remove unused install package - Bump version to 1.16.1 in both root and dist package.json files
This commit is contained in:
@@ -25,7 +25,7 @@ export default function Home() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<AnimatedShinyText className="inline-flex items-center justify-center px-4 py-1 transition ease-out hover:text-neutral-100 hover:duration-300 hover:dark:text-neutral-200">
|
<AnimatedShinyText className="inline-flex items-center justify-center px-4 py-1 transition ease-out hover:text-neutral-100 hover:duration-300 hover:dark:text-neutral-200">
|
||||||
<span>🚀 New Version - Release v1.16.0</span>
|
<span>🚀 New Version - Release v1.16.1</span>
|
||||||
<ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
<ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
||||||
</AnimatedShinyText>
|
</AnimatedShinyText>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,109 @@
|
|||||||
import { ComponentProps } from "react";
|
import { type ComponentProps } from "react";
|
||||||
import Copy from "./CopyMdx";
|
import Copy from "./CopyMdx";
|
||||||
|
import {
|
||||||
|
SiJavascript,
|
||||||
|
SiTypescript,
|
||||||
|
SiReact,
|
||||||
|
SiPython,
|
||||||
|
SiGo,
|
||||||
|
SiPhp,
|
||||||
|
SiRuby,
|
||||||
|
SiSwift,
|
||||||
|
SiKotlin,
|
||||||
|
SiHtml5,
|
||||||
|
SiCss3,
|
||||||
|
SiSass,
|
||||||
|
SiPostgresql,
|
||||||
|
SiGraphql,
|
||||||
|
SiYaml,
|
||||||
|
SiToml,
|
||||||
|
SiDocker,
|
||||||
|
SiNginx,
|
||||||
|
SiGit,
|
||||||
|
SiGnubash,
|
||||||
|
SiMarkdown,
|
||||||
|
} from "react-icons/si";
|
||||||
|
import { FaJava, FaCode } from "react-icons/fa";
|
||||||
|
import { TbJson } from "react-icons/tb";
|
||||||
|
|
||||||
|
type PreProps = ComponentProps<"pre"> & {
|
||||||
|
raw?: string;
|
||||||
|
"data-title"?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Component to display an icon based on the programming language
|
||||||
|
const LanguageIcon = ({ lang }: { lang: string }) => {
|
||||||
|
const iconProps = { className: "w-4 h-4" };
|
||||||
|
const languageToIconMap: Record<string, JSX.Element> = {
|
||||||
|
gitignore: <SiGit {...iconProps} />,
|
||||||
|
docker: <SiDocker {...iconProps} />,
|
||||||
|
dockerfile: <SiDocker {...iconProps} />,
|
||||||
|
nginx: <SiNginx {...iconProps} />,
|
||||||
|
sql: <SiPostgresql {...iconProps} />,
|
||||||
|
graphql: <SiGraphql {...iconProps} />,
|
||||||
|
yaml: <SiYaml {...iconProps} />,
|
||||||
|
yml: <SiYaml {...iconProps} />,
|
||||||
|
toml: <SiToml {...iconProps} />,
|
||||||
|
json: <TbJson {...iconProps} />,
|
||||||
|
md: <SiMarkdown {...iconProps} />,
|
||||||
|
markdown: <SiMarkdown {...iconProps} />,
|
||||||
|
bash: <SiGnubash {...iconProps} />,
|
||||||
|
sh: <SiGnubash {...iconProps} />,
|
||||||
|
shell: <SiGnubash {...iconProps} />,
|
||||||
|
swift: <SiSwift {...iconProps} />,
|
||||||
|
kotlin: <SiKotlin {...iconProps} />,
|
||||||
|
kt: <SiKotlin {...iconProps} />,
|
||||||
|
kts: <SiKotlin {...iconProps} />,
|
||||||
|
rb: <SiRuby {...iconProps} />,
|
||||||
|
ruby: <SiRuby {...iconProps} />,
|
||||||
|
php: <SiPhp {...iconProps} />,
|
||||||
|
go: <SiGo {...iconProps} />,
|
||||||
|
py: <SiPython {...iconProps} />,
|
||||||
|
python: <SiPython {...iconProps} />,
|
||||||
|
java: <FaJava {...iconProps} />,
|
||||||
|
tsx: <SiReact {...iconProps} />,
|
||||||
|
typescript: <SiTypescript {...iconProps} />,
|
||||||
|
ts: <SiTypescript {...iconProps} />,
|
||||||
|
jsx: <SiReact {...iconProps} />,
|
||||||
|
js: <SiJavascript {...iconProps} />,
|
||||||
|
javascript: <SiJavascript {...iconProps} />,
|
||||||
|
html: <SiHtml5 {...iconProps} />,
|
||||||
|
css: <SiCss3 {...iconProps} />,
|
||||||
|
scss: <SiSass {...iconProps} />,
|
||||||
|
sass: <SiSass {...iconProps} />,
|
||||||
|
};
|
||||||
|
return languageToIconMap[lang] || <FaCode {...iconProps} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to extract the language from className
|
||||||
|
function getLanguage(className: string = ""): string {
|
||||||
|
const match = className.match(/language-(\w+)/);
|
||||||
|
return match ? match[1] : "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Pre({ children, raw, ...rest }: PreProps) {
|
||||||
|
const { "data-title": title, className, ...restProps } = rest;
|
||||||
|
const language = getLanguage(className);
|
||||||
|
const hasTitle = !!title;
|
||||||
|
|
||||||
export default function Pre({
|
|
||||||
children,
|
|
||||||
raw,
|
|
||||||
...rest
|
|
||||||
}: ComponentProps<"pre"> & { raw?: string }) {
|
|
||||||
return (
|
return (
|
||||||
<div className="my-5 relative">
|
<div className="code-block-container">
|
||||||
<div className="absolute top-3 right-2.5 z-10 sm:block hidden">
|
<div className="code-block-actions">
|
||||||
<Copy content={raw!} />
|
{raw && <Copy content={raw} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
{hasTitle && (
|
||||||
<pre {...rest}>{children}</pre>
|
<div className="code-block-header">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<LanguageIcon lang={language} />
|
||||||
|
<span>{title}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="code-block-body">
|
||||||
|
<pre className={className} {...restProps}>
|
||||||
|
{children}
|
||||||
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ import rehypeSlug from "rehype-slug";
|
|||||||
import rehypeCodeTitles from "rehype-code-titles";
|
import rehypeCodeTitles from "rehype-code-titles";
|
||||||
import { page_routes, ROUTES } from "./routes-config";
|
import { page_routes, ROUTES } from "./routes-config";
|
||||||
import { visit } from "unist-util-visit";
|
import { visit } from "unist-util-visit";
|
||||||
import type { Node } from "unist";
|
import type { Node, Parent } from "unist";
|
||||||
import matter from "gray-matter";
|
import matter from "gray-matter";
|
||||||
|
|
||||||
// Type definitions for unist-util-visit
|
// Type definitions for unist-util-visit
|
||||||
@@ -16,6 +16,7 @@ interface Element extends Node {
|
|||||||
type: string;
|
type: string;
|
||||||
tagName?: string;
|
tagName?: string;
|
||||||
properties?: Record<string, unknown> & {
|
properties?: Record<string, unknown> & {
|
||||||
|
className?: string[];
|
||||||
raw?: string;
|
raw?: string;
|
||||||
};
|
};
|
||||||
children?: Node[];
|
children?: Node[];
|
||||||
@@ -77,6 +78,49 @@ const components = {
|
|||||||
AccordionGroup
|
AccordionGroup
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// helper function to handle rehype code titles, since by default we can't inject into the className of rehype-code-titles
|
||||||
|
const handleCodeTitles = () => (tree: Node) => {
|
||||||
|
visit(tree, "element", (node: Element, index: number | null, parent: Parent | null) => {
|
||||||
|
// Ensure the visited node is valid
|
||||||
|
if (!parent || index === null || node.tagName !== 'div') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the title div from rehype-code-titles
|
||||||
|
const isTitleDiv = node.properties?.className?.includes('rehype-code-title');
|
||||||
|
if (!isTitleDiv) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the next <pre> element, skipping over other nodes like whitespace text
|
||||||
|
let nextElement = null;
|
||||||
|
for (let i = index + 1; i < parent.children.length; i++) {
|
||||||
|
const sibling = parent.children[i];
|
||||||
|
if (sibling.type === 'element') {
|
||||||
|
nextElement = sibling as Element;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the next element is a <pre>, move the title to it
|
||||||
|
if (nextElement && nextElement.tagName === 'pre') {
|
||||||
|
const titleNode = node.children?.[0] as TextNode;
|
||||||
|
if (titleNode && titleNode.type === 'text') {
|
||||||
|
if (!nextElement.properties) {
|
||||||
|
nextElement.properties = {};
|
||||||
|
}
|
||||||
|
nextElement.properties['data-title'] = titleNode.value;
|
||||||
|
|
||||||
|
// Remove the original title div
|
||||||
|
parent.children.splice(index, 1);
|
||||||
|
|
||||||
|
// Return the same index to continue visiting from the correct position
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// can be used for other pages like blogs, Guides etc
|
// can be used for other pages like blogs, Guides etc
|
||||||
async function parseMdx<Frontmatter>(rawMdx: string) {
|
async function parseMdx<Frontmatter>(rawMdx: string) {
|
||||||
return await compileMDX<Frontmatter>({
|
return await compileMDX<Frontmatter>({
|
||||||
@@ -87,6 +131,7 @@ async function parseMdx<Frontmatter>(rawMdx: string) {
|
|||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
preProcess,
|
preProcess,
|
||||||
rehypeCodeTitles,
|
rehypeCodeTitles,
|
||||||
|
handleCodeTitles,
|
||||||
rehypePrism,
|
rehypePrism,
|
||||||
rehypeSlug,
|
rehypeSlug,
|
||||||
rehypeAutolinkHeadings,
|
rehypeAutolinkHeadings,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "docubook",
|
"name": "docubook",
|
||||||
"version": "1.16.0",
|
"version": "1.16.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
@@ -30,13 +30,13 @@
|
|||||||
"framer-motion": "^12.4.1",
|
"framer-motion": "^12.4.1",
|
||||||
"geist": "^1.3.1",
|
"geist": "^1.3.1",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"install": "^0.13.0",
|
|
||||||
"lucide-react": "^0.511.0",
|
"lucide-react": "^0.511.0",
|
||||||
"next": "^14.2.6",
|
"next": "^14.2.6",
|
||||||
"next-mdx-remote": "^5.0.0",
|
"next-mdx-remote": "^5.0.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-code-titles": "^1.2.0",
|
"rehype-code-titles": "^1.2.0",
|
||||||
"rehype-prism-plus": "^2.0.0",
|
"rehype-prism-plus": "^2.0.0",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ pre>code {
|
|||||||
display: grid;
|
display: grid;
|
||||||
max-width: inherit !important;
|
max-width: inherit !important;
|
||||||
padding: 14px 0 !important;
|
padding: 14px 0 !important;
|
||||||
|
border: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-line {
|
.code-line {
|
||||||
@@ -138,4 +139,4 @@ pre>code {
|
|||||||
background-position: 0% 0%;
|
background-position: 0% 0%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,3 +93,87 @@
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px; /* Sudut melengkung pada iframe */
|
border-radius: 8px; /* Sudut melengkung pada iframe */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================================================================== */
|
||||||
|
/* Custom styling for code blocks */
|
||||||
|
/* ======================================================================== */
|
||||||
|
|
||||||
|
.code-block-container {
|
||||||
|
position: relative;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background-color: hsl(var(--muted));
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-bottom: 1px solid hsl(var(--border));
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.75rem;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.code-block-actions button {
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
transition: color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.code-block-actions button:hover {
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.code-block-body pre[class*="language-"] {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 3rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
text-align: right;
|
||||||
|
color: var(--line-number-color);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-highlight {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: hsl(var(--primary) / 0.1);
|
||||||
|
border-left: 2px solid hsl(var(--primary));
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-body pre[data-line-numbers="true"] .line-highlight {
|
||||||
|
padding-left: 3.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-body::-webkit-scrollbar {
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
.code-block-body::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.code-block-body::-webkit-scrollbar-thumb {
|
||||||
|
background: hsl(var(--border));
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.code-block-body::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user