diff --git a/app/notion/_components/code.tsx b/app/notion/_components/code.tsx new file mode 100644 index 0000000..5f11c19 --- /dev/null +++ b/app/notion/_components/code.tsx @@ -0,0 +1,118 @@ +"use client" + +import React from "react"; +// import copyToClipboard from 'clipboard-copy' +// import { CodeBlock } from 'notion-types' +// import { getBlockTitle } from 'notion-utils' +import Prism, { highlightElement } from "prismjs"; +import { languages as Lan } from "prismjs"; + +// TODO: make it to dynamic +import "prismjs/components/prism-clike.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/themes/prism-tomorrow.css"; +import { cn } from '@/lib/utils'; + +// import { Text } from "../components/text"; +// import { useNotionContext } from "../context"; +import RichText from "../text"; +// import CopyIcon from '../icons/copy' +// import { cs } from "../utils"; + +interface CodeBlockProps { + block: any; + defaultLanguage?: string | undefined; + className?: string | undefined; +} + +// const Code = React.forwardRef< +// HTMLDivElement, +// React.HTMLAttributes +// >(({ className, children, ...props }, ref) => ( +// +// {children} +// +// )) +// Code.displayName = "CardHeader" + +// const Code = React.forwardRef((props: any, ref) => { +// const {className, children, ...prop} = props; +// return ( +// +// {children} +// +// ) +// }); +// Code.displayName = "CardHeader" + + +export function CodeRender({block, defaultLanguage, className} : CodeBlockProps) { + + + const codeRef = React.useRef(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; + } + + const lang = (code.language || defaultLanguage).toLowerCase(); + 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 + }) + return ( +
+
+        
+          {/*  */}
+          {codes}
+        
+      
+ + {caption && caption.length > 0 && ( +
+ +
+ )} +
+ ); + + // Solution 3: https://drupal-way.com/blog/nextjs-and-prismjs-integration + // const highlightedCode = Prism.highlight(codes, Lan[lang], lang); + // return
${highlightedCode}`}} + // /> +}; diff --git a/app/notion/render.tsx b/app/notion/render.tsx index f852584..8389cad 100644 --- a/app/notion/render.tsx +++ b/app/notion/render.tsx @@ -4,13 +4,16 @@ import Link from 'next/link'; import RichText from './text'; import styles from './post.module.css'; import { bulletListStyle, numberListStyle } from './tools'; +import { cn } from '@/lib/utils'; +import { CodeRender } from './_components/code'; +import React from 'react'; export function renderBlock(block: any, level: number = 1) { const { type, id } = block; const value = block[type]; - // console.log(type, id, value); + // console.log(type, id); switch (type) { // https://developers.notion.com/reference/block#paragraph @@ -20,7 +23,7 @@ export function renderBlock(block: any, level: number = 1) { // - mention: no rich_text exist case 'paragraph': return ( -

+

); @@ -43,29 +46,25 @@ export function renderBlock(block: any, level: number = 1) { ); - // before list rendering, array should be convert into children array. See getBlocks function. case 'bulleted_list': { - const styels = bulletListStyle(level) return ( -
    +
      {value.children.map((child: any) => renderBlock(child, level + 1))}
    ); } case 'numbered_list': { - const styels = numberListStyle(level) return ( -
      +
        {value.children.map((child: any) => renderBlock(child, level + 1))}
      ); } case 'bulleted_list_item': case 'numbered_list_item': - // console.log('item: ', type, value, value.children) return ( -
    1. +
    2. {!!block.children && renderNestedList(block, level + 1)}
    3. @@ -73,25 +72,28 @@ export function renderBlock(block: any, level: number = 1) { case 'to_do': return ( -
      +
      ); + case 'toggle': return ( -
      +
      - {block.children?.map((child: any) => ( - {renderBlock(child, level + 1)} - ))} +
      + {block.children?.map((child: any) => ( + {renderBlock(child, level + 1)} + ))} +
      ); + case 'child_page': return (
      @@ -109,18 +111,28 @@ export function renderBlock(block: any, level: number = 1) { ); } + case 'divider': - return
      ; + return
      ; + case 'quote': - return
      {value.rich_text[0].plain_text}
      ; + return
      {value.rich_text[0].plain_text}
      ; + case 'code': - return ( -
      -          
      -            {value.rich_text[0].plain_text}
      -          
      -        
      - ); + // return ( + //
      +      //     
      +      //       {value.rich_text[0].plain_text}
      +      //     
      +      //   
      + // ); + return ( + Loading...
      }> + + + ) + // + // ); case 'file': { const srcFile = value.type === 'external' ? value.external.url : value.file.url; const splitSourceArray = srcFile.split('/'); diff --git a/app/notion/text.tsx b/app/notion/text.tsx index 72687bc..349f10a 100644 --- a/app/notion/text.tsx +++ b/app/notion/text.tsx @@ -1,4 +1,6 @@ +import { cn } from '@/lib/utils'; import styles from './post.module.css'; +import React from 'react'; // convert notion color to tailwind css class name for each annotation style: // - "blue" @@ -27,7 +29,7 @@ const colorMap = new Map([ ["brown", "text-[#8d6e63]"], ["brown_background", "bg-[#d7ccc8]"], ["default", ""], - ["gray", "text-gray-400"], + ["gray", "text-gray-500"], ["gray_background", "bg-gray-200"], ["green", "text-green-800"], ["green_background", "bg-green-100"], @@ -43,10 +45,10 @@ const colorMap = new Map([ ["yellow_background", "bg-yellow-100"], ]); -const linkTextStyle = 'text-gray-400 underline' +const linkTextStyle = 'text-gray-500 underline' // https://developers.notion.com/reference/rich-text#the-annotation-object -function AnnotationStyle(annotations: any){ +function AnnotationStyle(annotations: any, extended ?: string){ const {bold, code, color, italic, strikethrough, underline} = annotations; const names = [ bold ? 'font-bold' : '', @@ -55,17 +57,18 @@ function AnnotationStyle(annotations: any){ strikethrough ? 'line-through' : '', underline ? 'underline' : '', color ? colorMap.get(color) : '', + extended ? extended : '', ]; return names.filter((el) => el != '' && el != undefined).join(' '); } -function Text(rt : any) { +function Text(rt : any, extended?: string) { const { annotations, text, } = rt; - const styels = AnnotationStyle(annotations); + const styels = AnnotationStyle(annotations, extended); return ( {href ? {plain_text} : plain_text} ) } -function Equation(rt : any) { +function Equation(rt : any, extended?: string) { const { annotations, equation, } = rt; - const styels = AnnotationStyle(annotations); + const styels = AnnotationStyle(annotations, extended); return (   } - return title.map((value: any) => { - const { type } = value; - if (type == 'text') { - return Text(value); - } else if (type == 'mention') { - return Mention(value) - } else if (type == 'equation') { - return Equation(value) - } - return null - }); + return ( + + {title.map((value: any) => { + const { type } = value; + if (type == 'text') { + return Text(value, extended); + } else if (type == 'mention') { + return Mention(value, extended) + } else if (type == 'equation') { + return Equation(value, extended) + } + return null + })} + + ) + // return title.map((value: any) => { + // const { type } = value; + // if (type == 'text') { + // return Text(value, extended); + // } else if (type == 'mention') { + // return Mention(value, extended) + // } else if (type == 'equation') { + // return Equation(value, extended) + // } + + // return null + // }); } diff --git a/package-lock.json b/package-lock.json index 3efe6e6..7ea0810 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "lucide-react": "^0.312.0", "next": "14.0.4", "next-themes": "^0.2.1", + "prismjs": "^1.29.0", "react": "^18", "react-dom": "^18", "react-wrap-balancer": "^1.1.0", @@ -34,6 +35,7 @@ "devDependencies": { "@types/ms": "^0.7.34", "@types/node": "^20", + "@types/prismjs": "^1.26.3", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.0.1", @@ -1387,6 +1389,12 @@ "form-data": "^4.0.0" } }, + "node_modules/@types/prismjs": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", + "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==", + "dev": true + }, "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", @@ -4589,6 +4597,14 @@ "node": ">= 0.8.0" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/package.json b/package.json index 76a5697..dd0d0be 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "lucide-react": "^0.312.0", "next": "14.0.4", "next-themes": "^0.2.1", + "prismjs": "^1.29.0", "react": "^18", "react-dom": "^18", "react-wrap-balancer": "^1.1.0", @@ -36,6 +37,7 @@ "devDependencies": { "@types/ms": "^0.7.34", "@types/node": "^20", + "@types/prismjs": "^1.26.3", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.0.1",