Skip to content

Commit

Permalink
Merge pull request #63 from 0si43/feature/table-of-contents
Browse files Browse the repository at this point in the history
目次要素を追加した
  • Loading branch information
0si43 authored May 5, 2024
2 parents 8896aea + 1e731f1 commit e9e3c20
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 33 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ yarn-error.log*

# ブログ記事内の画像
/public/blogImages

# TypeScript incremental build info
tsconfig.tsbuildinfo
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "tsc && next dev",
"build": "next build",
"start": "next start"
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Header = ({ titlePre = '' }) => {
<header className={styles.header}>
<Head>
<link rel="icon" href="/profile.png" />
<title>{titlePre} </title>
<title>{`${titlePre}`}</title>
<meta
name="description"
content="Shetommy's portfolio website and personal blog"
Expand Down
93 changes: 86 additions & 7 deletions src/components/renderNotionBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import linkCard from './linkCard'
import { Fragment } from 'react'
import Image from 'next/image'


export type RichText = {
type: 'text'
text: {
Expand Down Expand Up @@ -45,7 +44,12 @@ export type RichText = {
}

/// 子ブロックを含めたブロックをHTML要素にレンダリングする
export const renderBlock = (block: NotionBlockWithChildren) => {
export const renderBlock = (
{ block, tableOfContents }: {
block: NotionBlockWithChildren,
tableOfContents: NotionBlockWithChildren[]
}
) => {
if (block.ogpData?.requestUrl) {
return linkCard(block.ogpData.requestUrl, block.ogpData)
}
Expand All @@ -60,19 +64,19 @@ export const renderBlock = (block: NotionBlockWithChildren) => {
)
case 'heading_1':
return (
<h1>
<h1 id={block.id}>
<TextComponent richTexts={block.heading_1.text as RichText[]} />
</h1>
)
case 'heading_2':
return (
<h2>
<h2 id={block.id}>
<TextComponent richTexts={block.heading_2.text as RichText[]} />
</h2>
)
case 'heading_3':
return (
<h3>
<h3 id={block.id}>
<TextComponent richTexts={block.heading_3.text as RichText[]} />
</h3>
)
Expand Down Expand Up @@ -111,7 +115,7 @@ export const renderBlock = (block: NotionBlockWithChildren) => {
</summary>
<>
{block.children?.map((block) => (
<Fragment key={block.id}>{renderBlock(block)}</Fragment>
<Fragment key={block.id}>{renderBlock({ block: block, tableOfContents: tableOfContents })}</Fragment>
))}
</>
</details>
Expand Down Expand Up @@ -168,7 +172,7 @@ export const renderBlock = (block: NotionBlockWithChildren) => {
case 'divider':
return <hr></hr>
case 'table_of_contents':
return `(将来的にはここに目次が入るようにします。現在実装中)`
return (<TableOfContentsComponent tableOfContents={tableOfContents}/>)
default:
return `❌ Unsupported block (${
type === 'unsupported' ? 'unsupported by Notion API' : type
Expand Down Expand Up @@ -206,3 +210,78 @@ const TextComponent = ({ richTexts }: { richTexts: RichText[] }) => {

return <>{elements}</>
}

const TableOfContentsComponent = ({ tableOfContents }: { tableOfContents: NotionBlockWithChildren[] }) => {
if (tableOfContents.length === 0) {
return null
}

const renderTableOfContents = (blocks: NotionBlockWithChildren[]) => {
const groupedBlocks: NotionBlockWithChildren[][] = []
const sameHeadingBlocks: NotionBlockWithChildren[] = []

blocks.forEach((block, index) => {
if (sameHeadingBlocks[sameHeadingBlocks.length - 1] &&
block.type != sameHeadingBlocks[sameHeadingBlocks.length - 1].type) {
groupedBlocks.push([...sameHeadingBlocks])
sameHeadingBlocks.length = 0
}

sameHeadingBlocks.push(block)

if (index == blocks.length - 1) {
groupedBlocks.push([...sameHeadingBlocks])
}
})

return (
<ol>
{groupedBlocks.map((blocks) => {
switch (blocks[0]?.type) {
case 'heading_1':
return blocks.flatMap((block) => renderBlock(block))
case 'heading_2':
return (<ol key={blocks[0]?.id}>{blocks.flatMap((block) => renderBlock(block))}</ol>)
case 'heading_3':
return (<ol key={blocks[0]?.id}><ol>{blocks.flatMap((block) => renderBlock(block))}</ol></ol>)
default:
return null
}
})}
</ol>
)
}

const renderBlock = (block: NotionBlockWithChildren) => {
switch (block.type) {
case 'heading_1':
return (
<li key={block.id}>
<a href={`#${block.id}`}>{block.heading_1.text[0].plain_text}</a>
</li>
)
case 'heading_2':
return (
<li key={block.id}>
<a href={`#${block.id}`}>{block.heading_2.text[0].plain_text}</a>
</li>
)
case 'heading_3':
return (
<li key={block.id}>
<a href={`#${block.id}`}>{block.heading_3.text[0].plain_text}</a>
</li>
)

default:
return null
}
}

return (
<nav>
<h1>目次</h1>
{renderTableOfContents(tableOfContents)}
</nav>
)
}
27 changes: 19 additions & 8 deletions src/pages/articles/[title].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
getPageTitle,
getBlocks,
isPublishDate,
type NotionPage
type NotionPage,
NotionBlockWithChildren
} from '../../components/notion'
import { renderBlock } from '../../components/renderNotionBlock'
import getOgpData from '../../components/getOgpData'
Expand All @@ -17,15 +18,15 @@ import { Fragment } from 'react'
import { InferGetStaticPropsType, GetStaticPropsContext } from 'next'
import { ParsedUrlQuery } from 'querystring'

export default function Post({ title, blocks }: Props) {
export default function Post({ title, blocks, tableOfContentsBlocks }: Props) {
return (
<div>
<Header titlePre={title} />
<article className={styles.container}>
<h1 className={styles.name}>{title}</h1>
<section>
{blocks.map((block) => (
<Fragment key={block.id}>{renderBlock(block)}</Fragment>
<Fragment key={block.id}>{renderBlock({ block: block, tableOfContents: tableOfContentsBlocks })}</Fragment>
))}
</section>
</article>
Expand Down Expand Up @@ -93,29 +94,39 @@ export const getStaticProps = async (context: GetStaticPropsContext) => {

saveImageIfNeeded(blocksWithChildren)

/// OG情報を取得する
const blocksWithOGP = await Promise.all(
blocksWithChildren.map(async (block) => {
const blocksWithOGP: NotionBlockWithChildren[] = []
const tableOfContentsBlocks: NotionBlockWithChildren[] = []

await Promise.all(
blocksWithChildren.map(async (block, index) => {
/// 目次用のブロックを抽出
if (['heading_1', 'heading_2', 'heading_3'].includes(block.type)) {
tableOfContentsBlocks.push(block)
}

/// OG情報を取得する
if (block.type === 'paragraph'
&& block.paragraph.text.length == 1
&& block.paragraph.text[0].type === 'text'
&& block.paragraph.text[0].text.link?.url
) {
) {
const richText = block.paragraph.text[0] as { type: 'text'; text: { link: { url: string } } }
block.ogpData = await getOgpData(richText.text.link.url)
} else if (block.type === 'bookmark') {
block.ogpData = await getOgpData(block.bookmark.url)
} else if (block.type === 'link_preview') {
block.ogpData = await getOgpData(block.link_preview.url)
}
return block

blocksWithOGP[index] = block
})
)

return {
props: {
title,
blocks: blocksWithOGP,
tableOfContentsBlocks: tableOfContentsBlocks,
},
revalidate: 1,
}
Expand Down
21 changes: 19 additions & 2 deletions src/pages/articles/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import {
import styles from '../../styles/articles/index.module.css'
import Footer from '../../components/footer'

import { useEffect, useState } from 'react'
import { InferGetStaticPropsType } from 'next'
import Link from 'next/link'


export const databaseId = process.env.NOTION_DATABASE_ID
? process.env.NOTION_DATABASE_ID
: ''
Expand Down Expand Up @@ -44,6 +46,20 @@ export const getStaticProps = async () => {
}

export default function Home({ db, openingSentences }: Props) {
const [formattedDates, setFormattedDates] = useState<string[]>([]);

useEffect(() => {
const dates = db.map((page) => {
const date = new Date(getPageDate(page as NotionPage))
return date.toLocaleDateString(navigator.language, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
})
setFormattedDates(dates)
}, [db])

return (
<div>
<main className={styles.container}>
Expand All @@ -52,7 +68,6 @@ export default function Home({ db, openingSentences }: Props) {
<ol className={styles.posts}>
{db.map((post, index) => {
const title = getPageTitle(post as NotionPage)
const date = getPageDate(post as NotionPage).toLocaleDateString()

if (title.length <= 0) {
return <></>
Expand All @@ -64,7 +79,9 @@ export default function Home({ db, openingSentences }: Props) {
<h3 className={styles.postTitle}>
{title}
</h3>
<p className={styles.postDescription}>{date}</p>
<p className={styles.postDescription}>
{formattedDates[index]}
</p>
<p className={styles.postDescription}>
{openingSentences[index]}
</p>
Expand Down
28 changes: 14 additions & 14 deletions src/pages/dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ import Link from 'next/link'

export default function Dev() {
return (
<>
<h1>
プライバシーポリシー
</h1>
<p>
<Link href="/privacy_policy/wristcounter.html">
WristCounter
</Link>
</p>
<p>
<Link href="/privacy_policy/wristcounter_jp.html">
WristCounter(日本語)
</Link>
</p>
<>
<h1>
プライバシーポリシー
</h1>
<p>
<Link href="/privacy_policy/wristcounter.html">
WristCounter
</Link>
</p>
<p>
<Link href="/privacy_policy/wristcounter_jp.html">
WristCounter(日本語)
</Link>
</p>
</>
)
}
44 changes: 44 additions & 0 deletions src/styles/articles/post.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,47 @@
color: #999;
white-space: nowrap;
}

.container nav {
background-color: #f5f5f5;
padding: 16px;
border-radius: 5px;
}

.container nav h1 {
font-size: 16px;
font-weight: bold;
margin: 0;
margin-bottom: 16px;
}

.container nav ol {
font-size: 14px;
list-style-type: none;
padding-left: 20px;
}

.container nav > ol {
padding-left: 0;
}

.container nav li {
margin-bottom: 4px;
padding-bottom: 4px;
border-bottom: 1px solid #ccc;
}

.container nav > ol > li:last-child,
.container nav > ol > li > ol > li:last-child,
.container nav > ol > li > ol > li > ol > li:last-child {
border-bottom: none;
}

.container nav a {
text-decoration: none;
transition: color 0.3s;
}

.container nav a:hover {
text-decoration: underline;
}
1 change: 1 addition & 0 deletions src/styles/home/skills.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
margin: 1rem;
padding: 0 1rem 0 1rem;
display: flex;
align-items: center;
flex-shrink: 0;
box-shadow: 1px 0 5px 1px #999;
}
Expand Down

0 comments on commit e9e3c20

Please sign in to comment.