mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-12 17:51:53 +08:00
Migrate best practices to our editor (#9362)
* Add best practices * Migrate best practices to editor
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
"enabled": false
|
||||
},
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1762257454897
|
||||
"lastUpdateCheck": 1763378528944
|
||||
}
|
||||
}
|
||||
1
.astro/types.d.ts
vendored
1
.astro/types.d.ts
vendored
@@ -1 +1,2 @@
|
||||
/// <reference types="astro/client" />
|
||||
/// <reference path="content.d.ts" />
|
||||
@@ -20,10 +20,29 @@ export default defineConfig({
|
||||
status: 301,
|
||||
destination: '/ai',
|
||||
},
|
||||
},
|
||||
vite: {
|
||||
server: {
|
||||
allowedHosts: ['roadmap.sh', 'port3k.kamranahmed.info'],
|
||||
'/best-practices': {
|
||||
status: 301,
|
||||
destination: '/roadmaps',
|
||||
},
|
||||
'/best-practices/aws': {
|
||||
status: 301,
|
||||
destination: '/aws-best-practices',
|
||||
},
|
||||
'/best-practices/backend-performance': {
|
||||
status: 301,
|
||||
destination: '/backend-performance-best-practices',
|
||||
},
|
||||
'/best-practices/frontend-performance': {
|
||||
status: 301,
|
||||
destination: '/frontend-performance-best-practices',
|
||||
},
|
||||
'/best-practices/api-security': {
|
||||
status: 301,
|
||||
destination: '/api-security-best-practices',
|
||||
},
|
||||
'/best-practices/code-review': {
|
||||
status: 301,
|
||||
destination: '/code-review-best-practices',
|
||||
},
|
||||
},
|
||||
markdown: {
|
||||
@@ -72,5 +91,8 @@ export default defineConfig({
|
||||
ssr: {
|
||||
noExternal: [/^@roadmapsh\/editor.*$/],
|
||||
},
|
||||
server: {
|
||||
allowedHosts: ['roadmap.sh', 'port3k.kamranahmed.info'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
BIN
public/pdfs/roadmaps/api-security-best-practices.pdf
Normal file
BIN
public/pdfs/roadmaps/api-security-best-practices.pdf
Normal file
Binary file not shown.
BIN
public/pdfs/roadmaps/aws-best-practices.pdf
Normal file
BIN
public/pdfs/roadmaps/aws-best-practices.pdf
Normal file
Binary file not shown.
BIN
public/pdfs/roadmaps/backend-performance-best-practices.pdf
Normal file
BIN
public/pdfs/roadmaps/backend-performance-best-practices.pdf
Normal file
Binary file not shown.
BIN
public/pdfs/roadmaps/frontend-performance-best-practices.pdf
Normal file
BIN
public/pdfs/roadmaps/frontend-performance-best-practices.pdf
Normal file
Binary file not shown.
BIN
public/roadmaps/api-security-best-practices.png
Normal file
BIN
public/roadmaps/api-security-best-practices.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 284 KiB |
BIN
public/roadmaps/aws-best-practices.png
Normal file
BIN
public/roadmaps/aws-best-practices.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 276 KiB |
BIN
public/roadmaps/backend-performance-best-practices.png
Normal file
BIN
public/roadmaps/backend-performance-best-practices.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 333 KiB |
BIN
public/roadmaps/frontend-performance-best-practices.png
Normal file
BIN
public/roadmaps/frontend-performance-best-practices.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 223 KiB |
@@ -78,13 +78,6 @@ const defaultPages: PageType[] = [
|
||||
icon: <RoadmapIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
isProtected: true,
|
||||
},
|
||||
{
|
||||
id: 'best-practices',
|
||||
url: '/best-practices',
|
||||
title: 'Best Practices',
|
||||
group: 'Pages',
|
||||
icon: <BestPracticesIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
},
|
||||
{
|
||||
id: 'questions',
|
||||
url: '/questions',
|
||||
|
||||
@@ -453,12 +453,12 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
<HeroRoadmap
|
||||
key={roadmap.id}
|
||||
resourceId={roadmap.id}
|
||||
resourceType="best-practice"
|
||||
resourceType="roadmap"
|
||||
resourceTitle={roadmap.title}
|
||||
isFavorite={roadmap.isFavorite}
|
||||
percentageDone={percentageDone}
|
||||
isNew={roadmap.isNew}
|
||||
url={`/best-practices/${roadmap.id}`}
|
||||
url={`/${roadmap.id}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -176,6 +176,51 @@ export function EditorRoadmapRenderer(props: RoadmapRendererProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodeType === 'checklist-item' && (target.tagName === 'text' || target.tagName === 'tspan')) {
|
||||
e.preventDefault();
|
||||
|
||||
const textElement = target.tagName === 'tspan' ? (target.closest('text') as SVGTextElement) : target;
|
||||
const clickedText = textElement?.textContent?.trim();
|
||||
if (!clickedText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentChecklistId = targetGroup?.dataset?.parentId;
|
||||
if (!parentChecklistId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentChecklistGroup = roadmapRef.current?.querySelector(
|
||||
`g[data-node-id="${parentChecklistId}"][data-type="checklist"]`
|
||||
);
|
||||
if (!parentChecklistGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const labelGroup = parentChecklistGroup.querySelector(
|
||||
'g[data-type="checklist-label"]'
|
||||
);
|
||||
if (!labelGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const labelText = labelGroup.querySelector('text')?.textContent?.trim();
|
||||
if (!labelText) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('roadmap.checklist.click', {
|
||||
detail: {
|
||||
roadmapId: resourceId,
|
||||
labelText,
|
||||
clickedText,
|
||||
},
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// we don't have the topic popup for checklist-item
|
||||
if (nodeType === 'checklist-item') {
|
||||
return;
|
||||
|
||||
@@ -306,6 +306,53 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
||||
};
|
||||
}, [isOpen, isPersonalizeOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleChecklistClick = (e: CustomEvent) => {
|
||||
const { roadmapId: eventRoadmapId, labelText, clickedText } = e.detail;
|
||||
|
||||
if (eventRoadmapId !== roadmapId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
setIsOpen(false);
|
||||
showLoginPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
const roadmapTitle = roadmapDetail?.json?.title?.page || roadmapDetail?.json?.title?.card || 'this roadmap';
|
||||
const message = `Explain the '${roadmapTitle}' best practice '${labelText} > ${clickedText}'`;
|
||||
|
||||
flushSync(() => {
|
||||
setIsOpen(true);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
sendMessage(
|
||||
{ text: message, metadata: { json: textToJSON(message) } },
|
||||
{
|
||||
body: {
|
||||
roadmapId,
|
||||
...(activeChatHistoryId
|
||||
? { chatHistoryId: activeChatHistoryId }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToBottom('smooth');
|
||||
inputRef.current?.focus();
|
||||
}, 0);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
window.addEventListener('roadmap.checklist.click', handleChecklistClick as EventListener);
|
||||
return () => {
|
||||
window.removeEventListener('roadmap.checklist.click', handleChecklistClick as EventListener);
|
||||
};
|
||||
}, [roadmapId, roadmapDetail, sendMessage, activeChatHistoryId, scrollToBottom]);
|
||||
|
||||
function textToJSON(text: string): JSONContent {
|
||||
return {
|
||||
type: 'doc',
|
||||
|
||||
@@ -81,9 +81,6 @@ export function AccountDropdownList(props: AccountDropdownListProps) {
|
||||
<SquareUserRound className="h-4 w-4 stroke-[2.5px] text-slate-400 group-hover:text-white" />
|
||||
My Profile
|
||||
</span>
|
||||
<span className="rounded-xs bg-yellow-300 px-1 text-xs tracking-wide text-black uppercase">
|
||||
New
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li className="px-1">
|
||||
|
||||
@@ -55,7 +55,7 @@ export type GroupType = {
|
||||
roadmaps: {
|
||||
title: string;
|
||||
link: string;
|
||||
type: 'role' | 'skill';
|
||||
type: 'role' | 'skill' | 'best-practice';
|
||||
otherGroups?: AllowGroupNames[];
|
||||
}[];
|
||||
};
|
||||
@@ -196,7 +196,12 @@ const groups: GroupType[] = [
|
||||
title: 'JavaScript',
|
||||
link: '/javascript',
|
||||
type: 'skill',
|
||||
otherGroups: ['Web Development', 'DevOps', 'Mobile Development', 'Absolute Beginners'],
|
||||
otherGroups: [
|
||||
'Web Development',
|
||||
'DevOps',
|
||||
'Mobile Development',
|
||||
'Absolute Beginners',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Kotlin',
|
||||
@@ -538,6 +543,41 @@ const groups: GroupType[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Best Practices',
|
||||
roadmaps: [
|
||||
{
|
||||
title: 'Backend Performance',
|
||||
link: '/backend-performance-best-practices',
|
||||
type: 'best-practice',
|
||||
otherGroups: ['Web Development'],
|
||||
},
|
||||
{
|
||||
title: 'Frontend Performance',
|
||||
link: '/frontend-performance-best-practices',
|
||||
type: 'best-practice',
|
||||
otherGroups: ['Web Development'],
|
||||
},
|
||||
{
|
||||
title: 'Code Review',
|
||||
link: '/code-review-best-practices',
|
||||
type: 'best-practice',
|
||||
otherGroups: ['Web Development'],
|
||||
},
|
||||
{
|
||||
title: 'AWS',
|
||||
link: '/aws-review-best-practices',
|
||||
type: 'best-practice',
|
||||
otherGroups: ['Web Development', 'DevOps'],
|
||||
},
|
||||
{
|
||||
title: 'API Security',
|
||||
link: '/api-security-best-practices',
|
||||
type: 'best-practice',
|
||||
otherGroups: ['Web Development'],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const roleRoadmaps = groups.flatMap((group) =>
|
||||
@@ -546,6 +586,9 @@ const roleRoadmaps = groups.flatMap((group) =>
|
||||
const skillRoadmaps = groups.flatMap((group) =>
|
||||
group.roadmaps.filter((roadmap) => roadmap.type === 'skill'),
|
||||
);
|
||||
const bestPracticeRoadmaps = groups.flatMap((group) =>
|
||||
group.roadmaps.filter((roadmap) => roadmap.type === 'best-practice'),
|
||||
);
|
||||
|
||||
const allGroups = [
|
||||
{
|
||||
@@ -556,6 +599,10 @@ const allGroups = [
|
||||
group: 'Skill Based Roadmaps',
|
||||
roadmaps: skillRoadmaps,
|
||||
},
|
||||
{
|
||||
group: 'Best Practices',
|
||||
roadmaps: bestPracticeRoadmaps,
|
||||
},
|
||||
];
|
||||
|
||||
export function RoadmapsPage() {
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { BuiltInRoadmap } from '../components/Dashboard/PersonalDashboard';
|
||||
const roadmaps = await listOfficialRoadmaps();
|
||||
const roleRoadmaps = roadmaps.filter((roadmap) => roadmap.type === 'role');
|
||||
const skillRoadmaps = roadmaps.filter((roadmap) => roadmap.type === 'skill');
|
||||
const bestPractices = await getAllBestPractices();
|
||||
const bestPractices = roadmaps.filter(roadmap => roadmap.type === 'best-practice');
|
||||
const questionGroups = await getAllQuestionGroups();
|
||||
const guides = await listOfficialGuides();
|
||||
const videos = await getAllVideos();
|
||||
@@ -48,15 +48,18 @@ const enrichedSkillRoadmaps: BuiltInRoadmap[] = skillRoadmaps.map((roadmap) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const enrichedBestPractices = bestPractices.map((bestPractice) => {
|
||||
const { frontmatter } = bestPractice;
|
||||
|
||||
const enrichedBestPracticeRoadmaps: BuiltInRoadmap[] = bestPractices.map((roadmap) => {
|
||||
return {
|
||||
id: bestPractice.id,
|
||||
url: `/best-practices/${bestPractice.id}`,
|
||||
title: frontmatter.briefTitle,
|
||||
description: frontmatter.briefDescription,
|
||||
id: roadmap.slug,
|
||||
url: `/${roadmap.slug}`,
|
||||
title: roadmap.title.card,
|
||||
description: roadmap.description,
|
||||
relatedRoadmapIds: roadmap.relatedRoadmaps,
|
||||
renderer: 'editor',
|
||||
isNew: isNewRoadmap(roadmap.createdAt),
|
||||
metadata: {
|
||||
tags: ['best-practice-roadmap'],
|
||||
},
|
||||
};
|
||||
});
|
||||
---
|
||||
@@ -65,7 +68,7 @@ const enrichedBestPractices = bestPractices.map((bestPractice) => {
|
||||
<DashboardPage
|
||||
builtInRoleRoadmaps={enrichedRoleRoadmaps}
|
||||
builtInSkillRoadmaps={enrichedSkillRoadmaps}
|
||||
builtInBestPractices={enrichedBestPractices}
|
||||
builtInBestPractices={enrichedBestPracticeRoadmaps}
|
||||
questionGroups={questionGroups}
|
||||
guides={guides.slice(0, 10)}
|
||||
videos={videos.slice(0, 10)}
|
||||
|
||||
@@ -6,7 +6,6 @@ import FeaturedItems from '../components/FeaturedItems/FeaturedItems.astro';
|
||||
import { FeaturedVideoList } from '../components/FeaturedVideos/FeaturedVideoList';
|
||||
import HeroSection from '../components/HeroSection/HeroSection.astro';
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import { getAllBestPractices } from '../lib/best-practice';
|
||||
import { getAllVideos } from '../lib/video';
|
||||
import { listOfficialGuides } from '../queries/official-guide';
|
||||
import {
|
||||
@@ -17,7 +16,9 @@ import {
|
||||
const roadmaps = await listOfficialRoadmaps();
|
||||
const roleRoadmaps = roadmaps.filter((roadmap) => roadmap.type === 'role');
|
||||
const skillRoadmaps = roadmaps.filter((roadmap) => roadmap.type === 'skill');
|
||||
const bestPractices = await getAllBestPractices();
|
||||
const bestPractices = roadmaps.filter(
|
||||
(roadmap) => roadmap.type === 'best-practice',
|
||||
);
|
||||
|
||||
export const projectGroups = [
|
||||
{
|
||||
@@ -96,14 +97,17 @@ const videos = await getAllVideos();
|
||||
|
||||
<FeaturedItems
|
||||
heading='Best Practices'
|
||||
featuredItems={bestPractices.map((bestPractice) => ({
|
||||
text: bestPractice.frontmatter.briefTitle,
|
||||
url: `/best-practices/${bestPractice.id}`,
|
||||
isNew: bestPractice.frontmatter.isNew,
|
||||
isUpcoming: bestPractice.frontmatter.isUpcoming,
|
||||
}))}
|
||||
/>
|
||||
featuredItems={bestPractices.map((bestPracticeItem) => {
|
||||
const isNew = isNewRoadmap(bestPracticeItem.createdAt);
|
||||
|
||||
return {
|
||||
text: bestPracticeItem.title.card,
|
||||
url: `/${bestPracticeItem.slug}`,
|
||||
isNew,
|
||||
};
|
||||
})}
|
||||
showCreateRoadmap={false}
|
||||
/>
|
||||
<div class='grid grid-cols-1 gap-7 bg-gray-50 py-7 sm:gap-16 sm:py-16'>
|
||||
<FeaturedGuideList
|
||||
heading='Guides'
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { FetchError, httpGet } from '../lib/query-http';
|
||||
import type { Node, Edge } from '@roadmapsh/editor';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export const allowedOfficialRoadmapType = ['skill', 'role'] as const;
|
||||
export const allowedOfficialRoadmapType = ['skill', 'role', 'best-practice'] as const;
|
||||
export type AllowedOfficialRoadmapType =
|
||||
(typeof allowedOfficialRoadmapType)[number];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user