内容としては
NotionSDKを使ってdatabases queryを叩けるメソッドを作成
記事一覧に必要なデータを持つ型を作成
interface作成
こんな感じで記載していきたいと思います。
おtypographyページを作成して確認。
ブロックごとにcomponent作成
Blocksコンポーネントとしてループ
お勉強
フォルダ内一括import
/components/posts/blocks/
フォルダに、ブロックのコンポーネントを作成することにしました。
ブロックの種類は結構あるので、利用する箇所で毎回全てをimport
するのは結構大変ですし無駄です。
pythonの__init__.py
みたく、フォルダ内のモジュールを一括でimport
できないか探していましたが、やりかたありました。
フォルダの中にindex.ts
を用意
index.ts
1
2
3
4
5
6
7
export * from './Paragraph'
export * from './Heading1'
export * from './Heading2'
export * from './Heading3'
export * from './Callout'
export * from './Code'
export * from './Image'
そして使用したい箇所で
1
import * as NotionBlock from '../entities/notion/blocks'
とすると、index.ts
でexport
したコンポーネントが下記の要領で利用できるようになります。
1
NotionBlock.Paragraph
戻り値ムズい問題
Notion APIのレスポンスは結構詳細な情報まで返してくれるのですが、その分ネストがめちゃくちゃ深いです。
また記事ごとに使用ブロックの種類が違うので、レスポンスの型が一意に決まりません。
結構複雑なので、レスポンスの中身は一旦anyで受け付けて、type
を見て扱いたいentityに詰め直すようにしました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { Client } from "@notionhq/client"
import type {
QueryDatabaseResponse,
ListBlockChildrenResponse,
} from '@notionhq/client/build/src/api-endpoints.d'
import type {
NotionPostHead,
NotionTag,
NotionIcon,
} from '../entities/notion_entities';
import * as NotionBlock from '../entities/notion/blocks';
import * as NotionBlockInterfaces from '../interfaces/NotionApiResponses';
export default class Notion {
...
static createBlockList(response: ListBlockChildrenResponse) {
let blocks: Array<NotionBlock.Block> = [];
response.results.map((item: any) => {
let itemType = item.type;
switch (itemType) {
case 'paragraph':
let paragraph: NotionBlockInterfaces.IParagraphBlock = item;
blocks.push(new NotionBlock.Paragraph(paragraph));
break;
case 'heading_1':
let heading1: NotionBlockInterfaces.IHeading1Block = item;
blocks.push(new NotionBlock.Heading1(heading1));
break;
case 'heading_2':
let heading2: NotionBlockInterfaces.IHeading2Block = item;
blocks.push(new NotionBlock.Heading2(heading2));
break;
case 'heading_3':
let heading3: NotionBlockInterfaces.IHeading3Block = item;
blocks.push(new NotionBlock.Heading3(heading3));
break;
case 'callout':
let callout: NotionBlockInterfaces.ICalloutBlock = item;
blocks.push(new NotionBlock.Callout(callout));
break;
case 'code':
let code: NotionBlockInterfaces.ICodeBlock = item;
blocks.push(new NotionBlock.Code(code));
break;
case 'image':
let image: NotionBlockInterfaces.IImageBlock = item;
blocks.push(new NotionBlock.Image(image));
break;
default:
break;
}
})
return blocks;
}
}
こんな感じでメソッドを作って
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Layout from '../../components/layout';
import Block from '../../components/posts/Block';
import { useRouter } from 'next/router';
import Head from 'next/head';
import {v4 as uuidv4} from 'uuid';
import * as NotionBlock from '../../entities/notion/blocks';
import Notion from '../../lib/notions'
import { InferGetStaticPropsType, GetStaticPaths } from 'next'
type Props = InferGetStaticPropsType<typeof getStaticProps>;
export default function Post({postBlockJson}: Props) {
const postBlockList = Notion.createBlockList(postBlockJson);
return (
// ...
)
}
[id].tsx
側で呼んでいます。
paragraphの改行とinlineの両立問題
paragraphについて
めっちゃ頑張った
改行と横並びの両立
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import React from "react"
import { Text, Code } from '@chakra-ui/react'
import type { Paragraph as ParagraphEntity, } from '../../../entities/notion/blocks';
import _ from 'lodash'
type Props = {
enable: boolean,
color: string,
backgroundColor: string,
children?: React.ReactNode
}
export function Paragraph({entity}: {entity: ParagraphEntity}) {
if (entity.texts.length === 0) return <br />
const Bold: React.VFC<Props> = ({enable, color, backgroundColor, children}: Props) => {
if (enable) return <Text as='strong' fontWeight='bold' color={color} backgroundColor={backgroundColor} >{children}</Text>
return <>{children}</>
}
const Italic: React.VFC<Props> = ({enable, color, backgroundColor, children}: Props) => {
if (enable) return <Text as='i' color={color} backgroundColor={backgroundColor} >{children}</Text>
return <>{children}</>
}
const Underline: React.VFC<Props> = ({enable, color, backgroundColor, children}: Props) => {
if (enable) return <Text as='u' color={color} backgroundColor={backgroundColor} >{children}</Text>
return <>{children}</>
}
const Strikethrough: React.VFC<Props> = ({enable, color, backgroundColor, children}: Props) => {
if (enable) return <Text as='s' color={color} backgroundColor={backgroundColor} >{children}</Text>
return <>{children}</>
}
const CodeText: React.VFC<Props> = ({enable, color, backgroundColor, children}: Props) => {
if (enable) return <Code color={color} backgroundColor={backgroundColor} >{children}</Code>
return <>{children}</>
}
const RichText = ({text}: any) => {
if (text.content == null) return <br />
let content = text.content ?? ''
console.log(content)
const color = text.annotations.color + '.500'
const backgroundColor = (text.annotations.color + '.500').split('_background').length > 1 ? (text.annotations.color + '.500').split('_background')[0] : null
return (
<Bold
enable={text.annotations.bold}
color={color}
backgroundColor={backgroundColor}
>
<Italic
enable={text.annotations.italic}
color={color}
backgroundColor={backgroundColor}
>
<Underline
enable={text.annotations.underline}
color={color}
backgroundColor={backgroundColor}
>
<Strikethrough
enable={text.annotations.strikethrough}
color={color}
backgroundColor={backgroundColor}
>
<CodeText
enable={text.annotations.code}
color={color}
backgroundColor={backgroundColor}
>
{content}
</CodeText>
</Strikethrough>
</Underline>
</Italic>
</Bold>
)
}
const getLineEntities = (entity: ParagraphEntity) => {
let lineEntities = []
entity.texts.forEach(text => {
let eachLine = text.content.split('\n')
eachLine.forEach(line => {
let lineEntity = _.cloneDeep(text)
lineEntity.content = line
lineEntities.push(lineEntity)
let endLineEntity = _.cloneDeep(text)
endLineEntity.content = null
lineEntities.push(endLineEntity)
})
lineEntities.pop()
})
return lineEntities
}
return (
<Text>
{getLineEntities(entity).map(text => <RichText text={text} /> )}
</Text>
)
}
childrenはとくべつなprops
jsdomをつかってbuild出来ない問題
canvasが無いから
めちゃくちゃ重かったので入れたくない
1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
target: 'serverless',
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
if (isServer) {
config.plugins.push(new webpack.IgnorePlugin(
{
resourceRegExp: /canvas/,
contextRegExp: /jsdom$/,
}))
}
return config
}
}