Docs modifications (#5804)
- Fixes #5504 - Fixes #5503 - Return 404 when the page does not exist - Modified the footer in order to align it properly - Removed "noticed something to change" in each table of content - Fixed the URLs of the edit module - Added the edit module to Developers - Fixed header style on the REST API page. - Edited the README to point to Developers - Fixed selected state when clicking on sidebar elements --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
import MotionContainer from '@/app/_components/ui/layout/LoaderAnimation';
|
||||
@ -11,6 +10,15 @@ export interface User {
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
const StyledImg = styled.img`
|
||||
width: 100%; // Adjust width as needed
|
||||
height: 100%; // Adjust height as needed
|
||||
object-fit: cover; // Keeps the aspect ratio and covers the area
|
||||
position: absolute; // Similar to layout="fill" in Next Image
|
||||
top: 0;
|
||||
left: 0;
|
||||
`;
|
||||
|
||||
const AvatarGridContainer = styled.div`
|
||||
margin: 0 auto;
|
||||
max-width: 1024px;
|
||||
@ -73,12 +81,7 @@ const AvatarGrid = ({ users }: { users: User[] }) => {
|
||||
prefetch={false}
|
||||
>
|
||||
<AvatarItem key={user.id}>
|
||||
<Image
|
||||
src={user.avatarUrl}
|
||||
alt={user.id}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
/>
|
||||
<StyledImg src={user.avatarUrl} alt={user.id} />
|
||||
<span className="username">{user.id}</span>
|
||||
</AvatarItem>
|
||||
</Link>
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import { format } from 'date-fns';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { GithubIcon } from '@/app/_components/ui/icons/SvgIcons';
|
||||
|
||||
@ -90,7 +89,7 @@ export const ProfileCard = ({
|
||||
return (
|
||||
<ProfileContainer>
|
||||
<Avatar>
|
||||
<Image src={avatarUrl} alt={username} width={100} height={100} />
|
||||
<img src={avatarUrl} alt={username} width={100} height={100} />
|
||||
</Avatar>
|
||||
<Details>
|
||||
<h3 className="username">
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
type ClientOnlyProps = { children: any };
|
||||
|
||||
const ClientOnly = (props: ClientOnlyProps) => {
|
||||
const { children } = props;
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default dynamic(() => Promise.resolve(ClientOnly), {
|
||||
ssr: false,
|
||||
});
|
||||
@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import Image from 'next/image';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { ArticleContent } from '@/app/_components/ui/layout/articles/ArticleContent';
|
||||
@ -15,7 +16,6 @@ const StyledContainer = styled('div')`
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
borderBottom: `1px solid ${Theme.background.transparent.medium}`,
|
||||
fontFamily: `${Theme.font.family}`,
|
||||
})};
|
||||
width: 100%;
|
||||
@ -37,8 +37,9 @@ const StyledWrapper = styled.div`
|
||||
width: 440px;
|
||||
}
|
||||
|
||||
@media (min-width: 801px) {
|
||||
@media (min-width: 801px) and (max-width: 1500px) {
|
||||
max-width: 720px;
|
||||
min-width: calc(100% - 184px);
|
||||
margin: ${Theme.spacing(10)} 92px ${Theme.spacing(20)};
|
||||
}
|
||||
|
||||
@ -96,28 +97,30 @@ const StyledRectangle = styled.div`
|
||||
background: ${Theme.background.transparent.medium};
|
||||
`;
|
||||
|
||||
const StyledImageContainer = styled.div`
|
||||
border: 2px solid ${Theme.text.color.primary};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
const StyledImageContainer = styled.div<{ loaded: string }>`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 46%;
|
||||
overflow: hidden;
|
||||
border-radius: 16px;
|
||||
max-width: fit-content;
|
||||
border: 2px solid rgba(20, 20, 20, 0.08);
|
||||
background: #fafafa;
|
||||
transition: border-color 150ms ease-in-out;
|
||||
${({ loaded }) =>
|
||||
loaded === 'true' &&
|
||||
`border-color: ${Theme.text.color.primary};
|
||||
`}
|
||||
`;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
@media (min-width: 1000px) {
|
||||
width: 720px;
|
||||
}
|
||||
}
|
||||
const StyledImage = styled(Image)<{ loaded: string }>`
|
||||
opacity: ${({ loaded }) => (loaded === 'true' ? 1 : 0)};
|
||||
transition: opacity 250ms ease-in-out;
|
||||
`;
|
||||
|
||||
export default function DocsContent({ item }: { item: FileContent }) {
|
||||
const pathname = usePathname();
|
||||
const { uri, label } = getUriAndLabel(pathname);
|
||||
const [imageLoaded, setImageLoaded] = useState(false);
|
||||
|
||||
const BREADCRUMB_ITEMS = [
|
||||
{
|
||||
@ -136,12 +139,16 @@ export default function DocsContent({ item }: { item: FileContent }) {
|
||||
separator="/"
|
||||
/>
|
||||
<StyledHeading>{item.itemInfo.title}</StyledHeading>
|
||||
<StyledImageContainer>
|
||||
<StyledImageContainer loaded={imageLoaded.toString()}>
|
||||
{item.itemInfo.image && (
|
||||
<img
|
||||
<StyledImage
|
||||
id={`img-${item.itemInfo.title}`}
|
||||
src={item.itemInfo.image}
|
||||
alt={item.itemInfo.title}
|
||||
fill
|
||||
style={{ objectFit: 'cover' }}
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
loaded={imageLoaded.toString()}
|
||||
/>
|
||||
)}
|
||||
</StyledImageContainer>
|
||||
|
||||
@ -19,7 +19,6 @@ const StyledContainer = styled.div`
|
||||
flexDirection: 'column',
|
||||
background: `${Theme.background.secondary}`,
|
||||
borderRight: `1px solid ${Theme.background.transparent.medium}`,
|
||||
borderBottom: `1px solid ${Theme.background.transparent.medium}`,
|
||||
padding: `${Theme.spacing(10)} ${Theme.spacing(4)}`,
|
||||
gap: `${Theme.spacing(6)}`,
|
||||
})}
|
||||
|
||||
@ -120,7 +120,7 @@ const DocsSidebarSection = ({
|
||||
? '/user-guide/'
|
||||
: pathname.includes('developers')
|
||||
? '/developers/'
|
||||
: '/twenty-ui';
|
||||
: '/twenty-ui/';
|
||||
|
||||
const initializeUnfoldedState = () => {
|
||||
const unfoldedState: TopicsState = {};
|
||||
@ -171,11 +171,11 @@ const DocsSidebarSection = ({
|
||||
|
||||
{(unfolded[topic] || !hasMultipleFiles) &&
|
||||
cards.map((card) => {
|
||||
const isselected = pathname === `${path}${card.fileName}`;
|
||||
const sectionName = card.topic
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-');
|
||||
const routerPath = getCardPath(card, path, false, sectionName);
|
||||
const isselected = pathname === routerPath;
|
||||
return (
|
||||
<StyledSubTopicItem
|
||||
key={card.title}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
'use client';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import ClientOnly from '@/app/_components/docs/ClientOnly';
|
||||
import mq from '@/app/_components/ui/theme/mq';
|
||||
import { Theme } from '@/app/_components/ui/theme/theme';
|
||||
import { useHeadsObserver } from '@/app/user-guide/hooks/useHeadsObserver';
|
||||
@ -13,7 +15,6 @@ const StyledContainer = styled.div`
|
||||
flexDirection: 'column',
|
||||
background: `${Theme.background.secondary}`,
|
||||
borderLeft: `1px solid ${Theme.background.transparent.medium}`,
|
||||
borderBottom: `1px solid ${Theme.background.transparent.medium}`,
|
||||
padding: `0px ${Theme.spacing(6)}`,
|
||||
})};
|
||||
width: 300px;
|
||||
@ -96,7 +97,8 @@ const DocsTableContents = () => {
|
||||
useEffect(() => {
|
||||
const nodes: HTMLElement[] = Array.from(
|
||||
document.querySelectorAll('h2, h3, h4, h5'),
|
||||
);
|
||||
).filter((elem) => (elem as HTMLElement).id !== 'edit') as HTMLElement[];
|
||||
|
||||
const elements: HeadingType[] = nodes.map(
|
||||
(elem): HeadingType => ({
|
||||
id: elem.id,
|
||||
@ -106,41 +108,52 @@ const DocsTableContents = () => {
|
||||
level: Number(elem.nodeName.charAt(1)),
|
||||
}),
|
||||
);
|
||||
|
||||
setHeadings(elements);
|
||||
}, [pathname]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledNav>
|
||||
<StyledHeadingText>Table of Content</StyledHeadingText>
|
||||
<StyledUnorderedList>
|
||||
{headings.map((heading) => (
|
||||
<StyledList
|
||||
key={heading.text}
|
||||
style={getStyledHeading(heading.level)}
|
||||
>
|
||||
<StyledLink
|
||||
href={`#${heading.text}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
const yOffset = -70;
|
||||
const y =
|
||||
heading.elem.getBoundingClientRect().top +
|
||||
window.scrollY +
|
||||
yOffset;
|
||||
|
||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||
}}
|
||||
style={{
|
||||
fontWeight: activeText === heading.text ? 'bold' : 'normal',
|
||||
}}
|
||||
>
|
||||
{heading.text}
|
||||
</StyledLink>
|
||||
</StyledList>
|
||||
))}
|
||||
</StyledUnorderedList>
|
||||
<ClientOnly>
|
||||
{!!headings?.length && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<StyledUnorderedList>
|
||||
{headings.map((heading) => (
|
||||
<StyledList
|
||||
key={heading.text}
|
||||
style={getStyledHeading(heading.level)}
|
||||
>
|
||||
<StyledLink
|
||||
href={`#${heading.text}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
const yOffset = -70;
|
||||
const y =
|
||||
heading.elem.getBoundingClientRect().top +
|
||||
window.scrollY +
|
||||
yOffset;
|
||||
|
||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||
}}
|
||||
style={{
|
||||
fontWeight:
|
||||
activeText === heading.text ? 'bold' : 'normal',
|
||||
}}
|
||||
>
|
||||
{heading.text}
|
||||
</StyledLink>
|
||||
</StyledList>
|
||||
))}
|
||||
</StyledUnorderedList>
|
||||
</motion.div>
|
||||
)}
|
||||
</ClientOnly>
|
||||
</StyledNav>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -16,7 +16,6 @@ const StyledContent = styled.div`
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
line-height: 1.8;
|
||||
font-size: 13px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@ -28,6 +27,9 @@ const StyledContent = styled.div`
|
||||
font-weight: ${Theme.font.weight.regular};
|
||||
margin: 32px 0px 0px;
|
||||
text-align: justify;
|
||||
code {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
@ -42,6 +44,10 @@ const StyledContent = styled.div`
|
||||
text-decoration: none;
|
||||
color: ${Theme.text.color.primary};
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
import styled from '@emotion/styled';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { StyledButton } from '@/app/_components/ui/layout/header/styled';
|
||||
import { Theme } from '@/app/_components/ui/theme/theme';
|
||||
@ -65,17 +66,12 @@ const StyledButtonContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
interface ArticleEditContentProps {
|
||||
articleTitle: string;
|
||||
}
|
||||
|
||||
export default function ArticleEditContent({
|
||||
articleTitle,
|
||||
}: ArticleEditContentProps) {
|
||||
export default function ArticleEditContent() {
|
||||
const pathname = usePathname().replace('/section', '');
|
||||
return (
|
||||
<StyledContainer>
|
||||
<div>
|
||||
<h2>Noticed something to change?</h2>
|
||||
<h2 id="edit">Noticed something to change?</h2>
|
||||
<p>
|
||||
As an open-source company, we welcome contributions through Github.
|
||||
Help us keep it up-to-date, accurate, and easy to understand by
|
||||
@ -83,7 +79,7 @@ export default function ArticleEditContent({
|
||||
</p>
|
||||
<StyledButtonContainer>
|
||||
<a
|
||||
href={`https://github.com/twentyhq/twenty/blob/main/packages/twenty-website/src/content/user-guide/${articleTitle}`}
|
||||
href={`https://github.com/twentyhq/twenty/blob/main/packages/twenty-website/src/content${pathname}.mdx`}
|
||||
target="_blank"
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import { Gabarito } from 'next/font/google';
|
||||
|
||||
import mq from '@/app/_components/ui/theme/mq';
|
||||
|
||||
const gabarito = Gabarito({
|
||||
weight: ['400', '500'],
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
export const DesktopNav = styled.nav`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -18,6 +26,7 @@ export const DesktopNav = styled.nav`
|
||||
z-index: 4;
|
||||
transform-origin: 50% 50% 0px;
|
||||
border-bottom: 1px solid rgba(20, 20, 20, 0.08);
|
||||
font-family: ${gabarito.style.fontFamily};
|
||||
|
||||
@media (max-width: 809px) {
|
||||
display: none;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import DocsContent from '@/app/_components/docs/DocsContent';
|
||||
import { fetchArticleFromSlug } from '@/shared-utils/fetchArticleFromSlug';
|
||||
@ -27,5 +28,8 @@ export default async function DocsSlug({
|
||||
}) {
|
||||
const basePath = '/src/content/developers';
|
||||
const mainPost = await fetchArticleFromSlug(params.slug, basePath);
|
||||
return mainPost && <DocsContent item={mainPost} />;
|
||||
if (!mainPost) {
|
||||
notFound();
|
||||
}
|
||||
return <DocsContent item={mainPost} />;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import DocsContent from '@/app/_components/docs/DocsContent';
|
||||
import { fetchArticleFromSlug } from '@/shared-utils/fetchArticleFromSlug';
|
||||
@ -27,5 +28,8 @@ export default async function DocsSlug({
|
||||
}) {
|
||||
const basePath = `/src/content/developers/${params.folder}`;
|
||||
const mainPost = await fetchArticleFromSlug(params.documentation, basePath);
|
||||
return mainPost && <DocsContent item={mainPost} />;
|
||||
if (!mainPost) {
|
||||
notFound();
|
||||
}
|
||||
return <DocsContent item={mainPost} />;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import DocsMain from '@/app/_components/docs/DocsMain';
|
||||
import { getDocsArticles } from '@/content/user-guide/constants/getDocsArticles';
|
||||
@ -29,5 +30,11 @@ export default async function DocsSlug({
|
||||
const filePath = `src/content/developers/${params.folder}/`;
|
||||
const docsArticleCards = getDocsArticles(filePath);
|
||||
const isSection = true;
|
||||
const hasOnlyEmptySections = docsArticleCards.every(
|
||||
(article) => article.topic === 'Empty Section',
|
||||
);
|
||||
if (!docsArticleCards || hasOnlyEmptySections) {
|
||||
notFound();
|
||||
}
|
||||
return <DocsMain docsArticleCards={docsArticleCards} isSection={isSection} />;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import DocsContent from '@/app/_components/docs/DocsContent';
|
||||
import { fetchArticleFromSlug } from '@/shared-utils/fetchArticleFromSlug';
|
||||
@ -27,5 +28,8 @@ export default async function TwentyUISlug({
|
||||
}) {
|
||||
const basePath = '/src/content/twenty-ui';
|
||||
const mainPost = await fetchArticleFromSlug(params.slug, basePath);
|
||||
if (!mainPost) {
|
||||
notFound();
|
||||
}
|
||||
return mainPost && <DocsContent item={mainPost} />;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import DocsContent from '@/app/_components/docs/DocsContent';
|
||||
import { fetchArticleFromSlug } from '@/shared-utils/fetchArticleFromSlug';
|
||||
@ -27,5 +28,8 @@ export default async function TwentyUISlug({
|
||||
}) {
|
||||
const basePath = `/src/content/twenty-ui/${params.folder}`;
|
||||
const mainPost = await fetchArticleFromSlug(params.documentation, basePath);
|
||||
if (!mainPost) {
|
||||
notFound();
|
||||
}
|
||||
return mainPost && <DocsContent item={mainPost} />;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import DocsMain from '@/app/_components/docs/DocsMain';
|
||||
import { getDocsArticles } from '@/content/user-guide/constants/getDocsArticles';
|
||||
@ -29,5 +30,11 @@ export default async function TwentyUISlug({
|
||||
const filePath = `src/content/twenty-ui/${params.folder}/`;
|
||||
const docsArticleCards = getDocsArticles(filePath);
|
||||
const isSection = true;
|
||||
const hasOnlyEmptySections = docsArticleCards.every(
|
||||
(article) => article.topic === 'Empty Section',
|
||||
);
|
||||
if (!docsArticleCards || hasOnlyEmptySections) {
|
||||
notFound();
|
||||
}
|
||||
return <DocsMain docsArticleCards={docsArticleCards} isSection={isSection} />;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import DocsContent from '@/app/_components/docs/DocsContent';
|
||||
import { fetchArticleFromSlug } from '@/shared-utils/fetchArticleFromSlug';
|
||||
@ -27,5 +28,8 @@ export default async function UserGuideSlug({
|
||||
}) {
|
||||
const basePath = '/src/content/user-guide';
|
||||
const mainPost = await fetchArticleFromSlug(params.slug, basePath);
|
||||
return mainPost && <DocsContent item={mainPost} />;
|
||||
if (!mainPost) {
|
||||
notFound();
|
||||
}
|
||||
return <DocsContent item={mainPost} />;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import DocsContent from '@/app/_components/docs/DocsContent';
|
||||
import { fetchArticleFromSlug } from '@/shared-utils/fetchArticleFromSlug';
|
||||
@ -27,5 +28,8 @@ export default async function UserGuideSlug({
|
||||
}) {
|
||||
const basePath = `/src/content/user-guide/${params.folder}`;
|
||||
const mainPost = await fetchArticleFromSlug(params.documentation, basePath);
|
||||
if (!mainPost) {
|
||||
notFound();
|
||||
}
|
||||
return mainPost && <DocsContent item={mainPost} />;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import DocsMain from '@/app/_components/docs/DocsMain';
|
||||
import { getDocsArticles } from '@/content/user-guide/constants/getDocsArticles';
|
||||
@ -29,5 +30,11 @@ export default async function UserGuideSlug({
|
||||
const filePath = `src/content/user-guide/${params.folder}/`;
|
||||
const docsArticleCards = getDocsArticles(filePath);
|
||||
const isSection = true;
|
||||
const hasOnlyEmptySections = docsArticleCards.every(
|
||||
(article) => article.topic === 'Empty Section',
|
||||
);
|
||||
if (!docsArticleCards || hasOnlyEmptySections) {
|
||||
notFound();
|
||||
}
|
||||
return <DocsMain docsArticleCards={docsArticleCards} isSection={isSection} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user