mirror of
https://github.com/DefectingCat/DefectingCat.github.io
synced 2025-07-16 01:01:38 +00:00
Add post page
* add post table of content component * add post header component * modify rua css * fix RUA Link component external icon always displayed
This commit is contained in:
11
.idea/inspectionProfiles/Project_Default.xml
generated
11
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,6 +1,17 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="CssUnknownProperty" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myCustomPropertiesEnabled" value="true" />
|
||||
<option name="myIgnoreVendorSpecificProperties" value="false" />
|
||||
<option name="myCustomPropertiesList">
|
||||
<value>
|
||||
<list size="1">
|
||||
<item index="0" class="java.lang.String" itemvalue="scrollbar-width" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
@ -1,13 +1,18 @@
|
||||
import { FC } from 'react';
|
||||
import cn from 'classnames';
|
||||
|
||||
const RUAButton: FC = ({ children }) => {
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const RUAButton: FC<Props> = ({ className, children }) => {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={cn(
|
||||
'rounded-lg bg-white px-6 py-3 font-semibold',
|
||||
'focus:shadow-outline active:bg-gray-100 transition-all'
|
||||
'focus:shadow-outline active:bg-gray-100 transition-all',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
@ -8,7 +8,7 @@ interface Props {
|
||||
isExternal?: boolean;
|
||||
}
|
||||
|
||||
const RUALink: FC<Props> = ({ href, isExternal, children }) => {
|
||||
const RUALink: FC<Props> = ({ href, isExternal = false, children }) => {
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
@ -23,7 +23,7 @@ const RUALink: FC<Props> = ({ href, isExternal, children }) => {
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<FiExternalLink className="inline mx-[2px]" />
|
||||
{isExternal && <FiExternalLink className="inline mx-[2px]" />}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
|
56
components/post/PostHeader.tsx
Normal file
56
components/post/PostHeader.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { FC } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { FiCalendar } from 'react-icons/fi';
|
||||
import cn from 'classnames';
|
||||
|
||||
const DateFormater = dynamic(() => import('components/DateFormater'));
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
tags: string[] | string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
const PostHeader: FC<Props> = ({ title, tags, date }) => {
|
||||
return (
|
||||
<header className={'mb-6'}>
|
||||
<h1 className={'text-3xl md:text-4xl font-semibold text-gray-700 mb-4'}>
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
{Array.isArray(tags)
|
||||
? // Mutil tags
|
||||
tags.map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
className={cn(
|
||||
'rounded-md bg-gray-100 px-2 py-1 inline text-sm',
|
||||
'text-gray-700'
|
||||
)}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))
|
||||
: // Signal tags
|
||||
tags && (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-md bg-gray-100 px-2 py-1 inline text-sm',
|
||||
'text-gray-700'
|
||||
)}
|
||||
>
|
||||
{tags}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={'flex items-center text-gray-600 text-sm'}>
|
||||
<FiCalendar className={'mr-2'} />
|
||||
<DateFormater dateString={date} />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostHeader;
|
55
components/post/PostTOC.tsx
Normal file
55
components/post/PostTOC.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import React, { createElement, FC, Fragment } from 'react';
|
||||
import { unified } from 'unified';
|
||||
import remarkParse from 'remark-parse';
|
||||
import remarkToc from 'remark-toc';
|
||||
import remarkRehype from 'remark-rehype';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import rehypeReact from 'rehype-react';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const Link = dynamic(() => import('components/RUA/RUALink'));
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const tocPartten = /(?:^|\n)#{2,3}[ ](.*)/g;
|
||||
|
||||
const processedTOC = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkToc, {
|
||||
maxDepth: 3,
|
||||
})
|
||||
.use(remarkRehype, { allowDangerousHtml: true })
|
||||
.use(rehypeRaw)
|
||||
.use(rehypeSlug)
|
||||
.use(rehypeReact, {
|
||||
createElement,
|
||||
components: {
|
||||
a: (props: any) => <Link href={props.href}>{props.children}</Link>,
|
||||
},
|
||||
Fragment,
|
||||
});
|
||||
|
||||
const PostToc: FC<Props> = ({ content }) => {
|
||||
const matchedToc = content.match(tocPartten);
|
||||
|
||||
let toc: string | null = null;
|
||||
if (matchedToc) {
|
||||
toc = matchedToc.join(' ');
|
||||
}
|
||||
|
||||
const result = processedTOC.processSync(
|
||||
`\n## Table of contents\n${toc}`
|
||||
).result;
|
||||
const TOC = [
|
||||
(result.props as any).children[0],
|
||||
(result.props as any).children[1],
|
||||
(result.props as any).children[2],
|
||||
];
|
||||
|
||||
return <>{TOC}</>;
|
||||
};
|
||||
|
||||
export default PostToc;
|
59
lib/posts.ts
59
lib/posts.ts
@ -14,22 +14,21 @@ export type {
|
||||
|
||||
/**
|
||||
* Paging, get all posts length
|
||||
* @returns
|
||||
* Returns an array that looks like this:
|
||||
* [
|
||||
* {
|
||||
* params: {
|
||||
* num: '2' // num of page
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* params: {
|
||||
* num: '3'
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
export function getAllPostNum() {
|
||||
// Returns an array that looks like this:
|
||||
// [
|
||||
// {
|
||||
// params: {
|
||||
// num: '2' // num of page
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// params: {
|
||||
// num: '3'
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
const pagingSize = 10;
|
||||
const allPages = Math.ceil(fileNames.length / pagingSize);
|
||||
|
||||
@ -51,6 +50,10 @@ export interface PagingData {
|
||||
postDatas: AllPostsWithDescription[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pagination data.
|
||||
* @param start start page.
|
||||
*/
|
||||
export function getPagingData(start?: string) {
|
||||
const totalNum = allPostsWithDescription.length;
|
||||
const pagingSize = 10;
|
||||
@ -68,20 +71,24 @@ export function getPagingData(start?: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array that looks like this:
|
||||
* [
|
||||
* {
|
||||
* params: {
|
||||
* id: 'ssg-ssr'
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* params: {
|
||||
* id: 'pre-rendering'
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
export function getAllPostSlugs() {
|
||||
// Returns an array that looks like this:
|
||||
// [
|
||||
// {
|
||||
// params: {
|
||||
// id: 'ssg-ssr'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// params: {
|
||||
// id: 'pre-rendering'
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
|
||||
return allPostsWithMatter.map((post) => {
|
||||
return {
|
||||
params: {
|
||||
|
@ -1,9 +1,126 @@
|
||||
import { AllPostsWithContent, getAllPostSlugs, getPostData } from 'lib/posts';
|
||||
import { GetStaticProps } from 'next';
|
||||
import { FC } from 'react';
|
||||
import { GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||
import React, { createElement, Fragment } from 'react';
|
||||
import { unified } from 'unified';
|
||||
import remarkParse from 'remark-parse';
|
||||
import remarkRehype from 'remark-rehype';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeReact from 'rehype-react';
|
||||
import 'highlight.js/styles/github.css';
|
||||
// import 'highlight.js/styles/github-dark.css';
|
||||
import xml from 'highlight.js/lib/languages/xml';
|
||||
import bash from 'highlight.js/lib/languages/bash';
|
||||
import Head from 'next/head';
|
||||
import cn from 'classnames';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Image from 'next/image';
|
||||
import { FiChevronLeft } from 'react-icons/fi';
|
||||
import Sticky from 'react-stickynode';
|
||||
import useMediaQuery from 'lib/hooks/useMediaQuery';
|
||||
|
||||
const Post: FC = () => {
|
||||
return <></>;
|
||||
const Button = dynamic(() => import('components/RUA/RUAButton'));
|
||||
const Link = dynamic(() => import('components/RUA/RUALink'));
|
||||
const TableOfContent = dynamic(() => import('components/post/PostTOC'));
|
||||
const PostHeader = dynamic(() => import('components/post/PostHeader'));
|
||||
|
||||
const processedContent = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkRehype, { allowDangerousHtml: true })
|
||||
.use(rehypeRaw)
|
||||
.use(rehypeHighlight, {
|
||||
languages: { vue: xml, bash },
|
||||
aliases: { bash: ['npm'] },
|
||||
ignoreMissing: true,
|
||||
})
|
||||
.use(rehypeSlug)
|
||||
.use(remarkGfm, { tableCellPadding: true })
|
||||
.use(rehypeReact, {
|
||||
createElement,
|
||||
components: {
|
||||
a: (props: any) => (
|
||||
<Link href={props.href} isExternal>
|
||||
{props.children}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
Fragment,
|
||||
});
|
||||
|
||||
const Post = ({ postData }: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
const matched = useMediaQuery('(max-width: 640px)');
|
||||
|
||||
const { title, index_img, content, tags, date } = postData;
|
||||
|
||||
const postContent = processedContent.processSync(content).result;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
</Head>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'grid grid-cols-12 gap-4 p-2 container mx-auto',
|
||||
'md:py-8'
|
||||
)}
|
||||
>
|
||||
<aside
|
||||
className={cn(
|
||||
'col-span-12 justify-end',
|
||||
'md:col-span-2 lg:col-span-1'
|
||||
)}
|
||||
>
|
||||
<Sticky enabled={!matched} top={32}>
|
||||
<Button
|
||||
className={cn(
|
||||
'flex items-center w-full',
|
||||
'text-gray-600 md:justify-center'
|
||||
)}
|
||||
>
|
||||
<FiChevronLeft className="mr-2 md:hidden" />
|
||||
BACK
|
||||
</Button>
|
||||
</Sticky>
|
||||
</aside>
|
||||
|
||||
<main
|
||||
className={cn(
|
||||
'bg-white shadow-md col-span-12 rounded-lg',
|
||||
'md:col-span-10 overflow-hidden lg:col-span-8'
|
||||
)}
|
||||
>
|
||||
{index_img && (
|
||||
<div className="relative aspect-video ">
|
||||
<Image
|
||||
src={index_img}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
alt="Article image"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<article className={cn('p-4 lg:p-7')}>
|
||||
<PostHeader title={title} tags={tags} date={date} />
|
||||
|
||||
<section className={cn()} id={'write'}>
|
||||
{postContent}
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<aside className={cn('hidden lg:block col-span-3')}>
|
||||
<Sticky enabled top={32}>
|
||||
<TableOfContent content={content} />
|
||||
</Sticky>
|
||||
</aside>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Post;
|
||||
|
@ -1,32 +1,10 @@
|
||||
:root {
|
||||
--text-color: var(--chakra-colors-gray-600);
|
||||
--heading-color: #464b1f;
|
||||
--block-bg: linear-gradient(#fbebef, #dff0f5);
|
||||
--block-bg-color: #f8f8f8;
|
||||
--code-bg: var(--chakra-colors-gray-100);
|
||||
--code-border-color: #e7eaed;
|
||||
|
||||
--shadow-color: #c3c5c7;
|
||||
}
|
||||
|
||||
/* With Chakra dark mode */
|
||||
body.chakra-ui-dark {
|
||||
--text-color: var(--chakra-colors-whiteAlpha-800);
|
||||
--heading-color: var(--chakra-colors-whiteAlpha-800);
|
||||
--block-bg: linear-gradient(#c395a1, #8e9fa3);
|
||||
--block-bg-color: #888888;
|
||||
--code-bg: var(--chakra-colors-gray-500);
|
||||
--code-border-color: #919191;
|
||||
}
|
||||
|
||||
::selection {
|
||||
color: unset;
|
||||
background: rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
#write {
|
||||
font-size: 16px;
|
||||
color: var(--text-color);
|
||||
@apply text-gray-600;
|
||||
}
|
||||
|
||||
#table-of-contents {
|
||||
@ -34,7 +12,6 @@ body.chakra-ui-dark {
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.02em;
|
||||
line-height: 2rem;
|
||||
margin: 1rem 0;
|
||||
text-transform: uppercase;
|
||||
color: #797979;
|
||||
white-space: nowrap;
|
||||
@ -74,22 +51,16 @@ body.chakra-ui-dark {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#write a {
|
||||
color: var(--chakra-colors-teal-500);
|
||||
}
|
||||
|
||||
#write h1 {
|
||||
font-size: 3.25rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.03em;
|
||||
line-height: 4rem;
|
||||
margin: 0 0 1.75rem;
|
||||
padding: 20px 30px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 4rem;
|
||||
color: var(--heading-color);
|
||||
margin: 4rem 0;
|
||||
}
|
||||
|
||||
#write h2 {
|
||||
@ -98,7 +69,6 @@ body.chakra-ui-dark {
|
||||
letter-spacing: 0.02em;
|
||||
line-height: 2rem;
|
||||
padding: 1rem 0;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
|
||||
#write h3 {
|
||||
@ -106,7 +76,6 @@ body.chakra-ui-dark {
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.02em;
|
||||
line-height: 3rem;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
|
||||
#write h4 {
|
||||
@ -115,17 +84,14 @@ body.chakra-ui-dark {
|
||||
font-size: 1.25rem;
|
||||
line-height: 2rem;
|
||||
margin-top: 1rem;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
|
||||
#write h5 {
|
||||
font-size: 1.15rem;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
|
||||
#write h6 {
|
||||
font-size: 1rem;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
|
||||
#write p {
|
||||
@ -137,10 +103,10 @@ body.chakra-ui-dark {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: calc(0.1rem + 2.43px);
|
||||
width: calc(0.1rem + 2px);
|
||||
height: 100%;
|
||||
transform: translateX(calc(-0.05rem - 1.215px));
|
||||
background-image: var(--block-bg);
|
||||
transform: translateX(calc(-0.05rem - 1px));
|
||||
background-image: linear-gradient(#fbebef, #dff0f5);
|
||||
border-radius: calc(0.5rem + 0.25%) 0 0 calc(0.5rem + 0.25%);
|
||||
z-index: 1;
|
||||
}
|
||||
@ -155,8 +121,8 @@ body.chakra-ui-dark {
|
||||
position: relative;
|
||||
word-spacing: 0.06em;
|
||||
margin: 0.5rem 0;
|
||||
padding: 1rem calc(0.5rem + 5.13px);
|
||||
background-color: var(--block-bg-color);
|
||||
padding: 1rem calc(0.5rem + 5px);
|
||||
background-color: #f8f8f8;
|
||||
line-height: 1.6;
|
||||
border-radius: 0 calc(0.5rem + 0.25%) calc(0.5rem + 0.25%) 0;
|
||||
}
|
||||
@ -217,15 +183,6 @@ body.chakra-ui-dark {
|
||||
transform: translate(-1rem, -0.75rem);
|
||||
}
|
||||
|
||||
#write .md-task-list-item {
|
||||
transition: all 0.2s ease-in-out 0s;
|
||||
transform: translateX(1.4rem);
|
||||
}
|
||||
|
||||
#write .md-task-list-item::before {
|
||||
content: '';
|
||||
}
|
||||
|
||||
#write input + p {
|
||||
display: inline-block;
|
||||
}
|
||||
@ -272,53 +229,37 @@ body.chakra-ui-dark {
|
||||
color: #c3c4d0;
|
||||
}
|
||||
|
||||
#write .md-task-list-item:hover {
|
||||
color: #c3c4d0;
|
||||
}
|
||||
|
||||
#write [data-rmiz-wrap='visible'],
|
||||
#write [data-rmiz-wrap='hidden'] {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
#write pre {
|
||||
position: relative;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#write pre code {
|
||||
@apply bg-gray-100;
|
||||
padding: 6px 10px;
|
||||
background-color: var(--code-bg);
|
||||
}
|
||||
|
||||
#write code,
|
||||
#write tt {
|
||||
border: 1px solid var(--code-border-color);
|
||||
border-radius: 6px;
|
||||
@apply bg-gray-100 rounded-md;
|
||||
padding: 0 4px;
|
||||
font-size: 0.9em;
|
||||
background-color: var(--code-bg);
|
||||
}
|
||||
|
||||
#write mjx-container {
|
||||
font-size: 26px;
|
||||
font-family: 'Apple Symbols';
|
||||
}
|
||||
|
||||
#write table {
|
||||
@apply border border-gray-100;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid var(--code-border-color);
|
||||
}
|
||||
|
||||
#write th {
|
||||
background-color: var(--code-bg);
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
|
||||
#write td,
|
||||
#write th {
|
||||
position: relative;
|
||||
border: solid 1px var(--code-border-color);
|
||||
border: solid 1px #e7eaed;
|
||||
padding: 0.8em 0.5rem;
|
||||
}
|
||||
|
||||
@ -326,7 +267,6 @@ body.chakra-ui-dark {
|
||||
color: #fff;
|
||||
background-color: rgba(231, 153, 176, 0.68);
|
||||
border-radius: 0.2em;
|
||||
/*border: 1px solid #c5c8ca;*/
|
||||
padding: 0.1em 0.2em;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user