Migrate best practices to editor

This commit is contained in:
Kamran Ahmed
2025-11-17 13:05:14 +00:00
parent cbc6e71e79
commit 8716c60725
7 changed files with 116 additions and 33 deletions

View File

@@ -22,32 +22,27 @@ export default defineConfig({
},
'/best-practices': {
status: 301,
destination:'/roadmaps'
destination: '/roadmaps',
},
'/best-practices/aws': {
status: 301,
destination:'/aws-best-practices'
destination: '/aws-best-practices',
},
'/best-practices/backend-performance': {
status: 301,
destination:'/backend-performance-best-practices'
destination: '/backend-performance-best-practices',
},
'/best-practices/frontend-performance': {
status: 301,
destination:'/frontend-performance-best-practices'
destination: '/frontend-performance-best-practices',
},
'/best-practices/api-security': {
status: 301,
destination:'/api-security-best-practices'
destination: '/api-security-best-practices',
},
'/best-practices/code-review': {
status: 301,
destination:'/code-review-best-practices'
},
},
vite: {
server: {
allowedHosts: ['roadmap.sh', 'port3k.kamranahmed.info'],
destination: '/code-review-best-practices',
},
},
markdown: {
@@ -96,5 +91,8 @@ export default defineConfig({
ssr: {
noExternal: [/^@roadmapsh\/editor.*$/],
},
server: {
allowedHosts: ['roadmap.sh', 'port3k.kamranahmed.info'],
},
},
});

View File

@@ -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',

View File

@@ -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}`}
/>
);
})}

View File

@@ -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;

View File

@@ -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',

View File

@@ -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">

View File

@@ -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)}