refactor: roadmap specific guides (#9043)
* fix: ai course generate url * wip * wip * wip * feat: roadmap guides * wip * wip * feat: featured guide list
This commit is contained in:
1 parent
a024a573fe
commit
f79d8c0562
83 files changed
+912
-2407
No files matched your search
@@ -1,72 +0,0 @@
|
||||
---
|
||||
import { getGuideTableOfContent, type GuideFileType } from '../../lib/guide';
|
||||
import MarkdownFile from '../MarkdownFile.astro';
|
||||
import { TableOfContent } from '../TableOfContent/TableOfContent';
|
||||
import { RelatedGuides } from './RelatedGuides';
|
||||
|
||||
interface Props {
|
||||
guide: GuideFileType;
|
||||
}
|
||||
|
||||
const { guide } = Astro.props;
|
||||
|
||||
const allHeadings = guide.getHeadings();
|
||||
const tableOfContent = getGuideTableOfContent(allHeadings);
|
||||
|
||||
const showTableOfContent = tableOfContent.length > 0;
|
||||
const showRelatedGuides =
|
||||
guide?.relatedGuides && Object.keys(guide?.relatedGuides).length > 0;
|
||||
const { frontmatter: guideFrontmatter, author } = guide;
|
||||
---
|
||||
|
||||
<article class='lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]'>
|
||||
{
|
||||
(showTableOfContent || showRelatedGuides) && (
|
||||
<div class='sticky top-0 lg:relative bg-linear-to-r from-gray-50 py-0 lg:col-start-3 lg:col-end-4 lg:row-start-1'>
|
||||
<RelatedGuides
|
||||
relatedTitle={guideFrontmatter?.relatedTitle}
|
||||
relatedGuides={guide?.relatedGuides || {}}
|
||||
client:load
|
||||
/>
|
||||
<TableOfContent toc={tableOfContent} client:load />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div
|
||||
class:list={[
|
||||
'col-start-2 col-end-3 row-start-1 mx-auto max-w-[700px] py-5 sm:py-10',
|
||||
{
|
||||
'lg:border-r': showTableOfContent,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<MarkdownFile>
|
||||
<h1 class='mb-3 text-balance text-4xl font-bold'>
|
||||
{guideFrontmatter.title}
|
||||
</h1>
|
||||
<p class='my-0 flex items-center justify-start text-sm text-gray-400'>
|
||||
<a
|
||||
href={`/authors/${author.id}`}
|
||||
class='inline-flex items-center font-medium underline-offset-2 hover:text-gray-600 hover:underline'
|
||||
>
|
||||
<img
|
||||
alt={author.frontmatter.name}
|
||||
src={author.frontmatter.imageUrl}
|
||||
class='mb-0 mr-2 inline h-5 w-5 rounded-full'
|
||||
/>
|
||||
{author.frontmatter.name}
|
||||
</a>
|
||||
<span class='mx-2 hidden sm:inline'>·</span>
|
||||
<a
|
||||
class='hidden underline-offset-2 hover:text-gray-600 sm:inline'
|
||||
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.id}.md`}
|
||||
target='_blank'
|
||||
>
|
||||
Improve this Guide
|
||||
</a>
|
||||
</p>
|
||||
<guide.Content />
|
||||
</MarkdownFile>
|
||||
</div>
|
||||
</article>
|
||||
@@ -0,0 +1,60 @@
|
||||
import { cn } from '../../lib/classname';
|
||||
import { guideRenderer } from '../../lib/guide-renderer';
|
||||
import type { OfficialGuideResponse } from '../../queries/official-guide';
|
||||
import { TableOfContent } from '../TableOfContent/TableOfContent';
|
||||
import { RelatedGuides } from './RelatedGuides';
|
||||
|
||||
type GuideContentProps = {
|
||||
guide: OfficialGuideResponse;
|
||||
};
|
||||
|
||||
export function GuideContent(props: GuideContentProps) {
|
||||
const { guide } = props;
|
||||
const content = guideRenderer.render(guide.content);
|
||||
const tableOfContents = guideRenderer.tableOfContents(guide.content);
|
||||
const showTableOfContent = tableOfContents.length > 0;
|
||||
const hasRelatedGuides =
|
||||
guide.relatedGuides && guide.relatedGuides.length > 0;
|
||||
|
||||
return (
|
||||
<article className="lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]">
|
||||
{(showTableOfContent || hasRelatedGuides) && (
|
||||
<div className="sticky top-0 bg-linear-to-r from-gray-50 py-0 lg:relative lg:col-start-3 lg:col-end-4 lg:row-start-1">
|
||||
{hasRelatedGuides && (
|
||||
<RelatedGuides relatedGuides={guide?.relatedGuides || []} />
|
||||
)}
|
||||
|
||||
{showTableOfContent && <TableOfContent toc={tableOfContents} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'col-start-2 col-end-3 row-start-1 mx-auto max-w-[700px] py-5 sm:py-10',
|
||||
showTableOfContent && 'lg:border-r',
|
||||
)}
|
||||
>
|
||||
<div className="prose prose-xl prose-h2:mb-3 prose-h2:mt-10 prose-h2:scroll-mt-5 prose-h2:text-balance prose-h2:text-3xl prose-h3:mt-2 prose-h4:text-2xl prose-h3:scroll-mt-5 prose-h3:text-balance prose-h4:text-balance prose-h5:text-balance prose-h5:font-medium prose-blockquote:font-normal prose-code:bg-transparent prose-img:mt-1 sm:prose-h2:scroll-mt-10 sm:prose-h3:scroll-mt-10 prose-li:[&>p]:m-0 container">
|
||||
<h1 className="mb-3 text-4xl font-bold text-balance">
|
||||
{guide.title}
|
||||
</h1>
|
||||
<p className="my-0 mb-6 flex items-center justify-start text-sm text-gray-400">
|
||||
<a
|
||||
href={`/authors/${guide.author?.slug}`}
|
||||
className="inline-flex items-center font-medium underline-offset-2 hover:text-gray-600 hover:underline"
|
||||
>
|
||||
<img
|
||||
alt={guide.author?.name}
|
||||
src={guide.author?.avatar}
|
||||
className="mr-2 mb-0 inline h-5 w-5 rounded-full"
|
||||
/>
|
||||
{guide.author?.name}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import type { OfficialGuideDocument } from '../../queries/official-guide';
|
||||
import { cn } from '../../lib/classname';
|
||||
|
||||
type RelatedGuidesProps = {
|
||||
relatedTitle?: string;
|
||||
relatedGuides: Record<string, string>;
|
||||
relatedGuides: Pick<OfficialGuideDocument, 'title' | 'slug' | 'roadmapId'>[];
|
||||
};
|
||||
|
||||
export function RelatedGuides(props: RelatedGuidesProps) {
|
||||
@@ -12,14 +13,7 @@ export function RelatedGuides(props: RelatedGuidesProps) {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const relatedGuidesArray = Object.entries(relatedGuides).map(
|
||||
([title, url]) => ({
|
||||
title,
|
||||
url,
|
||||
}),
|
||||
);
|
||||
|
||||
if (relatedGuidesArray.length === 0) {
|
||||
if (relatedGuides.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -47,23 +41,32 @@ export function RelatedGuides(props: RelatedGuidesProps) {
|
||||
isOpen && 'block',
|
||||
)}
|
||||
>
|
||||
{relatedGuidesArray.map((relatedGuide) => (
|
||||
<li key={relatedGuide.url}>
|
||||
<a
|
||||
href={relatedGuide.url}
|
||||
className="text-sm text-gray-500 no-underline hover:text-black max-lg:block max-lg:border-b max-lg:px-3 max-lg:py-1"
|
||||
onClick={() => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
{relatedGuides.map((relatedGuide) => {
|
||||
const { roadmapId, slug, title } = relatedGuide;
|
||||
const href = roadmapId ? `/${roadmapId}/${slug}` : `/guides/${slug}`;
|
||||
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{relatedGuide.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
const className = cn(
|
||||
'text-sm text-gray-500 no-underline hover:text-black max-lg:block max-lg:border-b max-lg:px-3 max-lg:py-1',
|
||||
);
|
||||
|
||||
return (
|
||||
<li key={slug}>
|
||||
<a
|
||||
href={href}
|
||||
className={className}
|
||||
onClick={() => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user