使用Notion作为您的CMS
在本文中,我将向您展示如何将此平台与Notion集成,以将其用作我在Dealo上的文章的CMS。Notion在组织和撰写内容方面是一个很好的工具。
所有这里展示的代码都将使用Next.js和Notion JS客户端。
创建您的Notion集成
这个标题可能听起来有些令人困惑,但这就是Notion称呼它的方式。一个Notion集成是访问您的内容的门户,它将为您提供连接所需的API密钥。用他们自己的话来说:
“集成定义了公共API如何与您的Notion工作区进行程序化交互。它们需要被授权(即,获得明确的许可)才能对您的工作区进行任何更改。”
因此,您的第一步是创建一个集成。您可以阅读Li 现在让我们来玩一下 Notion。首先,您需要一种方法来组织您的文章。我建议使用表格
模板,这将允许您为文章添加属性,如状态、类别、关键词、标语(简短描述)
等,然后您可以用于过滤、元数据等。Notion 结构的最大优点之一是,您仍然可以为任何行写一篇完整的文章。
创建表格后,您需要连接刚刚创建的集成,方法如下: https://developers.notion.com/docs/create-a-notion-integration# (opens new window)。对于这种用例,集成将是内部
的,只需要读取权限。根据用户权限,我在本文中不会使用任何与用户相关的信息。如果您想要显示文章的作者,您将需要访问它。 创建页面
现在进入编码部分。 第一步是创建列出所有文章的根页面。
这是我们将使用的文件夹结构(我正在使用应用程序路由器):
app / └── blog / ├── [slug] ├── layout.tsx └── page.tsx
正如您所看到的,这是一个非常简单的结构。 如果您想要添加层次结构,比如按类别分组等,结构仍然会类似,尽管层次更多。
我不会详细介绍layout.tsx
组件的细节,因为页面的外观取决于您。 我们将首先关注page.tsx
。
由于这是关于博客的内容,页面不需要是动态的,将在构建时生成。 这将有助于 SEO 和页面加载时间。
现在让我们看一些代码。 这是根页面的代码:
import { Client } from '@notionhq/client';
const NOTION_API_KEY = 'your api key'; const NOTION_BLOG_DATABASE = 'the table id';
const notion = new Client({ auth: NOTION_API_KEY });
export default async function RootBlogPage() { const blogs = ( await notion.databases.query({ database_id: env.NOTION_BLOG_DATABASE, filter: { property: 'status', status: { equals: 'Done' } }, sorts: [ { property: 'created_at', direction: 'ascending', }, ], }) ).results as DatabaseObjectResponse[]; //...
这是如何从表中列出文章。注意过滤和排序;这是我提到使用表方法的好处之一,因为页面有更多属性可供使用。
要获取NOTION_BLOG_DATABASE
id,您可以从表中复制可共享链接并从中获取。链接看起来像这样:
https://www.notion.so/467bef11d2bf4877928f9be8447104a3?v=77da2764f80c46d590017de202c226a4&pv 下面,您将以您喜欢的任何布局显示文章。我使用了一个简单的两列网格和带有最少信息的卡片。
```jsx
<main className="max-w-[980px] mx-auto">
<div className="grid grid-cols-2 gap-24">
{blogs.map((blog: any) => (
<Link key={blog.id} href={`/blog/${(buildSlug(blog))}`}>
<div
key={blog.id}
className={cn(
'flex flex-col p-6 h-full rounded border border-neutral-200 dark:border-slate-800',
'hover:border-emerald-600 dark:hover:border-emerald-500 transition-border duration-200',
)}
>
<h2 className="text-2xl">{blog.properties.Name?.title[0].plain_text}</h2>
<div className="flex flex-row gap-2 mt-1">
{blog.properties.keywords.multi_select.map((tag: any) => (
<Code key={tag.id}>{tag.name}</Code>
))}
</div>
</div>
</Link>
))}
</div>
</main>
``` ```jsx
<main>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{blogs.map((blog) => (
<Link href={`/blog/${buildSlug(blog)}`} key={blog.id}>
<div className="border border-gray-200 p-4 rounded-lg">
<h2 className="text-lg font-semibold">{blog.properties.Name.title[0].plain_text}</h2>
<span className="mt-2 mb-4">{formatDate(blog.properties.created_at.created_time)}</span>
<p className="text-neutral-600 dark:text-neutral-300">{blog.properties.tagline.rich_text[0].plain_text}</p>
</div>
</Link>
))}
</div>
</main>
构建 buildSlug
函数我用来规范化链接的格式。您希望有人类可读的链接,但同时也需要文章的ID,以便实现自我修复的URL。自我修复的URL通过修复损坏的链接并保持页面与内容的最新状态来帮助SEO,例如:如果标题更改了。您可以在这篇文章 (opens new window)中阅读更多。
这是函数:
export function buildSlug(blog: any) {
const slug: string = blog.properties.Name?.title[0].plain_text.toLowerCase().replace(/ /g, '-');
const cleanId = blog.id.replace(/-/g, '');
return `${slug}-${cleanId}`;
}
渲染文章 #
现在让我们渲染文章。 让我们看看将呈现每篇文章的页面。这是一个更复杂的页面,因为它需要处理将Notion块转换为HTML标签,以及添加元数据。
这是处理元数据的函数。它需要是一个函数,而不是一个简单的const
导出,因为它需要获取每篇文章的特定数据。这是一个简单的实现,仍然缺少关键的元数据,如开放图形和Twitter相关标签。
export async function generateMetadata(props: Props): Promise<Metadata> {
const { params: { slug } } = props;
const articleId = slug.split('-').pop();
if (!articleId) {
return {};
}
const blog: any = await notion.pages.retrieve({
page_id: articleId,
});
const title = blog.properties.Name.title[0].plain_text;
return {
title,
description: blog.properties.tagline.rich_text[0].plain_text,
keywords: blog.properties.keywords.multi_select.map((tag: any) => tag.name),
};
}
接下来是generateStaticParams
。这个将 ```javascript
export async function 生成静态参数() {
const 博客文章 = (
await notion.databases.query({
database_id: env.NOTION_BLOG_DATABASE,
filter: {
property: 'status',
status: {
equals: 'Done'
}
},
sorts: [
{
property: 'created_at',
direction: 'ascending',
},
],
})
).results as DatabaseObjectResponse[];
return 博客文章.map((博客) => ({ slug: 构建Slug(博客), })) }
现在,要渲染实际文章,我们将创建一个页面,查询Notion页面信息以及构成文章本身的块。
```javascript
export default async function 博客详情页面(props: Props) {
const { params: { slug } } = props;
const 文章Id = slug.split('-').pop();
if (!文章Id) {
return notFound();
}
co
``` const page = await notion.pages.retrieve({
page_id: articleId,
});
const blocks = [];
let cursor = undefined;
do {
const response = await notion.blocks.children.list({
block_id: articleId,
start_cursor: cursor,
});
blocks.push(...response.results);
cursor = response.next_cursor;
} while (cursor);
const elements = await processNotionBlocks(blocks as BlockObjectResponse[]);
return (
<main className="max-w-[780px] mx-auto">
<div className="mb-24">
<Link href="/blog">
<Button variant="outline">
<IconArrowLeft size={14} className="mr-2"/>
返回文章列表
</Button>
</Link>
</div>
<h1 className="text-5xl mb-6 mt-4">
{page.properties.Name.title[0].plain_text}
</h1>
<div className="flex flex-row gap-2 mb-3">
{page.properties.keywords.multi_select.map((tag: any) => (
<Code key={tag.id}>{tag.name}</Code>
))}
</div> ```javascript
async function processNotionBlocks(blocks: BlockObjectResponse[]) {
const elements = [];
for (let i = 0; i < blocks.length; i++) {
const block = blocks[i] as BlockObjectResponse;
if (block.type === 'heading_1') {
elements.push(
<h1 key={block.id} className="text-4xl mt-8 mb-8">
{block.heading_1.rich_text.map((text) => text.plain_text).join('')}
</h1>
);
}
if (block.type === 'heading_2') {
elements.push(
<h2 key={block.id} className="text-3xl mt-8 mb-8">
{block.heading_2.rich_text.map((text) => text.plain_text).join('')}
</h2>
);
}
if (block.type === 'paragraph') {
elements.push(
<p key={block.id} className="mb-4">
{block.paragraph.text.map((text) => text.plain_text).join('')}
</p>
);
}
if (block.type === 'bulleted_list_item') {
elements.push(
<li key={block.id} className="list-disc ml-4">
{block.bulleted_list_item.text.map((text) => text.plain_text).join('')}
</li>
);
}
if (block.type === 'numbered_list_item') {
elements.push(
<li key={block.id} className="list-decimal ml-4">
{block.numbered_list_item.text.map((text) => text.plain_text).join('')}
</li>
);
}
if (block.type === 'to_do') {
elements.push(
<div key={block.id} className="flex items-center mb-2">
<input type="checkbox" className="mr-2" defaultChecked={block.to_do.checked} />
<p className={block.to_do.checked ? 'line-through mb-4' : 'mb-4'}>
{block.to_do.text.map((text) => text.plain_text).join('')}
</p>
</div>
);
}
if (block.type === 'image') {
elements.push(
<img key={block.id} src={block.image.file.url} alt={block.image.caption[0].plain_text} className="w-full mt-4 mb-4 object-cover" />
);
}
if (block.type === 'divider') {
elements.push(<hr key={block.id} className="border-t border-gray-300 my-4" />);
}
}
return elements;
}
``` ```javascript
if (block.type === 'heading_3') {
elements.push(
<h3 key={block.id} className="text-2xl mt-4 mb-4">
{block.heading_3.rich_text.map((text) => text.plain_text).join('')}
</h3>
);
}
if (block.type === 'paragraph') {
if (block.paragraph.rich_text.length === 0) {
elements.push(<br key={block.id}/>);
continue;
}
elements.push(
<p key={block.id} className="mb-2">
{block.paragraph.rich_text.map((text, index) => {
if (text.annotations.code) {
return (
<code
key={`${block.id}-${index}`}
className={cn(
'bg-emerald-200 dark:bg-emerald-800 rounded-sm px-1',
{
'font-bold': text.annotations.bold,
italic: text.annotations.italic,
'line-through': text.annotations.strikethrough,
underline: text.annotations.underline,
``` 以前,我会在工作时经常陷入混乱,感觉无法控制自己的时间和任务。但是,通过学习时间管理技巧和使用工具,我现在能更好地组织自己的工作和生活。下面是我分享的一些时间管理技巧:
1. 制定计划:每天清晨或工作开始前,列出当天需要完成的任务和目标。将它们按重要性和紧急程度排序,然后制定一个计划,逐步完成任务。
2. 使用时间管理工具:利用时间管理工具,如番茄工作法、日程表或任务管理应用程序,帮助你更好地管理时间和任务。
3. 集中注意力:避免分心和多任务处理,集中注意力完成一项任务,然后再处理下一项任务。
4. 设定目标:设定短期和长期目标,为自己制定计划和时间表,以实现这些目标。
5. 学会拒绝:学会拒绝那些会浪费你时间的事情,保持专注于重要任务。
6. 休息调整:合理安排工作和休息时间,保持身心健康,提高工作效率。
7. 反思总结:每天结束前,反思一天的工作,总结完成的任务和需要改进的地方,为明天的工作做准备。
通过这些时间管理技巧,我现在能更高效地工作和生活,实现自己的目标和梦想。希望这些技巧也能帮助你更好地管理时间和任务,提高工作效率。 如果block.type是'quote',则执行以下代码:
const quoteChildren = await notion.blocks.children.list({
block_id: block.id,
});
const quoteElements = await processNotionBlocks(quoteChildren.results as BlockObjectResponse[]);
elements.push(
<blockquote key={block.id} className="my-6 pl-4 border-l-4 border-emerald-500 dark:border-emerald-700">
{block.quote.rich_text.map((text) => text.plain_text).join('')}
{quoteElements}
</blockquote>
);
如果block.type是'code',则执行以下代码:
elements.push(
<pre key={block.id} data-el="code-block" className="group p-6 my-6 rounded bg-neutral-50 dark:bg-slate-900 overflow-x-auto">
<code className="text-sm font-mono py-[1px] px-1 rounded-sm bg-emerald-200 dark:bg-emerald-800 group-data-[el=code-block]:bg-neutral-50 group-data-[el=code-block]:dark:bg-slate-900">
{block.code.rich_text.map((text) => text.plain_text).join('')}
</code>
</pre>
); 如果块的类型是'paragraph',则将该块添加到元素数组中。
如果块的类型是'bulleted_list_item',则将该块及其后续相同类型的块添加到元素数组中。
如果块的类型是'numbered_list_item',则将该块及其后续相同类型的块添加到元素数组中。
将元素数组渲染为相应的HTML元素并返回。 将以下的Markdown翻译成中文,并删除第一级标题,同时删除图片链接,尽量消除Markdown格式错误和一些无用段落,重新装饰整篇文章,使之读起来更加自然:
imal list-outside pl-6 mb-2">
{bulletItems.map((item) => (
<li key={item.id}>
{item.numbered_list_item.rich_text.map((text) => text.plain_text).join('')}
</li>
))}
</ol>
);
}
}
return elements;
}
你可以(应该)完善这个函数,使其更加简单,因为可能会有更多的块类型需要支持,你可以看到它可能会变得难以控制,但作为初始步骤,这样做是可以的。
结语 #
这就是如何将Notion设置为你的内容管理系统。值得一提的是,你可能已经注意到,我没有提供任何API来接收Notion中新文章创建或更新时的通知。这是因为他们没有webhooks API。
这意味着只有在项目中进行构建时,你的文章才会被添加或更新。希望这不会成为一个严重问题,但值得注意。
感谢您一直坚持到最后。
祝好,编程愉快。