bug fixes and tags
This commit is contained in:
+12
-15
@@ -9,7 +9,10 @@ interface Blog {
|
||||
description: string,
|
||||
};
|
||||
|
||||
export async function queryDatabase(notion: Client, databaseId: string): Promise<[Array<any>, any]> {
|
||||
type QueryResponse = { cursor: string | null, hasMore: boolean, items: Blog[] };
|
||||
type Error = { msg: string };
|
||||
|
||||
export async function getBlogs(notion: Client, databaseId: string, cursor: string | undefined, pageSize: number): Promise<[number, QueryResponse | Error]> {
|
||||
try {
|
||||
const response = await notion.databases.query({
|
||||
database_id: databaseId,
|
||||
@@ -18,22 +21,16 @@ export async function queryDatabase(notion: Client, databaseId: string): Promise
|
||||
status: {
|
||||
equals: "Posted"
|
||||
}
|
||||
}
|
||||
},
|
||||
start_cursor: cursor,
|
||||
page_size: pageSize,
|
||||
});
|
||||
return [response.results, null];
|
||||
} catch (error: any) {
|
||||
return [[], error];
|
||||
return [200, {cursor: response.next_cursor, hasMore: response.has_more, items: response.results.map(convertBlog)}];
|
||||
} catch (err: any) {
|
||||
return [400, {msg: String(err)}];
|
||||
}
|
||||
}
|
||||
|
||||
export default async function getBlogs(notion: Client, databaseId: string): Promise<[number, Blog[] | {msg: string}]> {
|
||||
const [pages, err] = await queryDatabase(notion, databaseId);
|
||||
if (err === null) {
|
||||
return [200, pages.map(convertBlog)];
|
||||
}
|
||||
return [500, {msg: String(err)}];
|
||||
}
|
||||
|
||||
function convertBlog(page: any): Blog {
|
||||
return {
|
||||
id: page.id,
|
||||
@@ -48,7 +45,7 @@ interface FullBlog extends Blog {
|
||||
markdown: string,
|
||||
};
|
||||
|
||||
export async function getBlog(notion: Client, n2m: NotionToMarkdown, pageId: string): Promise<[number, FullBlog | {msg: string}]> {
|
||||
export async function getBlog(notion: Client, n2m: NotionToMarkdown, pageId: string): Promise<[number, FullBlog | Error]> {
|
||||
try {
|
||||
const page: any = await notion.pages.retrieve({ page_id: pageId })
|
||||
if (page.properties.Status.status.name !== 'Posted')
|
||||
@@ -62,7 +59,7 @@ export async function getBlog(notion: Client, n2m: NotionToMarkdown, pageId: str
|
||||
description: blog.description,
|
||||
tags: blog.tags,
|
||||
date: blog.date,
|
||||
markdown: n2m.toMarkdownString(mdBlocks).parent,
|
||||
markdown: n2m.toMarkdownString(mdBlocks).parent || '',
|
||||
}];
|
||||
} catch (err: any) {
|
||||
return [500, {msg: String(err)}];
|
||||
|
||||
+10
-5
@@ -1,7 +1,12 @@
|
||||
import getBlogs from './blog';
|
||||
import { getBlogs } from './blog';
|
||||
import { notion, databaseId } from './notion';
|
||||
|
||||
export async function GET() {
|
||||
let [status, data] = await getBlogs(notion, databaseId);
|
||||
return Response.json(data, { status });
|
||||
}
|
||||
export async function GET(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
var cursor = url.searchParams.get('cursor') || undefined;
|
||||
if (cursor === '')
|
||||
cursor = undefined;
|
||||
const size = parseInt(url.searchParams.get('size') || '5');
|
||||
let [status, response] = await getBlogs(notion, databaseId, cursor, size);
|
||||
return Response.json(response, { status });
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ h1 {
|
||||
}
|
||||
|
||||
.name {
|
||||
font-family: 'Roboto Mono';
|
||||
font-family: sans-serif;
|
||||
color: #f5304c;
|
||||
text-align: center;
|
||||
font-weight: 800;
|
||||
@@ -59,7 +59,7 @@ a {
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 100px auto;
|
||||
margin: 10px auto;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
+16
-47
@@ -3,56 +3,30 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import ContentLoader from "react-content-loader"
|
||||
|
||||
export default function LoadingBlog(props: any) {
|
||||
const [numComponents, setNumComponents] = useState(0);
|
||||
export function LoadingBlog(props: any) {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Only set to client after mount
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const updateComponentCount = () => {
|
||||
const windowHeight = window.innerHeight; // Get window height
|
||||
const componentHeight = 100; // Set the height of your component
|
||||
const count = Math.floor(windowHeight / componentHeight); // Calculate how many components fit
|
||||
setNumComponents(count);
|
||||
};
|
||||
|
||||
// Initial calculation
|
||||
updateComponentCount();
|
||||
|
||||
// Add resize event listener
|
||||
window.addEventListener('resize', updateComponentCount);
|
||||
|
||||
// Cleanup listener on unmount
|
||||
return () => {
|
||||
window.removeEventListener('resize', updateComponentCount);
|
||||
};
|
||||
}, []); // Empty dependency array ensures the effect runs once after the initial render
|
||||
|
||||
// Only render on the client
|
||||
if (!isClient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
{Array.from({ length: numComponents }, (_, index) => (
|
||||
<ContentLoader
|
||||
speed={2}
|
||||
viewBox="0 0 290 50"
|
||||
backgroundColor="#3b3b3b"
|
||||
foregroundColor="#f5304c"
|
||||
key={index}
|
||||
{...props}
|
||||
>
|
||||
<rect x="0" y="0" rx="7" ry="7" width="290" height="18" />
|
||||
<rect x="0" y="25" rx="3" ry="3" width="78" height="7" />
|
||||
<rect x="0" y="37" rx="3" ry="3" width="200" height="7" />
|
||||
</ContentLoader>
|
||||
))}
|
||||
<ContentLoader
|
||||
speed={2}
|
||||
viewBox="0 0 290 50"
|
||||
backgroundColor="#3b3b3b"
|
||||
foregroundColor="#f5304c"
|
||||
{...props}
|
||||
>
|
||||
<rect x="0" y="0" rx="7" ry="7" width="290" height="18" />
|
||||
<rect x="0" y="25" rx="3" ry="3" width="78" height="7" />
|
||||
<rect x="0" y="37" rx="3" ry="3" width="200" height="7" />
|
||||
</ContentLoader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -67,29 +41,24 @@ export function LoadingBlogRead(props: any) {
|
||||
|
||||
useEffect(() => {
|
||||
const updateRectCount = () => {
|
||||
// Calculate the number of rectangles based on window height
|
||||
const windowHeight = window.innerHeight;
|
||||
const rectHeight = 46; // Height of each rectangle
|
||||
const count = Math.floor(windowHeight / rectHeight); // Number of rectangles that fit
|
||||
const rectHeight = 46;
|
||||
const count = Math.floor(windowHeight / rectHeight);
|
||||
setNumRects(count);
|
||||
};
|
||||
|
||||
// Update on mount and window resize
|
||||
updateRectCount();
|
||||
window.addEventListener('resize', updateRectCount);
|
||||
|
||||
// Cleanup event listener on unmount
|
||||
return () => {
|
||||
window.removeEventListener('resize', updateRectCount);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Only render on the client
|
||||
if (!isClient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Dynamically set the viewBox height based on the number of rectangles
|
||||
const viewBoxHeight = window.innerHeight / 3;
|
||||
|
||||
return (
|
||||
@@ -106,10 +75,10 @@ export function LoadingBlogRead(props: any) {
|
||||
{Array.from({ length: numRects }).map((_, i) => (
|
||||
<rect
|
||||
x="0"
|
||||
y={45 + i * 14} // Adjust position based on index
|
||||
y={45 + i * 14}
|
||||
rx="3"
|
||||
ry="3"
|
||||
width={Math.random() * (300 - 80) + 80} // Random width
|
||||
width={Math.random() * (300 - 80) + 80}
|
||||
height="9"
|
||||
key={i}
|
||||
/>
|
||||
|
||||
+31
-51
@@ -1,65 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import '../../Blog.css';
|
||||
import '../Blog.css';
|
||||
import MarkdownRenderer from './MarkdownRenderer';
|
||||
import { LoadingBlogRead } from '../Loading';
|
||||
import { BlogT2 } from '../page';
|
||||
|
||||
export default function Blog({ params }: { params: Promise<{ id: string }> }) {
|
||||
const [blog, setBlog] = useState({ title: '', date: '', markdown: '' });
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [blog, setBlog] = useState<BlogT2 | null>(null);
|
||||
|
||||
const date = new Date(blog.date).toLocaleString('default', { month: 'long', day: 'numeric', year: 'numeric' });
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBlog = async () => {
|
||||
try {
|
||||
console.log('obtaining blog')
|
||||
const response = await fetch(`/api/${(await params).id}`);
|
||||
if (response.ok) {
|
||||
const blog = await response.json();
|
||||
document.title = blog.title + " - Leo's blog";
|
||||
setBlog(blog);
|
||||
} else {
|
||||
const errorData = await response.text();
|
||||
setError(errorData);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchBlog();
|
||||
}, [params]);
|
||||
|
||||
if (loading) {
|
||||
return <main>
|
||||
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||
<div className='blog_container'>
|
||||
<LoadingBlogRead/>
|
||||
</div>
|
||||
</main>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
useState(() => {
|
||||
params.then(({id}) => {
|
||||
fetch(`/api/${id}`).then((res) => {
|
||||
res.json().then((data) => {
|
||||
setBlog(data);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (blog !== null) {
|
||||
return (
|
||||
<main>
|
||||
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||
<div>Error: {error}</div>;
|
||||
<div className='blog_container'>
|
||||
<h2 className='blog_title'>{blog.title}</h2>
|
||||
<p className='date'>{new Date(blog.date).toLocaleString('default', { month: 'long', day: 'numeric', year: 'numeric' })}</p>
|
||||
<div className='markdown'><MarkdownRenderer>{blog.markdown.replaceAll('\n-\n', '\n<br/>\n')}</MarkdownRenderer></div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main>
|
||||
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||
<div className='blog_container'>
|
||||
<h2 className='blog_title'>{blog.title}</h2>
|
||||
<p className='date'>{date}</p>
|
||||
<div className='markdown'><MarkdownRenderer>{blog.markdown.replaceAll('\n-\n', '\n<br/>\n')}</MarkdownRenderer></div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<main>
|
||||
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||
<div className='blog_container'>
|
||||
<LoadingBlogRead/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
+104
-118
@@ -1,143 +1,129 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import '../Blog.css';
|
||||
import LoadingBlog from './Loading';
|
||||
import './Blog.css';
|
||||
import { LoadingBlog } from './Loading';
|
||||
import InfiniteScroll from "react-infinite-scroll-component";
|
||||
|
||||
type Blog = { title: string, description: string, id: string, date: string, tags: { name: string, color: string }[] };
|
||||
export type BlogT = {
|
||||
title: string,
|
||||
description: string,
|
||||
id: string,
|
||||
date: string,
|
||||
tags: { name: string, color: string }[]
|
||||
};
|
||||
|
||||
export type BlogT2 = {
|
||||
title: string,
|
||||
description: string,
|
||||
id: string,
|
||||
date: string,
|
||||
tags: { name: string, color: string }[],
|
||||
markdown: 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"
|
||||
}
|
||||
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() {
|
||||
const [data, setData] = useState<Blog[]>([]);
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [items, setItems] = useState<BlogT[]>([]);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [cursor, setCursor] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/');
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
setData(result);
|
||||
setLoading(false);
|
||||
} else {
|
||||
const errorData = await response.text();
|
||||
setError(errorData);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.toString());
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
|
||||
if (error) {
|
||||
if (error) {
|
||||
return (
|
||||
<main>
|
||||
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||
<div>Error: {error}</div>;
|
||||
</main>
|
||||
)
|
||||
const fetchData = () => {
|
||||
if (hasMore) {
|
||||
fetch(`/api/?cursor=${cursor}&size=10`).then((res) => {
|
||||
res.json().then((data: { items: BlogT[], hasMore: boolean, cursor: string }) => {
|
||||
setItems((prev) => [...prev, ...data.items]);
|
||||
setHasMore(data.hasMore);
|
||||
setCursor(data.cursor);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
} if (loading) {
|
||||
return (
|
||||
<main>
|
||||
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||
<LoadingBlog/>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const refreshData = () => {
|
||||
setItems([]);
|
||||
setHasMore(true);
|
||||
setCursor('');
|
||||
}
|
||||
|
||||
document.title = "Leo's blog";
|
||||
useEffect(() => {
|
||||
document.title = "Leo's blog";
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<a href="/blog"><h1 className='name'>Leo's blog</h1></a>
|
||||
<div className='container'>
|
||||
{data.map((blog, i) => {
|
||||
return (
|
||||
<a key={i} href={`/blog/${blog.id}`} style={{ textDecoration: 'none' }}>
|
||||
<div className='blog'>
|
||||
<h2 className='title'>{blog.title}</h2>
|
||||
<p className='date'>{new Date(blog.date).toLocaleString('default', { month: 'long', day: 'numeric', year: 'numeric' })}</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>
|
||||
<InfiniteScroll
|
||||
pullDownToRefresh
|
||||
pullDownToRefreshThreshold={50}
|
||||
dataLength={items.length}
|
||||
next={fetchData}
|
||||
hasMore={hasMore}
|
||||
loader={<LoadingBlog/>}
|
||||
refreshFunction={refreshData}
|
||||
endMessage={
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
<a>Yay! You have seen it all</a>
|
||||
</p>
|
||||
}
|
||||
>
|
||||
{items.map((blog, i) => (
|
||||
<a key={i} href={`/blog/${blog.id}`} style={{ textDecoration: 'none' }}>
|
||||
<div className='blog'>
|
||||
<h2 className='title'>{blog.title}</h2>
|
||||
<p className='date'>{new Date(blog.date).toLocaleString('default', { month: 'long', day: 'numeric', year: 'numeric' })}</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',
|
||||
marginLeft: i === 0 ? '0px' : '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>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
<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',
|
||||
marginLeft: i === 0 ? '0px' : '8px',
|
||||
}}>{tag.name}</span>
|
||||
))}
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
+7
-3
@@ -2,8 +2,8 @@ import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "Leo's portfolio",
|
||||
description: "Leo's portfolio"
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -14,7 +14,11 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en">
|
||||
<title>Leo's portfolio</title>
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico"></link>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/logo.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/logo.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<body>
|
||||
{children}
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user