-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
af01439
commit 6fe4e28
Showing
25 changed files
with
862 additions
and
184 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
--- | ||
title: 'Contribute to the blog' | ||
sidebarTitle: 'Blog' | ||
icon: 'newspaper' | ||
--- | ||
|
||
The [official Typebot blog](https://typebot.io/blog) is a place where we share any ideas, original content related to the chatbot industry, and Typebot itself. | ||
|
||
You are free to contribute to the blog or fix any typos you may find. | ||
|
||
1. Head over to the [content folder](https://github.com/baptisteArno/typebot.io/tree/main/apps/landing-page/content) on the Github repo. | ||
2. Click on the blog post file you want to edit. Or create a new file by clicking on the `Add file` button. | ||
3. If you did not already have a fork of the repository, you will be prompted to create one. | ||
4. Once you're happy with your changes, hit `Commit changes...`. | ||
5. Click on `Create pull request`. | ||
6. Add a title and a description to describe your changes. | ||
7. Click on `Create pull request`. | ||
|
||
It will be reviewed and merged if approved! | ||
|
||
## New article guidelines | ||
|
||
- The article should be related to chatbots, or Typebot. | ||
- The article should be written in English. | ||
- The article should be original content. No plagiarism. | ||
- The article should not be 100% AI-generated. | ||
|
||
The mdx file should always start with the following frontmatter: | ||
|
||
```md | ||
--- | ||
title: 'My awesome blog post' | ||
publishedAt: '2023-11-19' | ||
summary: 'A short summary of the blog post.' | ||
--- | ||
``` | ||
|
||
By default the og image is generated from the title of the blog post. If you want to use a custom og image, you can specify a `image` field in the frontmatter. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
'use client' | ||
import { Heading, Stack, Text } from '@chakra-ui/react' | ||
import { Link } from '@chakra-ui/next-js' | ||
|
||
type Props = { | ||
allBlogs: { | ||
metadata: { | ||
title: string | ||
publishedAt: string | ||
} | ||
slug: string | ||
}[] | ||
} | ||
|
||
export const Posts = ({ allBlogs }: Props) => ( | ||
<Stack | ||
spacing={10} | ||
mx="auto" | ||
maxW="3xl" | ||
my="20" | ||
fontSize="17px" | ||
textAlign="justify" | ||
> | ||
<Heading>Latest blog posts:</Heading> | ||
<Stack> | ||
{allBlogs | ||
.filter((post) => post.metadata.publishedAt) | ||
.sort((a, b) => { | ||
if ( | ||
new Date(a.metadata.publishedAt) > new Date(b.metadata.publishedAt) | ||
) { | ||
return -1 | ||
} | ||
return 1 | ||
}) | ||
.map((post) => ( | ||
<Link key={post.slug} href={`/blog/${post.slug}`}> | ||
<Stack | ||
w="full" | ||
rounded="md" | ||
borderColor="gray.600" | ||
borderWidth={1} | ||
p="4" | ||
> | ||
<Heading as="h2" fontSize="2xl"> | ||
{post.metadata.title} | ||
</Heading> | ||
<Text color="gray.500"> | ||
{new Date(post.metadata.publishedAt).toDateString()} | ||
</Text> | ||
</Stack> | ||
</Link> | ||
))} | ||
</Stack> | ||
</Stack> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
'use client' | ||
|
||
import { Link } from '@chakra-ui/next-js' | ||
import { Heading, Stack, Text } from '@chakra-ui/react' | ||
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote' | ||
import { highlight } from 'sugar-high' | ||
|
||
type Props = { | ||
metadata: { | ||
title: string | ||
publishedAt: string | ||
} | ||
mdxSource: MDXRemoteSerializeResult | ||
} | ||
|
||
export const Post = ({ metadata, mdxSource }: Props) => ( | ||
<Stack spacing={10} my="20"> | ||
<Stack mx="auto" w="full" maxW="65ch"> | ||
<Heading>{metadata.title}</Heading> | ||
<Text>{formatDate(metadata.publishedAt)}</Text> | ||
</Stack> | ||
<Stack | ||
mx="auto" | ||
spacing={0} | ||
as="article" | ||
className="prose prose-quoteless prose-neutral prose-invert" | ||
> | ||
<MDXRemote | ||
{...mdxSource} | ||
components={{ | ||
h1: (props) => <Heading as="h1" {...props} />, | ||
h2: (props) => <Heading as="h2" {...props} />, | ||
h3: (props) => <Heading as="h3" {...props} />, | ||
h4: (props) => <Heading as="h4" {...props} />, | ||
h5: (props) => <Heading as="h5" {...props} />, | ||
h6: (props) => <Heading as="h6" {...props} />, | ||
code: ({ children, ...props }) => { | ||
const codeHTML = highlight(children?.toString() ?? '') | ||
return ( | ||
<code dangerouslySetInnerHTML={{ __html: codeHTML }} {...props} /> | ||
) | ||
}, | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
link: (props: any) => <Link {...props} />, | ||
}} | ||
/> | ||
</Stack> | ||
</Stack> | ||
) | ||
|
||
function formatDate(date: string) { | ||
const currentDate = new Date().getTime() | ||
if (!date.includes('T')) { | ||
date = `${date}T00:00:00` | ||
} | ||
const targetDate = new Date(date).getTime() | ||
const timeDifference = Math.abs(currentDate - targetDate) | ||
const daysAgo = Math.floor(timeDifference / (1000 * 60 * 60 * 24)) | ||
|
||
const fullDate = new Date(date).toLocaleString('en-us', { | ||
month: 'long', | ||
day: 'numeric', | ||
year: 'numeric', | ||
}) | ||
|
||
if (daysAgo < 1) { | ||
return 'Today' | ||
} else if (daysAgo < 7) { | ||
return `${fullDate} (${daysAgo}d ago)` | ||
} else if (daysAgo < 30) { | ||
const weeksAgo = Math.floor(daysAgo / 7) | ||
return `${fullDate} (${weeksAgo}w ago)` | ||
} else if (daysAgo < 365) { | ||
const monthsAgo = Math.floor(daysAgo / 30) | ||
return `${fullDate} (${monthsAgo}mo ago)` | ||
} else { | ||
const yearsAgo = Math.floor(daysAgo / 365) | ||
return `${fullDate} (${yearsAgo}y ago)` | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import type { Metadata } from 'next' | ||
import { notFound } from 'next/navigation' | ||
import { getBlogPosts } from '@/app/db/blog' | ||
import { Post } from './Post' | ||
import { serialize } from 'next-mdx-remote/serialize' | ||
import '@/assets/prose.css' | ||
import { env } from '@typebot.io/env' | ||
|
||
export async function generateMetadata({ | ||
params, | ||
}: { | ||
params: { slug: string } | ||
}): Promise<Metadata | undefined> { | ||
const post = getBlogPosts().find( | ||
(post) => post.slug === params.slug && post.metadata.publishedAt | ||
) | ||
if (!post) { | ||
return | ||
} | ||
|
||
const { | ||
title, | ||
publishedAt: publishedTime, | ||
summary: description, | ||
image, | ||
} = post.metadata | ||
const ogImage = image | ||
? `${env.LANDING_PAGE_URL}${image}` | ||
: `${env.LANDING_PAGE_URL}/og?title=${title}` | ||
|
||
return { | ||
title, | ||
description, | ||
openGraph: { | ||
title, | ||
description, | ||
type: 'article', | ||
publishedTime, | ||
url: `${env.LANDING_PAGE_URL}/blog/${post.slug}`, | ||
images: [ | ||
{ | ||
url: ogImage, | ||
}, | ||
], | ||
}, | ||
twitter: { | ||
card: 'summary_large_image', | ||
title, | ||
description, | ||
images: [ogImage], | ||
}, | ||
} | ||
} | ||
|
||
export default async function Blog({ params }: { params: { slug: string } }) { | ||
const post = getBlogPosts().find( | ||
(post) => post.slug === params.slug && post.metadata.publishedAt | ||
) | ||
|
||
if (!post) { | ||
notFound() | ||
} | ||
|
||
const mdxSource = await serialize(post.content) | ||
|
||
return <Post metadata={post.metadata} mdxSource={mdxSource} /> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { getBlogPosts } from '@/app/db/blog' | ||
import { Posts } from './Posts' | ||
|
||
export const metadata = { | ||
title: 'Blog', | ||
description: 'Read my thoughts on software development, design, and more.', | ||
} | ||
|
||
export default function Home() { | ||
const allBlogs = getBlogPosts() | ||
|
||
return <Posts allBlogs={allBlogs} /> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import fs from 'fs' | ||
import path from 'path' | ||
|
||
type Metadata = { | ||
title: string | ||
publishedAt: string | ||
summary: string | ||
image?: string | ||
} | ||
|
||
function parseFrontmatter(fileContent: string) { | ||
const frontmatterRegex = /---\s*([\s\S]*?)\s*---/ | ||
const match = frontmatterRegex.exec(fileContent) | ||
const frontMatterBlock = match![1] | ||
const content = fileContent.replace(frontmatterRegex, '').trim() | ||
const frontMatterLines = frontMatterBlock.trim().split('\n') | ||
const metadata: Partial<Metadata> = {} | ||
|
||
frontMatterLines.forEach((line) => { | ||
const [key, ...valueArr] = line.split(': ') | ||
let value = valueArr.join(': ').trim() | ||
value = value.replace(/^['"](.*)['"]$/, '$1') // Remove quotes | ||
metadata[key.trim() as keyof Metadata] = value | ||
}) | ||
|
||
return { metadata: metadata as Metadata, content } | ||
} | ||
|
||
function getMDXFiles(dir: fs.PathLike) { | ||
return fs.readdirSync(dir).filter((file) => path.extname(file) === '.mdx') | ||
} | ||
|
||
function readMDXFile(filePath: fs.PathOrFileDescriptor) { | ||
const rawContent = fs.readFileSync(filePath, 'utf-8') | ||
return parseFrontmatter(rawContent) | ||
} | ||
|
||
function extractTweetIds(content: string) { | ||
const tweetMatches = content.match(/<StaticTweet\sid="[0-9]+"\s\/>/g) | ||
return tweetMatches?.map((tweet) => tweet.match(/[0-9]+/g)?.[0]) || [] | ||
} | ||
|
||
function getMDXData(dir: string) { | ||
const mdxFiles = getMDXFiles(dir) | ||
return mdxFiles.map((file) => { | ||
const { metadata, content } = readMDXFile(path.join(dir, file)) | ||
const slug = path.basename(file, path.extname(file)) | ||
const tweetIds = extractTweetIds(content) | ||
return { | ||
metadata, | ||
slug, | ||
tweetIds, | ||
content, | ||
} | ||
}) | ||
} | ||
|
||
export function getBlogPosts() { | ||
return getMDXData(path.join(process.cwd(), 'content')) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* eslint-disable @next/next/no-sync-scripts */ | ||
/* eslint-disable @next/next/no-page-custom-font */ | ||
import type { Metadata } from 'next' | ||
import { Header } from 'components/common/Header/Header' | ||
import { Footer } from 'components/common/Footer' | ||
import { Providers } from './providers' | ||
import { EndCta } from '@/components/Homepage/EndCta' | ||
import 'assets/style.css' | ||
|
||
export const metadata: Metadata = { | ||
title: 'Typebot - Open-source conversational apps builder', | ||
description: | ||
'Powerful blocks to create unique chat experiences. Embed them anywhere on your apps and start collecting results like magic.', | ||
} | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: Readonly<{ | ||
children: React.ReactNode | ||
}>) { | ||
return ( | ||
<html lang="en"> | ||
<head> | ||
<link rel="icon" type="image/png" href="/favicon.png" /> | ||
<link | ||
href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Open+Sans:wght@400;500;600;700&family=Indie+Flower:wght@400&display=swap" | ||
rel="stylesheet" | ||
/> | ||
<script src="/__ENV.js" /> | ||
</head> | ||
|
||
<body style={{ backgroundColor: '#171923' }}> | ||
<Providers> | ||
<Header /> | ||
{children} | ||
<EndCta /> | ||
<Footer /> | ||
</Providers> | ||
</body> | ||
</html> | ||
) | ||
} |
Oops, something went wrong.