mirror of
https://github.com/Lumither/nocturne.git
synced 2026-10-17 10:26:21 +00:00
feat(blog) add footer for post reading part
This commit is contained in:
11 files changed
+177
-22
No files matched your search
@@ -230,7 +230,7 @@ $$
|
||||
_insert_columns TEXT := 'post_id';
|
||||
_insert_values TEXT := quote_literal(_post_id);
|
||||
_update_set TEXT := '';
|
||||
_blacklist TEXT[] := ARRAY['id', 'tags'];
|
||||
_blacklist TEXT[] := ARRAY['tags'];
|
||||
BEGIN
|
||||
SELECT EXISTS(SELECT 1 FROM meta WHERE post_id = _post_id) INTO _exists;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ function Layout({
|
||||
}) {
|
||||
return (
|
||||
// <div className={ 'flex flex-auto p-7 pl-2 md:pl-7 items-stretch justify-center' }>
|
||||
<div className={ 'flex flex-auto p-7 pl-2 md:pl-7 w-full justify-center' }>
|
||||
<div className={ 'flex flex-auto p-7 pl-2 lg:pl-7 w-full justify-center' }>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import MinifyPostCard from '@/app/blog/post/[id]/MinifyPostCard';
|
||||
import Link from 'next/link';
|
||||
import { FaCreativeCommons } from 'react-icons/fa6';
|
||||
|
||||
type Props = {
|
||||
post: any
|
||||
}
|
||||
|
||||
const Footer = (props: Props) => {
|
||||
const { post } = props;
|
||||
|
||||
const header_img = post['header_img'];
|
||||
const adjacent_posts = post['adj'];
|
||||
const prev_post = adjacent_posts['prev'];
|
||||
const next_post = adjacent_posts['next'];
|
||||
const link = post['meta']['link'] ?? `https://lumither.com/blog/post/${ post['meta']['id'] }`;
|
||||
const author = post['meta']['author'] ?? 'Tao';
|
||||
const author_link = post['meta']['author_link'] ?? '/about';
|
||||
const post_date = new Date(post['first_update']);
|
||||
const update_date = new Date(post['last_update']);
|
||||
const cc = post['meta']['cc'] ?? 'by-nc-sa';
|
||||
|
||||
|
||||
return (
|
||||
<div className={ `w-full` }>
|
||||
|
||||
<div className={ 'flex transition bg-zinc-300/80 dark:bg-gray-600/25' }>
|
||||
<div className={ `p-8 w-full` }>
|
||||
<div className={ 'flex flex-col space-y-4 relative' }>
|
||||
<div className={ 'z-[2]' }>
|
||||
<p className={ 'text-xl font-bold' }>{ post['meta']['title'] }</p>
|
||||
<Link href={ link }>
|
||||
<p className={ 'text-sm underline decoration-indigo-500 dark:decoration-sky-500' }>{ link }</p>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={ 'z-[2] flex flex-row flex-wrap gap-x-7 gap-y-2' }>
|
||||
<div>
|
||||
<p className={ 'text-lg font-bold' }>Author</p>
|
||||
<Link href={ author_link }>
|
||||
<p className={ 'underline decoration-indigo-500 dark:decoration-sky-500' }>{ author }</p>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<p className={ 'text-lg font-bold' }>Posted on</p>
|
||||
<p>{ post_date.toLocaleString('default', {
|
||||
month: `long`,
|
||||
day: `numeric`,
|
||||
year: `numeric`
|
||||
}) }</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className={ 'text-lg font-bold' }>Updated on</p>
|
||||
<p>{ update_date.toLocaleString('default', {
|
||||
month: `long`,
|
||||
day: `numeric`,
|
||||
year: `numeric`
|
||||
}) }</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className={ 'text-lg font-bold' }>Published under</p>
|
||||
<Link href={ `https://creativecommons.org/licenses/${ cc }/4.0/` }>
|
||||
<p className={ 'underline decoration-indigo-500 dark:decoration-sky-500' }>{ `CC ${ cc.toUpperCase() } 4.0` }</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<FaCreativeCommons size={ '80px' }
|
||||
className={ 'absolute inset-y-0 right-0 opacity-50 brightness-75 z-0' } />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={ `flex justify-center` }>
|
||||
<div className={ `w-full shadow-2xl ` }>
|
||||
<div className={ `flex flex-col md:flex-row w-full` }>
|
||||
{
|
||||
next_post &&
|
||||
<MinifyPostCard post={ next_post } desc={ 'prev' }></MinifyPostCard>
|
||||
}
|
||||
{
|
||||
prev_post &&
|
||||
<MinifyPostCard post={ prev_post } desc={ 'next' }></MinifyPostCard>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Footer;
|
||||
@@ -10,15 +10,15 @@ type Props = {
|
||||
|
||||
const Headers = (props: Props) => {
|
||||
let post = props.post;
|
||||
let title = (post as any)['title'];
|
||||
let sub_title = (post as any)['sub_title'];
|
||||
let title = post['title'];
|
||||
let sub_title = post['sub_title'];
|
||||
// let summary = (post as any)['summary'];
|
||||
let category = (post as any)['category'];
|
||||
let tags = (post as any)['tags'];
|
||||
let last_update = new Date((post as any)['last_update']);
|
||||
let first_update = new Date((post as any)['first_update']);
|
||||
let post_id = (post as any)['post_id'];
|
||||
let header_img = (post as any)['header_img'];
|
||||
let category = post['category'];
|
||||
let tags = post['tags'];
|
||||
// let last_update = new Date((post as any)['last_update']);
|
||||
let first_update = new Date(post['first_update']);
|
||||
// let post_id = (post as any)['post_id'];
|
||||
let header_img = post['header_img'];
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -39,7 +39,7 @@ const Headers = (props: Props) => {
|
||||
|
||||
<div
|
||||
className={ header_img !== '' ?
|
||||
'mx-8 -mt-16 backdrop-blur bg-zinc-300/80 dark:bg-gray-600/25 rounded-2xl z-30'
|
||||
'mx-8 -mt-16 backdrop-blur transition bg-zinc-300/80 dark:bg-gray-600/25 rounded-2xl z-30'
|
||||
: 'mx-6 mt-6'
|
||||
}>
|
||||
<div className={ `p-4` }>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { Card, CardFooter, CardHeader } from '@nextui-org/card';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
type Props = {
|
||||
post: any
|
||||
desc: string
|
||||
}
|
||||
|
||||
const MinifyPostCard = (props: Props) => {
|
||||
const { post, desc } = props;
|
||||
const img_url = post['header_img'] as string;
|
||||
return (
|
||||
<div className={ 'w-full transition hover:brightness-75' }>
|
||||
<Card isFooterBlurred
|
||||
className="w-full h-[300px] shadow-none border-0 rounded-none overflow-hidden"
|
||||
as={ Link }
|
||||
isPressable
|
||||
href={ `/blog/post/${ post['id'] }` }
|
||||
>
|
||||
<CardHeader className="absolute z-10 top-1 flex-col items-start">
|
||||
<p className={ `text-tiny uppercase font-bold ${ img_url ? 'text-white/60' : 'text-black/60 dark:text-white/60' }` }>{ desc }</p>
|
||||
</CardHeader>
|
||||
{ img_url &&
|
||||
<div>
|
||||
<Image
|
||||
className={ 'object-cover object-center ' }
|
||||
src={ img_url }
|
||||
alt={ post.title }
|
||||
fill
|
||||
/>
|
||||
<CardFooter
|
||||
className="absolute transition-colors bg-zinc-300/80 dark:bg-gray-600/25 dark:backdrop-brightness-75 bottom-0 justify-between rounded-none">
|
||||
<div>
|
||||
<p className="text-2xl font-bold">{ post['title'] }</p>
|
||||
<p className="">{ post['sub_title'] }</p>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</div>
|
||||
}
|
||||
{ !img_url &&
|
||||
<div className={ 'h-full grid p-6 place-content-center' }>
|
||||
<div className={ 'flex flex-row items-center text-center max-h-fit' }>
|
||||
<p className={ 'text-3xl font-bold' }>{ post['title'] }</p>
|
||||
<div className={ 'h-full w-1 mx-2 bg-zinc-500 dark:bg-zinc-400' }></div>
|
||||
<p className={ 'text-xl' }>{ post['sub_title'] }</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default MinifyPostCard;
|
||||
@@ -10,6 +10,7 @@ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeSanitize from 'rehype-sanitize';
|
||||
import Headers from '@/app/blog/post/[id]/Headers';
|
||||
import Footer from '@/app/blog/post/[id]/Footer';
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
@@ -87,6 +88,12 @@ async function PostCard(props: Props) {
|
||||
} }
|
||||
>{ (post_data as any)['content'] }</Markdown>
|
||||
</CardBody>
|
||||
|
||||
|
||||
{/* card footer */ }
|
||||
|
||||
<Footer post={ post_data } />
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={ `dark:bg-[#282830] min-h-screen` }>
|
||||
<body className={ `transition duration-300 dark:bg-[#282830] min-h-screen` }>
|
||||
<NextThemesProvider attribute="class" defaultTheme="dark">
|
||||
<div className={ `justify-center flex flex-row w-full max-w-full` }>
|
||||
<Navbar />
|
||||
|
||||
@@ -89,7 +89,7 @@ function Navbar() {
|
||||
}, [ path ]);
|
||||
|
||||
|
||||
const minMobileWidth: number = 768;
|
||||
const minMobileWidth: number = 1024;
|
||||
const [ isMobile, setIsMobile ] = useState(false);
|
||||
useEffect(() => {
|
||||
const updateMobileWidth = () => {
|
||||
@@ -111,9 +111,9 @@ function Navbar() {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={ `flex flex-col min-w-[50px] md:min-w-[256px] mr-0 md:mr-2 ml-2 md:ml-8 min-h-dvh pt-8 sticky` }>
|
||||
className={ `flex flex-col min-w-[50px] lg:min-w-[256px] mr-0 lg:mr-2 ml-2 lg:ml-8 min-h-dvh pt-8 sticky` }>
|
||||
<div className={ `fixed min-h-screen h-full` }>
|
||||
<div className={ `hidden md:block` }>
|
||||
<div className={ `hidden lg:block` }>
|
||||
<div className={ `flex justify-center items-center my-7` }>
|
||||
<NextImage
|
||||
src={ 'https://oss.lumither.com/blog/pictures/compr/1717153492846703409_avatar.webp' }
|
||||
@@ -132,7 +132,7 @@ function Navbar() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul className={ `flex flex-col space-y-2 md:space-y-0 md:flex-row items-center my-4 md:space-x-2` }>
|
||||
<ul className={ `flex flex-col space-y-2 lg:space-y-0 lg:flex-row items-center my-4 lg:space-x-2` }>
|
||||
{
|
||||
connections.map((conn, key) => (
|
||||
<li key={ key }>
|
||||
@@ -154,7 +154,7 @@ function Navbar() {
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={ `flex justify-center md:justify-start w-full mt-8 ml-0 md:-ml-3` }>
|
||||
className={ `flex justify-center lg:justify-start w-full mt-8 ml-0 lg:-ml-3` }>
|
||||
<ul className={ `flex flex-col space-y-2 w-full` }>
|
||||
{
|
||||
entries.map((meta, key) => (
|
||||
@@ -165,11 +165,11 @@ function Navbar() {
|
||||
color={ `default` }
|
||||
fullWidth
|
||||
isIconOnly={ isMobile }
|
||||
className={ 'flex md:justify-start w-fit md:w-full' }
|
||||
className={ 'flex lg:justify-start w-fit lg:w-full' }
|
||||
aria-label={ `navbar: ${ meta.display_name }` }
|
||||
href={ meta.href }>
|
||||
{ meta.icon }
|
||||
<p className={ `font-bold hidden md:block` }>{ meta.display_name }</p>
|
||||
<p className={ `font-bold hidden lg:block` }>{ meta.display_name }</p>
|
||||
</Button>
|
||||
</li>
|
||||
))
|
||||
@@ -177,7 +177,7 @@ function Navbar() {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className={ `mt-8 flex md:hidden justify-center w-full` }>
|
||||
<div className={ `mt-8 flex lg:hidden justify-center w-full` }>
|
||||
<p className={ `text-center sideways-lr font-bold text-2xl text-zinc-500 dark:text-zinc-400` }>
|
||||
{ shownPath }
|
||||
</p>
|
||||
|
||||
@@ -33,7 +33,7 @@ function ThemeSwitcher() {
|
||||
}
|
||||
} }
|
||||
></Switch>
|
||||
<p className={ `font-bold hidden md:block` }>{ `Toggle Theme` }</p>
|
||||
<p className={ `font-bold hidden lg:block` }>{ `Toggle Theme` }</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.2",
|
||||
"postcss": "^8",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in new issue
Block a user