Website: markdown to release (#4146)
* website: markdown to release * remove Image.png * fixed font weight * Change folder structure * remove react-markdown --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledLineContainer = styled.div`
|
||||
width: 810px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
width: auto;
|
||||
margin: 24px 0;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledLine = styled.div`
|
||||
height: 1px;
|
||||
background-color: #d9d9d9;
|
||||
margin-bottom: 48px;
|
||||
margin-left: 148px;
|
||||
margin-top: 48px;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Line = () => {
|
||||
return (
|
||||
<StyledLineContainer>
|
||||
<StyledLine />
|
||||
</StyledLineContainer>
|
||||
);
|
||||
};
|
||||
110
packages/twenty-website/src/app/_components/releases/Release.tsx
Normal file
110
packages/twenty-website/src/app/_components/releases/Release.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import { Gabarito } from 'next/font/google';
|
||||
import { compileMDX } from 'next-mdx-remote/rsc';
|
||||
import remarkBehead from 'remark-behead';
|
||||
import gfm from 'remark-gfm';
|
||||
|
||||
import { ReleaseNote } from '@/app/get-releases';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 810px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
font-weight: 400;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
width: auto;
|
||||
margin: 24px 0;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledVersion = styled.div`
|
||||
text-align: center;
|
||||
width: 148px;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: start;
|
||||
font-weight: 500;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledRelease = styled.span`
|
||||
color: #b3b3b3;
|
||||
`;
|
||||
|
||||
const StyledDate = styled.span`
|
||||
color: #474747;
|
||||
font-size: 16px;
|
||||
`;
|
||||
|
||||
const StlyedContent = styled.div`
|
||||
flex: 1;
|
||||
|
||||
h3 {
|
||||
color: #141414;
|
||||
font-size: 40px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #474747;
|
||||
font-size: 16px;
|
||||
line-height: 28.8px;
|
||||
color: #818181;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
const gabarito = Gabarito({
|
||||
weight: ['400', '500'],
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
export const Release = async ({ release }: { release: ReleaseNote }) => {
|
||||
let mdxSource;
|
||||
try {
|
||||
mdxSource = await compileMDX({
|
||||
source: release.content,
|
||||
options: {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [gfm, [remarkBehead, { depth: 2 }]],
|
||||
},
|
||||
},
|
||||
});
|
||||
mdxSource = mdxSource.content;
|
||||
} catch (error) {
|
||||
console.error('An error occurred during MDX rendering:', error);
|
||||
mdxSource = `<p>Oops! Something went wrong.</p> ${error}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer className={gabarito.className}>
|
||||
<StyledVersion>
|
||||
<StyledRelease>{release.release}</StyledRelease>
|
||||
<StyledDate>
|
||||
{release.date.endsWith(new Date().getFullYear().toString())
|
||||
? release.date.slice(0, -5)
|
||||
: release.date}
|
||||
</StyledDate>
|
||||
</StyledVersion>
|
||||
|
||||
<StlyedContent>{mdxSource}</StlyedContent>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
margin: 64px auto;
|
||||
text-align: center;
|
||||
font-size: 1.8em;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 1em;
|
||||
margin: 16px auto;
|
||||
}
|
||||
`;
|
||||
const StyledHeader = styled.h1`
|
||||
color: #b3b3b3;
|
||||
margin: 0;
|
||||
`;
|
||||
const StyledSubHeader = styled.h1`
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
export const Title = () => {
|
||||
return (
|
||||
<StyledTitle>
|
||||
<StyledHeader>Latest</StyledHeader>
|
||||
<StyledSubHeader>Releases</StyledSubHeader>
|
||||
</StyledTitle>
|
||||
);
|
||||
};
|
||||
49
packages/twenty-website/src/app/get-releases.tsx
Normal file
49
packages/twenty-website/src/app/get-releases.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import fs from 'fs';
|
||||
import matter from 'gray-matter';
|
||||
|
||||
export interface ReleaseNote {
|
||||
slug: string;
|
||||
date: string;
|
||||
release: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
function compareSemanticVersions(a: string, b: string) {
|
||||
const a1 = a.split('.');
|
||||
const b1 = b.split('.');
|
||||
|
||||
const len = Math.min(a1.length, b1.length);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const a2 = +a1[i] || 0;
|
||||
const b2 = +b1[i] || 0;
|
||||
|
||||
if (a2 !== b2) {
|
||||
return a2 > b2 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return b1.length - a1.length;
|
||||
}
|
||||
|
||||
export async function getReleases(): Promise<ReleaseNote[]> {
|
||||
const files = fs.readdirSync('src/content/releases');
|
||||
const releasenotes: ReleaseNote[] = [];
|
||||
|
||||
for (const fileName of files) {
|
||||
if (!fileName.endsWith('.md') && !fileName.endsWith('.mdx')) {
|
||||
continue;
|
||||
}
|
||||
const file = fs.readFileSync(`src/content/releases/${fileName}`, 'utf-8');
|
||||
const { data: frontmatter, content } = matter(file);
|
||||
releasenotes.push({
|
||||
slug: fileName.slice(0, -4),
|
||||
date: frontmatter.Date,
|
||||
release: frontmatter.release,
|
||||
content: content,
|
||||
});
|
||||
}
|
||||
|
||||
releasenotes.sort((a, b) => compareSemanticVersions(b.release, a.release));
|
||||
|
||||
return releasenotes;
|
||||
}
|
||||
32
packages/twenty-website/src/app/releases/page.tsx
Normal file
32
packages/twenty-website/src/app/releases/page.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
import { Line } from '@/app/_components/releases/Line';
|
||||
import { Release } from '@/app/_components/releases/Release';
|
||||
import { Title } from '@/app/_components/releases/StyledTitle';
|
||||
import { ContentContainer } from '@/app/_components/ui/layout/ContentContainer';
|
||||
import { getReleases } from '@/app/get-releases';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Twenty - Releases',
|
||||
description: 'Latest releases of Twenty',
|
||||
};
|
||||
|
||||
const Home = async () => {
|
||||
const releases = await getReleases();
|
||||
|
||||
return (
|
||||
<ContentContainer>
|
||||
<Title />
|
||||
|
||||
{releases.map((note, index) => (
|
||||
<React.Fragment key={note.slug}>
|
||||
<Release release={note} />
|
||||
{index != releases.length - 1 && <Line />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ContentContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
Reference in New Issue
Block a user