Skip to content

Commit

Permalink
feat: block layout for code
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-guoba committed Jan 31, 2024
1 parent e8b3665 commit b373b5f
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 48 deletions.
118 changes: 118 additions & 0 deletions app/notion/_components/code.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>
// >(({ className, children, ...props }, ref) => (
// <code
// ref={ref}
// className={cn("space-y-1.5", className)}
// {...props}
// >
// {children}
// </code>
// ))
// Code.displayName = "CardHeader"

// const Code = React.forwardRef((props: any, ref) => {
// const {className, children, ...prop} = props;
// return (
// <code
// ref={ref}
// className={cn("space-y-1.5", className)}
// {...prop}
// >
// {children}
// </code>
// )
// });
// Code.displayName = "CardHeader"


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;
}

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 (
<div id={id} className={cn(className, "text-sm")}>
<pre className="rounded py-8">
<code className={`language-${lang}`} ref={codeRef}>
{/* <RichText title={code.rich_text} /> */}
{codes}
</code>
</pre>

{caption && caption.length > 0 && (
<figcaption className="font-normal">
<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>`}}
// />
};
64 changes: 38 additions & 26 deletions app/notion/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,7 +23,7 @@ export function renderBlock(block: any, level: number = 1) {
// - mention: no rich_text exist
case 'paragraph':
return (
<p id={id} className='py-1'>
<p id={id} className='mt-1.5'>
<RichText title={value.rich_text} />
</p>
);
Expand All @@ -43,55 +46,54 @@ export function renderBlock(block: any, level: number = 1) {
</h3>
);


// before list rendering, array should be convert into children array. See getBlocks function.
case 'bulleted_list': {
const styels = bulletListStyle(level)
return (
<ul className={`${styels} pt-1 pl-5`}>
<ul key={id} className={cn(bulletListStyle(level), level == 1 ? 'mt-1.5' : '', "pl-5")}>
{value.children.map((child: any) => renderBlock(child, level + 1))}
</ul>
);
}
case 'numbered_list': {
const styels = numberListStyle(level)
return (
<ol className={`${styels} pt-1 pl-5`}>
<ol key={id} className={cn(numberListStyle(level), level == 1 ? 'mt-1.5' : '', 'pl-5')}>
{value.children.map((child: any) => renderBlock(child, level + 1))}
</ol>
);
}
case 'bulleted_list_item':
case 'numbered_list_item':
// console.log('item: ', type, value, value.children)
return (
<li key={block.id} className='pt-1'>
<li key={id} className='mt-1.5'>
<RichText title={value.rich_text} />
{!!block.children && renderNestedList(block, level + 1)}
</li>
);

case 'to_do':
return (
<div>
<div className='mt-1.5'>
<label htmlFor={id}>
<input type="checkbox" id={id} defaultChecked={value.checked} />
{' '}
<RichText title={value.rich_text} />
<input className='w-4 h-4 border-black border-4 rounded-none mr-2' type="checkbox" id={id} defaultChecked={value.checked} />
{value.checked ? <RichText title={value.rich_text} extended='text-gray-500 line-through'/> : <RichText title={value.rich_text}/>}
</label>
</div>
);

case 'toggle':
return (
<details>
<details className='mt-1.5'>
<summary>
<RichText title={value.rich_text} />
</summary>
{block.children?.map((child: any) => (
<Fragment key={child.id}>{renderBlock(child, level + 1)}</Fragment>
))}
<div className='ml-4'>
{block.children?.map((child: any) => (
<Fragment key={child.id}>{renderBlock(child, level + 1)}</Fragment>
))}
</div>
</details>
);

case 'child_page':
return (
<div className={styles.childPage}>
Expand All @@ -109,18 +111,28 @@ export function renderBlock(block: any, level: number = 1) {
</figure>
);
}

case 'divider':
return <hr key={id} />;
return <hr className='mt-1.5 border-gray-200' key={id} />;

case 'quote':
return <blockquote key={id}>{value.rich_text[0].plain_text}</blockquote>;
return <blockquote className='mt-1.5 border-black border-l-4 pl-4 py-1.5 whitespace-pre-wrap' key={id}>{value.rich_text[0].plain_text}</blockquote>;

case 'code':
return (
<pre className={styles.pre}>
<code className={styles.code_block} key={id}>
{value.rich_text[0].plain_text}
</code>
</pre>
);
// return (
// <pre className={styles.pre}>
// <code className={styles.code_block} key={id}>
// {value.rich_text[0].plain_text}
// </code>
// </pre>
// );
return (
<React.Suspense fallback={<div>Loading...</div>}>
<CodeRender block={block} className='mt-1.5'></CodeRender>
</React.Suspense>
)
// <CodeRender block={block}></CodeRender>
// );
case 'file': {
const srcFile = value.type === 'external' ? value.external.url : value.file.url;
const splitSourceArray = srcFile.split('/');
Expand Down
63 changes: 41 additions & 22 deletions app/notion/text.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -27,7 +29,7 @@ const colorMap = new Map<string, string>([
["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"],
Expand All @@ -43,10 +45,10 @@ const colorMap = new Map<string, string>([
["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' : '',
Expand All @@ -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 (
<span
className={styels}
Expand All @@ -77,29 +80,29 @@ function Text(rt : any) {
}

// https://developers.notion.com/reference/rich-text#mention
function Mention(rt : any) {
function Mention(rt : any, extended?: string) {
const {
annotations,
href,
plain_text
} = rt;
const styels = AnnotationStyle(annotations);
const styels = AnnotationStyle(annotations, extended);

return (
<span
className={`${styels} font-medium`}
className={cn(styels, 'font-medium')}
>
{href ? <a className='underline' href={href}>{plain_text}</a> : plain_text}
</span>
)
}

function Equation(rt : any) {
function Equation(rt : any, extended?: string) {
const {
annotations,
equation,
} = rt;
const styels = AnnotationStyle(annotations);
const styels = AnnotationStyle(annotations, extended);
return (
<span
className={styels}
Expand All @@ -114,21 +117,37 @@ function Equation(rt : any) {
// This includes styling decisions, such as the use of italics, font size, and font color, as well as formatting,
// such as the use of hyperlinks or code blocks.
// See: https://developers.notion.com/reference/rich-text
export default function RichText({ title}: any) {
export default function RichText({ title, extended }: any) {
// empmty lines should be rendered with &emsp
if (!title || title?.length == 0) {
return <span>&nbsp;</span>
}
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 (
<React.Fragment>
{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
})}
</React.Fragment>
)
// 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
// });
}

0 comments on commit b373b5f

Please sign in to comment.