);
-}
+}
\ No newline at end of file
diff --git a/lib/markdown.ts b/lib/markdown.ts
index a59eca6..9c1ec01 100644
--- a/lib/markdown.ts
+++ b/lib/markdown.ts
@@ -8,7 +8,7 @@ import rehypeSlug from "rehype-slug";
import rehypeCodeTitles from "rehype-code-titles";
import { page_routes, ROUTES } from "./routes-config";
import { visit } from "unist-util-visit";
-import type { Node } from "unist";
+import type { Node, Parent } from "unist";
import matter from "gray-matter";
// Type definitions for unist-util-visit
@@ -16,6 +16,7 @@ interface Element extends Node {
type: string;
tagName?: string;
properties?: Record
& {
+ className?: string[];
raw?: string;
};
children?: Node[];
@@ -77,6 +78,49 @@ const components = {
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 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 , 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
async function parseMdx(rawMdx: string) {
return await compileMDX({
@@ -87,6 +131,7 @@ async function parseMdx(rawMdx: string) {
rehypePlugins: [
preProcess,
rehypeCodeTitles,
+ handleCodeTitles,
rehypePrism,
rehypeSlug,
rehypeAutolinkHeadings,
diff --git a/package.json b/package.json
index 303530a..17250be 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "docubook",
- "version": "1.16.0",
+ "version": "1.16.1",
"private": true,
"scripts": {
"dev": "next dev",
@@ -30,13 +30,13 @@
"framer-motion": "^12.4.1",
"geist": "^1.3.1",
"gray-matter": "^4.0.3",
- "install": "^0.13.0",
"lucide-react": "^0.511.0",
"next": "^14.2.6",
"next-mdx-remote": "^5.0.0",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-icons": "^5.5.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-code-titles": "^1.2.0",
"rehype-prism-plus": "^2.0.0",
diff --git a/styles/globals.css b/styles/globals.css
index 4903c18..84669c5 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -90,6 +90,7 @@ pre>code {
display: grid;
max-width: inherit !important;
padding: 14px 0 !important;
+ border: 0 !important;
}
.code-line {
@@ -138,4 +139,4 @@ pre>code {
background-position: 0% 0%;
}
}
- }
\ No newline at end of file
+ }
diff --git a/styles/syntax.css b/styles/syntax.css
index 0459326..c6b15c7 100644
--- a/styles/syntax.css
+++ b/styles/syntax.css
@@ -93,3 +93,87 @@
border: none;
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));
+}
\ No newline at end of file