tags and other changes
This commit is contained in:
Generated
+1006
File diff suppressed because it is too large
Load Diff
@@ -25,8 +25,12 @@
|
|||||||
"react-loading-skeleton": "^3.5.0",
|
"react-loading-skeleton": "^3.5.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-syntax-highlighter": "^15.6.1",
|
"react-syntax-highlighter": "^15.6.1",
|
||||||
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
|
"remark-breaks": "^4.0.0",
|
||||||
|
"remark-footnotes": "^4.0.1",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
|
"remark-math": "^6.0.0",
|
||||||
"typewriter-effect": "^2.21.0"
|
"typewriter-effect": "^2.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+29
-10
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: 'Roboto Mono';
|
font-family: 'Roboto Mono';
|
||||||
color: white;
|
color: white;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -52,12 +52,6 @@ h1 {
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown {
|
|
||||||
font-family: 'Roboto Mono';
|
|
||||||
margin-top: 30px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #f5304c;
|
color: #f5304c;
|
||||||
font-family: 'Roboto Mono';
|
font-family: 'Roboto Mono';
|
||||||
@@ -90,15 +84,40 @@ a {
|
|||||||
|
|
||||||
.blog_container {
|
.blog_container {
|
||||||
margin: 60px auto;
|
margin: 60px auto;
|
||||||
width: 600px;
|
width: 730px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 730px) {
|
||||||
.blog_container {
|
.blog_container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
MARKDOWN
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
.markdown {
|
||||||
|
font-family: 'Roboto Mono';
|
||||||
|
margin-top: 30px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol li::marker {
|
||||||
|
color: #f5304c;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li::marker {
|
||||||
|
color: #f5304c;
|
||||||
}
|
}
|
||||||
+24
-18
@@ -4,7 +4,7 @@ import { NotionToMarkdown } from "notion-to-md";
|
|||||||
interface Blog {
|
interface Blog {
|
||||||
id: string,
|
id: string,
|
||||||
title: string,
|
title: string,
|
||||||
tags: string[],
|
tags: { name: string, color: string }[],
|
||||||
date: string,
|
date: string,
|
||||||
description: string,
|
description: string,
|
||||||
};
|
};
|
||||||
@@ -26,12 +26,12 @@ export async function queryDatabase(notion: Client, databaseId: string): Promise
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function getBlogs(notion: Client, databaseId: string): Promise<[number, Blog[]]> {
|
export default async function getBlogs(notion: Client, databaseId: string): Promise<[number, Blog[] | {msg: string}]> {
|
||||||
const [pages, err] = await queryDatabase(notion, databaseId);
|
const [pages, err] = await queryDatabase(notion, databaseId);
|
||||||
if (err === null) {
|
if (err === null) {
|
||||||
return [200, pages.map(convertBlog)];
|
return [200, pages.map(convertBlog)];
|
||||||
}
|
}
|
||||||
return [500, err];
|
return [500, {msg: String(err)}];
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertBlog(page: any): Blog {
|
function convertBlog(page: any): Blog {
|
||||||
@@ -40,7 +40,7 @@ function convertBlog(page: any): Blog {
|
|||||||
title: page.properties.Title.title[0].plain_text,
|
title: page.properties.Title.title[0].plain_text,
|
||||||
description: [page.properties.Description.rich_text.map((d: { plain_text: string; }) => (d.plain_text))].join(" "),
|
description: [page.properties.Description.rich_text.map((d: { plain_text: string; }) => (d.plain_text))].join(" "),
|
||||||
date: page.properties.Date.created_time,
|
date: page.properties.Date.created_time,
|
||||||
tags: page.properties.Tags.multi_select.map((tag: any) => (tag.name)),
|
tags: page.properties.Tags.multi_select.map((tag: any) => ({name: tag.name, color: tag.color})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,20 +48,26 @@ interface FullBlog extends Blog {
|
|||||||
markdown: string,
|
markdown: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getBlog(notion: Client, n2m: NotionToMarkdown, pageId: string): Promise<[number, FullBlog]> {
|
export async function getBlog(notion: Client, n2m: NotionToMarkdown, pageId: string): Promise<[number, FullBlog | {msg: string}]> {
|
||||||
try {
|
try {
|
||||||
const page = await notion.pages.retrieve({ page_id: pageId })
|
const page: any = await notion.pages.retrieve({ page_id: pageId })
|
||||||
const blog = convertBlog(page);
|
if (page.properties.Status.status.name !== 'Posted')
|
||||||
const mdBlocks = await n2m.pageToMarkdown(page.id);
|
return [400, {msg: 'Blog not found'}]
|
||||||
return [200, {
|
try {
|
||||||
id: blog.id,
|
const blog = convertBlog(page);
|
||||||
title: blog.title,
|
const mdBlocks = await n2m.pageToMarkdown(page.id);
|
||||||
description: blog.description,
|
return [200, {
|
||||||
tags: blog.tags,
|
id: blog.id,
|
||||||
date: blog.date,
|
title: blog.title,
|
||||||
markdown: n2m.toMarkdownString(mdBlocks).parent,
|
description: blog.description,
|
||||||
}];
|
tags: blog.tags,
|
||||||
} catch (error: any) {
|
date: blog.date,
|
||||||
return [400, error];
|
markdown: n2m.toMarkdownString(mdBlocks).parent,
|
||||||
|
}];
|
||||||
|
} catch (err: any) {
|
||||||
|
return [500, {msg: String(err)}];
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return [400, {msg: 'Blog not found'}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ export default function LoadingBlog(props: any) {
|
|||||||
{Array.from({ length: numComponents }, (_, index) => (
|
{Array.from({ length: numComponents }, (_, index) => (
|
||||||
<ContentLoader
|
<ContentLoader
|
||||||
speed={2}
|
speed={2}
|
||||||
viewBox="0 0 340 50"
|
viewBox="0 0 290 50"
|
||||||
backgroundColor="#3b3b3b"
|
backgroundColor="#3b3b3b"
|
||||||
foregroundColor="#f5304c"
|
foregroundColor="#f5304c"
|
||||||
key={index}
|
key={index}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Markdown from 'react-markdown';
|
import Markdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
|
import remarkBreaks from 'remark-breaks';
|
||||||
|
import remarkMath from 'remark-math';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
|
import rehypeKatex from 'rehype-katex';
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
import { gruvboxDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
import { gruvboxDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||||
|
import 'katex/dist/katex.min.css';
|
||||||
|
|
||||||
type MarkdownRendererProps = {
|
type MarkdownRendererProps = {
|
||||||
children: string;
|
children: string;
|
||||||
@@ -12,12 +16,11 @@ type MarkdownRendererProps = {
|
|||||||
export default function MarkdownRenderer({ children: markdown }: MarkdownRendererProps) {
|
export default function MarkdownRenderer({ children: markdown }: MarkdownRendererProps) {
|
||||||
return (
|
return (
|
||||||
<Markdown
|
<Markdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm, remarkMath, remarkBreaks]}
|
||||||
rehypePlugins={[rehypeRaw]}
|
rehypePlugins={[rehypeRaw, rehypeKatex]}
|
||||||
components={{
|
components={{
|
||||||
code({ node, inline, className, children, ...props }: any) {
|
code({ node, inline, className, children, ...props }: any) {
|
||||||
const match = /language-(\w+)/.exec(className || '');
|
const match = /language-(\w+)/.exec(className || '');
|
||||||
|
|
||||||
return !inline && match ? (
|
return !inline && match ? (
|
||||||
<SyntaxHighlighter style={gruvboxDark} PreTag="div" language={match[1]} {...props}>
|
<SyntaxHighlighter style={gruvboxDark} PreTag="div" language={match[1]} {...props}>
|
||||||
{String(children).replace(/\n$/, '')}
|
{String(children).replace(/\n$/, '')}
|
||||||
@@ -28,9 +31,30 @@ export default function MarkdownRenderer({ children: markdown }: MarkdownRendere
|
|||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
ul({ children, ...props }) {
|
||||||
|
return (
|
||||||
|
<ul style={{ listStyleType: 'disc', listStylePosition: 'inside' }} {...props}>
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ol({ children, ...props }) {
|
||||||
|
return (
|
||||||
|
<ol style={{ listStyleType: 'decimal', listStylePosition: 'inside' }} {...props}>
|
||||||
|
{children}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
li({ children, ...props }) {
|
||||||
|
return (
|
||||||
|
<li style={{ marginBottom: '4px' }} {...props}>
|
||||||
|
{children}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{markdown}
|
{markdown}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import '../../Blog.css';
|
import '../../Blog.css';
|
||||||
import MarkdownRenderer from './MarkdownRenderer';
|
import MarkdownRenderer from './MarkdownRenderer';
|
||||||
import LoadingBlog, { LoadingBlogRead } from '../Loading';
|
import { LoadingBlogRead } from '../Loading';
|
||||||
|
|
||||||
export default function Blog({ params }: { params: Promise<{ id: string }> }) {
|
export default function Blog({ params }: { params: Promise<{ id: string }> }) {
|
||||||
const [blog, setBlog] = useState({ title: '', date: '', markdown: '' });
|
const [blog, setBlog] = useState({ title: '', date: '', markdown: '' });
|
||||||
@@ -44,7 +44,12 @@ export default function Blog({ params }: { params: Promise<{ id: string }> }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div className="error">Error loading blog: {error}</div>; // Show error if there is one
|
return (
|
||||||
|
<main>
|
||||||
|
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||||
|
<div>Error: {error}</div>;
|
||||||
|
</main>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -53,7 +58,7 @@ export default function Blog({ params }: { params: Promise<{ id: string }> }) {
|
|||||||
<div className='blog_container'>
|
<div className='blog_container'>
|
||||||
<h2 className='blog_title'>{blog.title}</h2>
|
<h2 className='blog_title'>{blog.title}</h2>
|
||||||
<p className='date'>{date}</p>
|
<p className='date'>{date}</p>
|
||||||
<div className='markdown'><MarkdownRenderer>{blog.markdown}</MarkdownRenderer></div>
|
<div className='markdown'><MarkdownRenderer>{blog.markdown.replaceAll('\n-\n', '\n<br/>\n')}</MarkdownRenderer></div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
+85
-4
@@ -4,9 +4,58 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import '../Blog.css';
|
import '../Blog.css';
|
||||||
import LoadingBlog from './Loading';
|
import LoadingBlog from './Loading';
|
||||||
|
|
||||||
|
type Blog = { title: string, description: string, id: string, date: string, tags: { name: string, color: string }[] };
|
||||||
|
|
||||||
|
const notionColors: { [index: string]: {
|
||||||
|
background: string,
|
||||||
|
color: string,
|
||||||
|
} } = {
|
||||||
|
default: {
|
||||||
|
background: "#191919",
|
||||||
|
color: "#D4D4D4"
|
||||||
|
},
|
||||||
|
gray: {
|
||||||
|
background: "#252525",
|
||||||
|
color: "#9B9B9B"
|
||||||
|
},
|
||||||
|
brown: {
|
||||||
|
background: "#2E2724",
|
||||||
|
color: "#A27763"
|
||||||
|
},
|
||||||
|
orange: {
|
||||||
|
background: "#36291F",
|
||||||
|
color: "#CB7B37"
|
||||||
|
},
|
||||||
|
yellow: {
|
||||||
|
background: "#372E20",
|
||||||
|
color: "#C19138"
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
background: "#242B26",
|
||||||
|
color: "#4F9768"
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
background: "#1F282D",
|
||||||
|
color: "#447ACB"
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
background: "#2A2430",
|
||||||
|
color: "#865DBB"
|
||||||
|
},
|
||||||
|
pink: {
|
||||||
|
background: "#2E2328",
|
||||||
|
color: "#BA4A78"
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
background: "#332523",
|
||||||
|
color: "#BE524B"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default function Blog() {
|
export default function Blog() {
|
||||||
const [data, setData] = useState([{ title: '', description: '', id: '', date: '' }]);
|
const [data, setData] = useState<Blog[]>([]);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@@ -16,6 +65,7 @@ export default function Blog() {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
setData(result);
|
setData(result);
|
||||||
|
setLoading(false);
|
||||||
} else {
|
} else {
|
||||||
const errorData = await response.text();
|
const errorData = await response.text();
|
||||||
setError(errorData);
|
setError(errorData);
|
||||||
@@ -30,12 +80,19 @@ export default function Blog() {
|
|||||||
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>Error: {JSON.stringify(error)}</div>; // Display any errors
|
if (error) {
|
||||||
} if (data.length === 1 && data[0].id === '') {
|
return (
|
||||||
|
<main>
|
||||||
|
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||||
|
<div>Error: {error}</div>;
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} if (loading) {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||||
<LoadingBlog/>
|
<LoadingBlog/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -53,6 +110,30 @@ export default function Blog() {
|
|||||||
<h2 className='title'>{blog.title}</h2>
|
<h2 className='title'>{blog.title}</h2>
|
||||||
<p className='date'>{new Date(blog.date).toLocaleString('default', { month: 'long', day: 'numeric', year: 'numeric' })}</p>
|
<p className='date'>{new Date(blog.date).toLocaleString('default', { month: 'long', day: 'numeric', year: 'numeric' })}</p>
|
||||||
<p className='description'>{blog.description}</p>
|
<p className='description'>{blog.description}</p>
|
||||||
|
{blog.tags.map((tag, i) => (
|
||||||
|
notionColors[tag.color] !== undefined ?
|
||||||
|
<span key={i} style={{
|
||||||
|
background: notionColors[tag.color].background,
|
||||||
|
color: notionColors[tag.color].color,
|
||||||
|
fontSize: '12px',
|
||||||
|
border: '5px solid ' + notionColors[tag.color].background,
|
||||||
|
fontFamily: 'sans-serif',
|
||||||
|
borderRadius: '5px',
|
||||||
|
display: 'inline-block',
|
||||||
|
marginTop: '8px'
|
||||||
|
}}>{tag.name}</span>
|
||||||
|
:
|
||||||
|
<span key={i} style={{
|
||||||
|
background: tag.color,
|
||||||
|
color: tag.color,
|
||||||
|
fontSize: '12px',
|
||||||
|
border: '5px solid ' + tag.color,
|
||||||
|
fontFamily: 'sans-serif',
|
||||||
|
borderRadius: '5px',
|
||||||
|
display: 'inline-block',
|
||||||
|
marginTop: '8px'
|
||||||
|
}}>{tag.name}</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user