Skip to content

Commit

Permalink
feat: replace prismjs with Shiki
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-guoba committed Feb 27, 2024
1 parent d9f3aeb commit 4de2568
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 73 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Blog build on [Next.js](https://nextjs.org/) and [Notion Public API](https://www

## Features

1. Built using Next.js, TS, Tailwind CSS and other plugins(Prism、React-pdf and others).
1. Built using Next.js, TS, Tailwind CSS and other plugins(Shiki、React-pdf and others).
2. Use [Notion Public API](https://developers.notion.com/)
3. Full support for dark mode
4. Support [Static Site Generation](https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation)
Expand Down Expand Up @@ -52,7 +52,7 @@ Most common block types are supported. But some blocks information not supported
| Callout | ✅ Yes | |
| Child Databases | ❌ Missing | Not planned. |
| Child page | ✅ Yes | |
| Code | ✅ Yes | Use [prismjs](https://prismjs.com/) |
| Code | ✅ Yes | Use [shiki](https://shiki.style/) |
| Column list and column | ✅ Yes | |
| Divider | ❌ Missing | |
| Embed | ✅ Yes | |
Expand Down
160 changes: 110 additions & 50 deletions app/notion/_components/code.tsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,150 @@
"use client";
// "use client";

import React from "react";
import { highlightElement } from "prismjs";
// import { highlightElement } from "prismjs";
// import { languages as Lan } from "prismjs";

// TODO: make it to dynamic
import "prismjs/components/prism-c.min.js";
import "prismjs/components/prism-cpp.min.js";
import "prismjs/components/prism-css-extras.min.js";
import "prismjs/components/prism-css.min.js";
import "prismjs/components/prism-javascript.min.js";
import "prismjs/components/prism-js-extras.min.js";
import "prismjs/components/prism-json.min.js";
import "prismjs/components/prism-jsx.min.js";
import "prismjs/components/prism-tsx.min.js";
import "prismjs/components/prism-typescript.min.js";
import "prismjs/components/prism-go.js";
import "prismjs/components/prism-ini.js";
import "prismjs/components/prism-shell-session.js";
import "prismjs/components/prism-bash.min.js";
import "prismjs/components/prism-java.min.js";
import "prismjs/components/prism-python.min.js";

import "prismjs/themes/prism-tomorrow.css";
// import "prismjs/components/prism-c.min.js";
// import "prismjs/components/prism-cpp.min.js";
// import "prismjs/components/prism-css-extras.min.js";
// import "prismjs/components/prism-css.min.js";
// import "prismjs/components/prism-javascript.min.js";
// import "prismjs/components/prism-js-extras.min.js";
// import "prismjs/components/prism-json.min.js";
// import "prismjs/components/prism-jsx.min.js";
// import "prismjs/components/prism-tsx.min.js";
// import "prismjs/components/prism-typescript.min.js";
// import "prismjs/components/prism-go.js";
// import "prismjs/components/prism-ini.js";
// import "prismjs/components/prism-shell-session.js";
// import "prismjs/components/prism-bash.min.js";
// import "prismjs/components/prism-java.min.js";
// import "prismjs/components/prism-python.min.js";

// import "prismjs/themes/prism-tomorrow.css";
import { cn } from "@/lib/utils";

import RichText from "../text";
import { bundledLanguages, getHighlighter } from "shiki";
// import type { Highlighter, Lang, Theme } from 'shiki'

// import type { BundledLanguage, BundledTheme } from "shiki"; // Import the types from shiki

interface CodeBlockProps {
block: any;
defaultLanguage?: string | undefined;
className?: string | undefined;
}

export function CodeRender({ block, defaultLanguage, className }: CodeBlockProps) {
const codeRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (codeRef.current) {
try {
highlightElement(codeRef.current);
} catch (err) {
console.warn("prismjs highlight error", err);
}
}
}, [codeRef]);
// export function CodeRender({ block, defaultLanguage, className }: CodeBlockProps) {
// const codeRef = React.useRef<HTMLDivElement>(null);
// React.useEffect(() => {
// if (codeRef.current) {
// try {
// highlightElement(codeRef.current);
// } catch (err) {
// console.warn("prismjs highlight error", err);
// }
// }
// }, [codeRef]);

// const { code, id, type } = block;
// if (type != "code" || !code) {
// return null;
// }

// let lang = (code.language || defaultLanguage).toLowerCase();
// if (lang == "c++") {
// lang = "cpp";
// }
// const caption = code.caption;

// // Text cann't be nested in code element. So there are two ways to render code
// // Solution 1: Replace code with div tag.
// // Solution 2: Render all raw plain-text
// let codes = "";
// code.rich_text?.map((item: any) => {
// codes += item?.plain_text;
// });

// // TODO: 1. add copy to clipboard feature
// // TODO: 2. support mermaid diagram. see https://stackblitz.com/edit/react-ts-mermaid?file=Mermaid.tsx
// return (
// <div key={id} className={cn(className, "text-sm")}>
// <pre className="rounded py-8">
// <code className={`language-${lang}`} ref={codeRef}>
// {codes}
// </code>
// </pre>

// {caption && caption.length > 0 && (
// <figcaption className="px-1.5 text-sm font-normal text-slate-600">
// <RichText title={caption} />
// </figcaption>
// )}
// </div>
// );

// Solution 3: https://drupal-way.com/blog/nextjs-and-prismjs-integration
// const highlightedCode = Prism.highlight(codes, Lan[lang], lang);
// return <div
// dangerouslySetInnerHTML={{__html: `<pre class="language-${lang}" tabIndex="0"><code class="language-${lang}">${highlightedCode}</code></pre>`}}
// />
// }

// let highlighter: Highlighter;

// shiki render
export async function ShikiCodeRender({ block, defaultLanguage, className }: CodeBlockProps) {
const { code, id, type } = block;
if (type != "code" || !code) {
return null;
}

let lang = (code.language || defaultLanguage).toLowerCase();

// https://shiki.style/languages
if (lang == "c++") {
lang = "cpp";
} else if (lang == "assembly") {
lang = "asm";
}
if (!(lang in bundledLanguages)) {
throw new Error('Unsupported language "' + lang + '"');
}
const caption = code.caption;

// Text cann't be nested in code element. So there are two ways to render code
// Solution 1: Replace code with div tag.
// Solution 2: Render all raw plain-text
const theme = 'github-dark';
let codes = "";
code.rich_text?.map((item: any) => {
codes += item?.plain_text;
});

// TODO: 1. add copy to clipboard feature
// TODO: 2. support mermaid diagram. see https://stackblitz.com/edit/react-ts-mermaid?file=Mermaid.tsx
return (
<div key={id} className={cn(className, "text-sm")}>
<pre className="rounded py-8">
<code className={`language-${lang}`} ref={codeRef}>
{codes}
</code>
</pre>
const highlighter = await getHighlighter({
langs: [lang],
themes: [theme],
});

const html = await highlighter.codeToHtml(codes, {
lang: lang,
theme: theme,
});
// console.log(html);

const caption = code.caption;

return (
<div key={id} className={cn(className, "text-sm flex-col w-full max-w-full overflow-hidden")}>
<div
dangerouslySetInnerHTML={{
__html: `<pre class="language-${lang}" style="background: #24292e; padding: 1em; margin: 0.5em 0px; overflow: auto;" tabIndex="0"><code class="language-${lang}">${html}</code></pre>`,
}}
/>
{caption && caption.length > 0 && (
<figcaption className="px-1.5 text-sm font-normal text-slate-600">
<RichText title={caption} />
</figcaption>
)}
</div>
);

// Solution 3: https://drupal-way.com/blog/nextjs-and-prismjs-integration
// const highlightedCode = Prism.highlight(codes, Lan[lang], lang);
// return <div
// dangerouslySetInnerHTML={{__html: `<pre class="language-${lang}" tabIndex="0"><code class="language-${lang}">${highlightedCode}</code></pre>`}}
// />
}
2 changes: 1 addition & 1 deletion app/notion/_components/pdf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export function PDFRender({ block, className }: PDFBlockProps) {
renderTextLayer={false}
onLoadSuccess={onPageLoadSuccess}
onRenderError={() => setLoading(false)}
// width={Math.max(pageWidth * 0.75, 320)}
width={Math.max(pageWidth * 0.75, 320)}
/>
</Document>
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/notion/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import RichText from "./text";
import { bulletListStyle, numberListStyle } from "./tools";
import { cn } from "@/lib/utils";
import React from "react";
import { CodeRender } from "./_components/code";
import { ShikiCodeRender } from "./_components/code";
import { CalloutRender } from "./_components/callout";
import { ImageRender } from "./_components/image";
import { BookmarkPreviewRender } from "./_components/bookmark";
Expand Down Expand Up @@ -131,7 +131,7 @@ export function renderBlock(block: any, level: number = 1) {
case "code":
return (
<React.Suspense fallback={<div>Loading...</div>}>
<CodeRender block={block} className="mt-1.5"></CodeRender>
<ShikiCodeRender block={block} className="mt-1.5"></ShikiCodeRender>
</React.Suspense>
);

Expand Down
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"lucide-react": "^0.312.0",
"next": "^14.1.0",
"next-themes": "^0.2.1",
"prismjs": "^1.29.0",
"react": "^18",
"react-dom": "^18",
"react-pdf": "^7.7.0",
Expand All @@ -45,7 +44,6 @@
"@next/eslint-plugin-next": "^14.1.0",
"@types/ms": "^0.7.34",
"@types/node": "^20",
"@types/prismjs": "^1.26.3",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
Expand All @@ -54,6 +52,7 @@
"postcss": "^8",
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.11",
"shiki": "^1.1.7",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
Expand Down

0 comments on commit 4de2568

Please sign in to comment.