mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-16 11:51:49 +08:00
Compare commits
77 Commits
feat/cmd-k
...
feat/devop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c0adf22b6 | ||
|
|
6a4481a3d0 | ||
|
|
557c426078 | ||
|
|
d61a83a0a3 | ||
|
|
7500c6c1cb | ||
|
|
b51076dd0a | ||
|
|
8010bfc832 | ||
|
|
0f80f26d17 | ||
|
|
40d25c43f4 | ||
|
|
686a7382ab | ||
|
|
88401bd7b1 | ||
|
|
1d97467c05 | ||
|
|
2388fa148b | ||
|
|
d574fccbc8 | ||
|
|
89cc55a1eb | ||
|
|
8c75354235 | ||
|
|
9eb9dc8cd8 | ||
|
|
afa28bddd3 | ||
|
|
5cf0e76765 | ||
|
|
16b3f8ff49 | ||
|
|
d2055e0f6d | ||
|
|
4010157baf | ||
|
|
75c7e83264 | ||
|
|
8ca22e0dcc | ||
|
|
2b8d1f470c | ||
|
|
c4d9651e95 | ||
|
|
813c0ebd93 | ||
|
|
e376942c8d | ||
|
|
6d91c11856 | ||
|
|
1d47b1fb7b | ||
|
|
54a9e586bf | ||
|
|
b58c2a1356 | ||
|
|
dec5e58063 | ||
|
|
b0a4130229 | ||
|
|
a06e992b8a | ||
|
|
6e1072bea9 | ||
|
|
1f9eb18bfb | ||
|
|
603bd2b107 | ||
|
|
0163d9e4f9 | ||
|
|
910579f463 | ||
|
|
d6a28a312a | ||
|
|
267a4a7be5 | ||
|
|
59111a1a90 | ||
|
|
9f5d1aef74 | ||
|
|
36eed57ec2 | ||
|
|
cc054bb24b | ||
|
|
056256015d | ||
|
|
dd5f3795ec | ||
|
|
8c29d43bef | ||
|
|
aa32258aa1 | ||
|
|
d2394aca77 | ||
|
|
6804535fe4 | ||
|
|
3852e7d96f | ||
|
|
eb852caee8 | ||
|
|
1414693e33 | ||
|
|
fbdb7e77c3 | ||
|
|
c72658938f | ||
|
|
718c582a8c | ||
|
|
12f385dffd | ||
|
|
35f500d218 | ||
|
|
44949709d1 | ||
|
|
476557db80 | ||
|
|
f7625a8250 | ||
|
|
c06c236da5 | ||
|
|
24c262282e | ||
|
|
876330522d | ||
|
|
f1c771e95c | ||
|
|
d3668b25e9 | ||
|
|
b0493c370c | ||
|
|
e67caa0ffe | ||
|
|
82a44ddfef | ||
|
|
205fe6cc23 | ||
|
|
591cac8bfa | ||
|
|
42debdeab0 | ||
|
|
0555452bf2 | ||
|
|
cc7f9d94bb | ||
|
|
51d986b86f |
18
package.json
18
package.json
@@ -21,25 +21,25 @@
|
||||
"test:e2e": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/preact": "^2.2.0",
|
||||
"@astrojs/sitemap": "^1.3.1",
|
||||
"@astrojs/preact": "^2.2.1",
|
||||
"@astrojs/sitemap": "^1.3.3",
|
||||
"@astrojs/tailwind": "^3.1.3",
|
||||
"@fingerprintjs/fingerprintjs": "^3.4.1",
|
||||
"@nanostores/preact": "^0.4.1",
|
||||
"astro": "^2.5.0",
|
||||
"astro-compress": "^1.1.43",
|
||||
"@nanostores/preact": "^0.5.0",
|
||||
"astro": "^2.5.7",
|
||||
"astro-compress": "^1.1.46",
|
||||
"jose": "^4.14.4",
|
||||
"js-cookie": "^3.0.5",
|
||||
"nanostores": "^0.8.1",
|
||||
"nanostores": "^0.9.1",
|
||||
"node-html-parser": "^6.1.5",
|
||||
"npm-check-updates": "^16.10.12",
|
||||
"preact": "^10.14.1",
|
||||
"preact": "^10.15.1",
|
||||
"rehype-external-links": "^2.1.0",
|
||||
"roadmap-renderer": "^1.0.6",
|
||||
"tailwindcss": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.33.0",
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"csv-parser": "^3.0.0",
|
||||
@@ -48,7 +48,7 @@
|
||||
"markdown-it": "^13.0.1",
|
||||
"openai": "^3.2.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-astro": "^0.9.0",
|
||||
"prettier-plugin-astro": "^0.10.0",
|
||||
"prettier-plugin-tailwindcss": "^0.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
633
pnpm-lock.yaml
generated
633
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
public/images/partners/apollo-workshop.png
Normal file
BIN
public/images/partners/apollo-workshop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
BIN
public/pdfs/roadmaps/code-review.pdf
Normal file
BIN
public/pdfs/roadmaps/code-review.pdf
Normal file
Binary file not shown.
BIN
public/pdfs/roadmaps/cpp.pdf
Normal file
BIN
public/pdfs/roadmaps/cpp.pdf
Normal file
Binary file not shown.
BIN
public/roadmaps/cpp.png
Normal file
BIN
public/roadmaps/cpp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 773 KiB |
@@ -40,6 +40,7 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [Software Design and Architecture Roadmap](https://roadmap.sh/software-design-architecture)
|
||||
- [JavaScript Roadmap](https://roadmap.sh/javascript)
|
||||
- [TypeScript Roadmap](https://roadmap.sh/typescript)
|
||||
- [C++ Roadmap](https://roadmap.sh/cpp)
|
||||
- [React Roadmap](https://roadmap.sh/react)
|
||||
- [Vue Roadmap](https://roadmap.sh/vue)
|
||||
- [Angular Roadmap](https://roadmap.sh/angular)
|
||||
@@ -61,6 +62,7 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [MongoDB Roadmap](https://roadmap.sh/mongodb)
|
||||
- [UX Design Roadmap](https://roadmap.sh/ux-design)
|
||||
- [Docker Roadmap](https://roadmap.sh/docker)
|
||||
- [Prompt Engineering Roadmap](https://roadmap.sh/prompt-engineering)
|
||||
|
||||
We have also added a new form of visual content covering best practices:
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const jsonsDir = path.join(process.cwd(), 'public/jsons');
|
||||
const childJsonDirs = fs.readdirSync(jsonsDir);
|
||||
|
||||
childJsonDirs.forEach((childJsonDir) => {
|
||||
const fullChildJsonDirPath = path.join(jsonsDir, childJsonDir);
|
||||
const jsonFiles = fs.readdirSync(fullChildJsonDirPath);
|
||||
|
||||
jsonFiles.forEach((jsonFileName) => {
|
||||
console.log(`Compressing ${jsonFileName}...`);
|
||||
|
||||
const jsonFilePath = path.join(fullChildJsonDirPath, jsonFileName);
|
||||
const json = require(jsonFilePath);
|
||||
|
||||
fs.writeFileSync(jsonFilePath, JSON.stringify(json));
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,6 @@ const path = require('path');
|
||||
|
||||
const OPEN_AI_API_KEY = process.env.OPEN_AI_API_KEY;
|
||||
const ALL_ROADMAPS_DIR = path.join(__dirname, '../src/data/roadmaps');
|
||||
const ROADMAP_JSON_DIR = path.join(__dirname, '../public/jsons/roadmaps');
|
||||
|
||||
const roadmapId = process.argv[2];
|
||||
|
||||
@@ -61,9 +60,9 @@ function writeTopicContent(currTopicUrl) {
|
||||
|
||||
const roadmapTitle = roadmapId.replace(/-/g, ' ');
|
||||
|
||||
let prompt = `I am reading a guide about "${roadmapTitle}". I am on the topic "${parentTopic}". I want to know more about "${childTopic}". Write me a brief summary for that topic. Content should be in markdown. Behave as if you are the author of the guide.`;
|
||||
let prompt = `I am reading a guide about "${roadmapTitle}". I am on the topic "${parentTopic}". I want to know more about "${childTopic}". Write me with a brief summary of that. Content should be in markdown. I already know the benefits of each so do not add benefits in the output. Also include the code examples if applicable to this topic.`;
|
||||
if (!childTopic) {
|
||||
prompt = `I am reading a guide about "${roadmapTitle}". I am on the topic "${parentTopic}". I want to know more about "${parentTopic}". Write me a brief summary for that topic. Content should be in markdown. Behave as if you are the author of the guide.`;
|
||||
prompt = `I am reading a guide about "${roadmapTitle}". I am on the topic "${parentTopic}". I want to know more about "${parentTopic}". Write me with a brief summary of that. Content should be in markdown. I already know the benefits of each so do not add benefits in the output. Also include the code examples if applicable to this topic.`;
|
||||
}
|
||||
|
||||
console.log(`Generating '${childTopic || parentTopic}'...`);
|
||||
@@ -139,7 +138,11 @@ async function writeFileForGroup(group, topicUrlToPathMapping) {
|
||||
async function run() {
|
||||
const topicUrlToPathMapping = getFilesInFolder(ROADMAP_CONTENT_DIR);
|
||||
|
||||
const roadmapJson = require(path.join(ROADMAP_JSON_DIR, `${roadmapId}.json`));
|
||||
const roadmapJson = require(path.join(
|
||||
ALL_ROADMAPS_DIR,
|
||||
`${roadmapId}/${roadmapId}`
|
||||
));
|
||||
|
||||
const groups = roadmapJson?.mockup?.controls?.control?.filter(
|
||||
(control) =>
|
||||
control.typeID === '__group__' &&
|
||||
|
||||
@@ -84,8 +84,9 @@ function prepareDirTree(control, dirTree, dirSortOrders) {
|
||||
|
||||
const roadmap = require(path.join(
|
||||
__dirname,
|
||||
`../public/jsons/roadmaps/${roadmapId}`
|
||||
`../src/data/roadmaps/${roadmapId}/${roadmapId}`
|
||||
));
|
||||
|
||||
const controls = roadmap.mockup.controls.control;
|
||||
|
||||
// Prepare the dir tree that we will be creating and also calculate the sort orders
|
||||
|
||||
115
src/components/AccountSidebar.astro
Normal file
115
src/components/AccountSidebar.astro
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
import AstroIcon from './AstroIcon.astro';
|
||||
|
||||
const { activePageId, activePageTitle } = Astro.props;
|
||||
|
||||
export interface Props {
|
||||
activePageId: string;
|
||||
activePageTitle: string;
|
||||
}
|
||||
|
||||
const sidebarLinks = [
|
||||
{
|
||||
href: '/account',
|
||||
title: 'Activity',
|
||||
id: 'activity',
|
||||
icon: {
|
||||
glyph: 'analytics',
|
||||
classes: 'h-3 w-4',
|
||||
}
|
||||
},
|
||||
{
|
||||
href: '/account/update-profile',
|
||||
title: 'Profile',
|
||||
id: 'profile',
|
||||
icon: {
|
||||
glyph: 'user',
|
||||
classes: 'h-4 w-4',
|
||||
}
|
||||
},
|
||||
{
|
||||
href: '/account/update-password',
|
||||
title: 'Security',
|
||||
id: 'change-password',
|
||||
icon: {
|
||||
glyph: 'security',
|
||||
classes: 'h-4 w-4'
|
||||
}
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<div class='relative mb-5 block border-b p-4 shadow-inner md:hidden'>
|
||||
<button
|
||||
class='flex h-10 w-full items-center justify-between rounded-md border bg-white px-2 text-center text-gray-900 text-sm font-medium'
|
||||
id='settings-menu'
|
||||
>
|
||||
{activePageTitle}
|
||||
<AstroIcon icon='dropdown' />
|
||||
</button>
|
||||
<ul
|
||||
id='settings-menu-dropdown'
|
||||
class='absolute left-0 right-0 z-10 mt-1 hidden space-y-1.5 bg-white p-2 shadow-lg'
|
||||
>
|
||||
{
|
||||
sidebarLinks.map((sidebarLink) => (
|
||||
<li>
|
||||
<a
|
||||
href={sidebarLink.href}
|
||||
class={`flex items-center w-full rounded px-3 py-1.5 text-slate-900 hover:bg-slate-200 text-sm ${
|
||||
activePageId === sidebarLink.id ? 'bg-slate-100' : ''
|
||||
}`}
|
||||
>
|
||||
<AstroIcon icon={sidebarLink.icon.glyph} class={`${sidebarLink.icon.classes} mr-2`} />
|
||||
{sidebarLink.title}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class='container flex min-h-screen items-stretch'>
|
||||
<!-- Start Desktop Sidebar -->
|
||||
<aside class='hidden shrink-0 w-44 border-r border-slate-200 py-10 md:block'>
|
||||
<nav>
|
||||
<ul class='space-y-1'>
|
||||
{
|
||||
sidebarLinks.map((sidebarLink) => (
|
||||
<li>
|
||||
<a
|
||||
href={sidebarLink.href}
|
||||
class={`font-regular flex w-full items-center gap-2 px-2 py-1.5 text-sm border-r-2 ${
|
||||
activePageId === sidebarLink.id ? 'text-black border-r-black bg-gray-100' : 'text-gray-500 border-r-transparent hover:border-r-gray-300'
|
||||
}`}
|
||||
>
|
||||
<AstroIcon icon={sidebarLink.icon.glyph} class={`${sidebarLink.icon.classes} mr-0`} />
|
||||
{sidebarLink.title}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<!-- /End Desktop Sidebar -->
|
||||
|
||||
<div class='grow px-0 py-0 md:px-10 md:py-10'>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const menuButton = document.getElementById('settings-menu');
|
||||
const menuDropdown = document.getElementById('settings-menu-dropdown');
|
||||
|
||||
menuButton?.addEventListener('click', () => {
|
||||
menuDropdown?.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!menuButton?.contains(e.target as Node)) {
|
||||
menuDropdown?.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
56
src/components/Activity/ActivityCounters.tsx
Normal file
56
src/components/Activity/ActivityCounters.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
type ActivityCountersType = {
|
||||
done: {
|
||||
today: number;
|
||||
total: number;
|
||||
};
|
||||
learning: {
|
||||
today: number;
|
||||
total: number;
|
||||
};
|
||||
streak: {
|
||||
count: number;
|
||||
};
|
||||
};
|
||||
|
||||
type ActivityCounterType = {
|
||||
text: string;
|
||||
count: string;
|
||||
};
|
||||
|
||||
function ActivityCounter(props: ActivityCounterType) {
|
||||
const { text, count } = props;
|
||||
|
||||
return (
|
||||
<div class="relative flex flex-1 flex-row-reverse sm:flex-col px-0 sm:px-4 py-2 sm:py-4 text-center sm:pt-10 items-center gap-2 sm:gap-0 justify-end">
|
||||
<h2 class="text-base sm:text-5xl font-bold">
|
||||
{count}
|
||||
</h2>
|
||||
<p class="mt-0 sm:mt-2 text-sm text-gray-400">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ActivityCounters(props: ActivityCountersType) {
|
||||
const { done, learning, streak } = props;
|
||||
|
||||
return (
|
||||
<div class="mx-0 -mt-5 sm:-mx-10 md:-mt-10">
|
||||
<div class="flex flex-col sm:flex-row gap-0 sm:gap-2 divide-y sm:divide-y-0 divide-x-0 sm:divide-x border-b">
|
||||
<ActivityCounter
|
||||
text={'Topics Completed'}
|
||||
count={`${done?.total || 0}`}
|
||||
/>
|
||||
|
||||
<ActivityCounter
|
||||
text={'Currently Learning'}
|
||||
count={`${learning?.total || 0}`}
|
||||
/>
|
||||
|
||||
<ActivityCounter
|
||||
text={'Visit Streak'}
|
||||
count={`${streak?.count || 0}d`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
161
src/components/Activity/ActivityPage.tsx
Normal file
161
src/components/Activity/ActivityPage.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { ActivityCounters } from './ActivityCounters';
|
||||
import { ResourceProgress } from './ResourceProgress';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import { EmptyActivity } from './EmptyActivity';
|
||||
|
||||
type ActivityResponse = {
|
||||
done: {
|
||||
today: number;
|
||||
total: number;
|
||||
};
|
||||
learning: {
|
||||
today: number;
|
||||
total: number;
|
||||
roadmaps: {
|
||||
title: string;
|
||||
id: string;
|
||||
learning: number;
|
||||
done: number;
|
||||
total: number;
|
||||
skipped: number;
|
||||
updatedAt: string;
|
||||
}[];
|
||||
bestPractices: {
|
||||
title: string;
|
||||
id: string;
|
||||
learning: number;
|
||||
done: number;
|
||||
skipped: number;
|
||||
total: number;
|
||||
updatedAt: string;
|
||||
}[];
|
||||
};
|
||||
streak: {
|
||||
count: number;
|
||||
firstVisitAt: Date | null;
|
||||
lastVisitAt: Date | null;
|
||||
};
|
||||
activity: {
|
||||
type: 'done' | 'learning' | 'pending' | 'skipped';
|
||||
createdAt: Date;
|
||||
metadata: {
|
||||
resourceId?: string;
|
||||
resourceType?: 'roadmap' | 'best-practice';
|
||||
topicId?: string;
|
||||
topicLabel?: string;
|
||||
resourceTitle?: string;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
|
||||
export function ActivityPage() {
|
||||
const [activity, setActivity] = useState<ActivityResponse>();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
async function loadActivity() {
|
||||
const { error, response } = await httpGet<ActivityResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-stats`
|
||||
);
|
||||
|
||||
if (!response || error) {
|
||||
console.error('Error loading activity');
|
||||
console.error(error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setActivity(response);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadActivity().finally(() => {
|
||||
pageProgressMessage.set('');
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const learningRoadmaps = activity?.learning.roadmaps || [];
|
||||
const learningBestPractices = activity?.learning.bestPractices || [];
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActivityCounters
|
||||
done={activity?.done || { today: 0, total: 0 }}
|
||||
learning={activity?.learning || { today: 0, total: 0 }}
|
||||
streak={activity?.streak || { count: 0 }}
|
||||
/>
|
||||
|
||||
<div class="mx-0 px-0 py-5 md:-mx-10 md:px-8 md:py-8">
|
||||
{learningRoadmaps.length === 0 &&
|
||||
learningBestPractices.length === 0 && <EmptyActivity />}
|
||||
|
||||
{(learningRoadmaps.length > 0 || learningBestPractices.length > 0) && (
|
||||
<>
|
||||
<h2 class="mb-3 text-xs uppercase text-gray-400">
|
||||
Continue Following
|
||||
</h2>
|
||||
<div class="flex flex-col gap-3">
|
||||
{learningRoadmaps
|
||||
.sort((a, b) => {
|
||||
const updatedAtA = new Date(a.updatedAt);
|
||||
const updatedAtB = new Date(b.updatedAt);
|
||||
|
||||
return updatedAtB.getTime() - updatedAtA.getTime();
|
||||
})
|
||||
.map((roadmap) => (
|
||||
<ResourceProgress
|
||||
doneCount={roadmap.done || 0}
|
||||
learningCount={roadmap.learning || 0}
|
||||
totalCount={roadmap.total || 0}
|
||||
skippedCount={roadmap.skipped || 0}
|
||||
resourceId={roadmap.id}
|
||||
resourceType={'roadmap'}
|
||||
updatedAt={roadmap.updatedAt}
|
||||
title={roadmap.title}
|
||||
onCleared={() => {
|
||||
pageProgressMessage.set('Updating activity');
|
||||
loadActivity().finally(() => {
|
||||
pageProgressMessage.set('');
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{learningBestPractices
|
||||
.sort((a, b) => {
|
||||
const updatedAtA = new Date(a.updatedAt);
|
||||
const updatedAtB = new Date(b.updatedAt);
|
||||
|
||||
return updatedAtB.getTime() - updatedAtA.getTime();
|
||||
})
|
||||
.map((bestPractice) => (
|
||||
<ResourceProgress
|
||||
doneCount={bestPractice.done || 0}
|
||||
totalCount={bestPractice.total || 0}
|
||||
learningCount={bestPractice.learning || 0}
|
||||
resourceId={bestPractice.id}
|
||||
skippedCount={bestPractice.skipped || 0}
|
||||
resourceType={'best-practice'}
|
||||
title={bestPractice.title}
|
||||
updatedAt={bestPractice.updatedAt}
|
||||
onCleared={() => {
|
||||
pageProgressMessage.set('Updating activity');
|
||||
loadActivity().finally(() => {
|
||||
pageProgressMessage.set('');
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
27
src/components/Activity/EmptyActivity.tsx
Normal file
27
src/components/Activity/EmptyActivity.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import CheckIcon from '../../icons/roadmap.svg';
|
||||
|
||||
export function EmptyActivity() {
|
||||
return (
|
||||
<div class="rounded-md">
|
||||
<div class="flex flex-col items-center p-7 text-center">
|
||||
<img
|
||||
alt="no roadmaps"
|
||||
src={CheckIcon}
|
||||
class="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10"
|
||||
/>
|
||||
<h2 class="text-lg sm:text-xl font-bold">No Progress</h2>
|
||||
<p className="my-1 sm:my-2 max-w-[400px] text-gray-500 text-sm sm:text-base">
|
||||
Progress will appear here as you start tracking your{' '}
|
||||
<a href="/roadmaps" class="mt-4 text-blue-500 hover:underline">
|
||||
Roadmaps
|
||||
</a>{' '}
|
||||
or{' '}
|
||||
<a href="/best-practices" class="mt-4 text-blue-500 hover:underline">
|
||||
Best Practices
|
||||
</a>{' '}
|
||||
progress.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
139
src/components/Activity/ResourceProgress.tsx
Normal file
139
src/components/Activity/ResourceProgress.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { useState } from 'preact/hooks';
|
||||
import { httpPost } from '../../lib/http';
|
||||
import { getRelativeTimeString } from '../../lib/date';
|
||||
|
||||
type ResourceProgressType = {
|
||||
resourceType: 'roadmap' | 'best-practice';
|
||||
resourceId: string;
|
||||
title: string;
|
||||
updatedAt: string;
|
||||
totalCount: number;
|
||||
doneCount: number;
|
||||
learningCount: number;
|
||||
skippedCount: number;
|
||||
onCleared: () => void;
|
||||
};
|
||||
|
||||
export function ResourceProgress(props: ResourceProgressType) {
|
||||
const [isClearing, setIsClearing] = useState(false);
|
||||
const [isConfirming, setIsConfirming] = useState(false);
|
||||
|
||||
const {
|
||||
updatedAt,
|
||||
resourceType,
|
||||
resourceId,
|
||||
title,
|
||||
totalCount,
|
||||
learningCount,
|
||||
doneCount,
|
||||
skippedCount,
|
||||
onCleared,
|
||||
} = props;
|
||||
|
||||
async function clearProgress() {
|
||||
setIsClearing(true);
|
||||
const { error, response } = await httpPost(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-clear-resource-progress`,
|
||||
{
|
||||
resourceId,
|
||||
resourceType,
|
||||
}
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
alert('Error clearing progress. Please try again.');
|
||||
console.error(error);
|
||||
setIsClearing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem(`${resourceType}-${resourceId}-progress`);
|
||||
console.log(`${resourceType}-${resourceId}-progress`);
|
||||
setIsClearing(false);
|
||||
setIsConfirming(false);
|
||||
onCleared();
|
||||
}
|
||||
|
||||
const url =
|
||||
resourceType === 'roadmap'
|
||||
? `/${resourceId}`
|
||||
: `/best-practices/${resourceId}`;
|
||||
|
||||
const totalMarked = doneCount + skippedCount;
|
||||
const progressPercentage = Math.round((totalMarked / totalCount) * 100);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<a
|
||||
href={url}
|
||||
className="group relative flex cursor-pointer items-center rounded-t-md border p-3 text-gray-600 hover:border-gray-300 hover:text-black"
|
||||
>
|
||||
<span
|
||||
className={`absolute left-0 top-0 block h-full cursor-pointer rounded-tl-md bg-black/5 group-hover:bg-black/10`}
|
||||
style={{
|
||||
width: `${progressPercentage}%`,
|
||||
}}
|
||||
></span>
|
||||
<span className="relative flex-1 cursor-pointer truncate">
|
||||
{title}
|
||||
</span>
|
||||
<span className="ml-1 cursor-pointer text-sm text-gray-400">
|
||||
{getRelativeTimeString(updatedAt)}
|
||||
</span>
|
||||
</a>
|
||||
<p className="sm:space-between flex flex-row items-start rounded-b-md border border-t-0 px-2 py-2 text-xs text-gray-500">
|
||||
<span className="hidden flex-1 gap-1 sm:flex">
|
||||
{doneCount > 0 && (
|
||||
<>
|
||||
<span>{doneCount} done</span> •
|
||||
</>
|
||||
)}
|
||||
{learningCount > 0 && (
|
||||
<>
|
||||
<span>{learningCount} in progress</span> •
|
||||
</>
|
||||
)}
|
||||
{skippedCount > 0 && (
|
||||
<>
|
||||
<span>{skippedCount} skipped</span> •
|
||||
</>
|
||||
)}
|
||||
<span>{totalCount} total</span>
|
||||
</span>
|
||||
{!isConfirming && (
|
||||
<button
|
||||
className="text-red-500 hover:text-red-800"
|
||||
onClick={() => setIsConfirming(true)}
|
||||
disabled={isClearing}
|
||||
>
|
||||
{!isClearing && (
|
||||
<>
|
||||
Clear Progress <span>×</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isClearing && 'Processing...'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{isConfirming && (
|
||||
<span>
|
||||
Are you sure?{' '}
|
||||
<button
|
||||
onClick={clearProgress}
|
||||
className="ml-1 mr-1 text-red-500 underline hover:text-red-800"
|
||||
>
|
||||
Yes
|
||||
</button>{' '}
|
||||
<button
|
||||
onClick={() => setIsConfirming(false)}
|
||||
className="text-red-500 underline hover:text-red-800"
|
||||
>
|
||||
No
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -26,7 +26,10 @@ const EmailLoginForm: FunctionComponent<{}> = () => {
|
||||
|
||||
// Log the user in and reload the page
|
||||
if (response?.token) {
|
||||
Cookies.set(TOKEN_COOKIE_NAME, response.token);
|
||||
Cookies.set(TOKEN_COOKIE_NAME, response.token, {
|
||||
path: '/',
|
||||
expires: 30,
|
||||
});
|
||||
window.location.reload();
|
||||
|
||||
return;
|
||||
|
||||
@@ -59,7 +59,10 @@ export function GitHubButton(props: GitHubButtonProps) {
|
||||
|
||||
localStorage.removeItem(GITHUB_REDIRECT_AT);
|
||||
localStorage.removeItem(GITHUB_LAST_PAGE);
|
||||
Cookies.set(TOKEN_COOKIE_NAME, response.token);
|
||||
Cookies.set(TOKEN_COOKIE_NAME, response.token, {
|
||||
path: '/',
|
||||
expires: 30,
|
||||
});
|
||||
window.location.href = redirectUrl;
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
@@ -57,7 +57,10 @@ export function GoogleButton(props: GoogleButtonProps) {
|
||||
|
||||
localStorage.removeItem(GOOGLE_REDIRECT_AT);
|
||||
localStorage.removeItem(GOOGLE_LAST_PAGE);
|
||||
Cookies.set(TOKEN_COOKIE_NAME, response.token);
|
||||
Cookies.set(TOKEN_COOKIE_NAME, response.token, {
|
||||
path: '/',
|
||||
expires: 30,
|
||||
});
|
||||
window.location.href = redirectUrl;
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { httpPost } from '../../lib/http';
|
||||
import Cookies from 'js-cookie';
|
||||
import {TOKEN_COOKIE_NAME} from "../../lib/jwt";
|
||||
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
|
||||
|
||||
export default function ResetPasswordForm() {
|
||||
const [code, setCode] = useState('');
|
||||
@@ -53,7 +53,10 @@ export default function ResetPasswordForm() {
|
||||
}
|
||||
|
||||
const token = response.token;
|
||||
Cookies.set(TOKEN_COOKIE_NAME, token);
|
||||
Cookies.set(TOKEN_COOKIE_NAME, token, {
|
||||
path: '/',
|
||||
expires: 30,
|
||||
});
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,10 @@ export function TriggerVerifyAccount() {
|
||||
return;
|
||||
}
|
||||
|
||||
Cookies.set(TOKEN_COOKIE_NAME, response.token);
|
||||
Cookies.set(TOKEN_COOKIE_NAME, response.token, {
|
||||
path: '/',
|
||||
expires: 30,
|
||||
});
|
||||
window.location.href = '/';
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
@@ -32,8 +32,9 @@ function showHideGuestElements(hideOrShow: 'hide' | 'show' = 'hide') {
|
||||
// Prepares the UI for the user who is logged in
|
||||
function handleGuest() {
|
||||
const authenticatedRoutes = [
|
||||
'/settings/update-profile',
|
||||
'/settings/update-password',
|
||||
'/account/update-profile',
|
||||
'/account/update-password',
|
||||
'/account',
|
||||
];
|
||||
|
||||
showHideAuthElements('hide');
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
---
|
||||
|
||||
<div class='recaptcha-field mb-2'></div>
|
||||
<input type='hidden' name='g-recaptcha-response' class='recaptcha-response' />
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<script src='./captcha.js'></script>
|
||||
|
||||
<script is:inline>
|
||||
window.onCaptchaLoad = function () {
|
||||
if (!window.grecaptcha) {
|
||||
console.warn('window.grecaptcha is not defined');
|
||||
return;
|
||||
}
|
||||
|
||||
const recaptchaFields = document.querySelectorAll('.recaptcha-field');
|
||||
|
||||
// render recaptcha on fields
|
||||
recaptchaFields.forEach((field) => {
|
||||
// If captcha already rendered for this field
|
||||
if (field.hasAttribute('data-recaptcha-id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renderedId = window.grecaptcha.render(field, {
|
||||
sitekey: '6Ldn2YsjAAAAABlUxNxukAuDAUIuZIhO0hRVxzJW',
|
||||
});
|
||||
|
||||
field.setAttribute('data-recaptcha-id', renderedId);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script
|
||||
src='https://www.google.com/recaptcha/api.js?onload=onCaptchaLoad&render=explicit'
|
||||
async
|
||||
defer
|
||||
></script>
|
||||
@@ -1,49 +0,0 @@
|
||||
class Captcha {
|
||||
constructor() {
|
||||
this.onDOMLoaded = this.onDOMLoaded.bind(this);
|
||||
this.bindValidation = this.bindValidation.bind(this);
|
||||
this.validateCaptchaBeforeSubmit =
|
||||
this.validateCaptchaBeforeSubmit.bind(this);
|
||||
}
|
||||
|
||||
validateCaptchaBeforeSubmit(e) {
|
||||
const target = e.target;
|
||||
const captchaField = target.querySelector('.recaptcha-field');
|
||||
|
||||
if (captchaField) {
|
||||
const captchaId = captchaField.dataset.recaptchaId;
|
||||
const captchaResponse = window.grecaptcha.getResponse(captchaId);
|
||||
|
||||
// If valid captcha is not present, prevent form submission
|
||||
if (!captchaResponse) {
|
||||
e.preventDefault();
|
||||
alert('Please verify that you are human first');
|
||||
return false;
|
||||
}
|
||||
|
||||
target.querySelector('.recaptcha-response').value = captchaResponse;
|
||||
}
|
||||
|
||||
target.closest('.popup').classList.add('hidden');
|
||||
return true;
|
||||
}
|
||||
|
||||
bindValidation() {
|
||||
const forms = document.querySelectorAll('[captcha-form]');
|
||||
|
||||
forms.forEach((form) => {
|
||||
form.addEventListener('submit', this.validateCaptchaBeforeSubmit);
|
||||
});
|
||||
}
|
||||
|
||||
onDOMLoaded() {
|
||||
this.bindValidation();
|
||||
}
|
||||
|
||||
init() {
|
||||
window.addEventListener('DOMContentLoaded', this.onDOMLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
const captcha = new Captcha();
|
||||
captcha.init();
|
||||
204
src/components/CommandMenu/CommandMenu.tsx
Normal file
204
src/components/CommandMenu/CommandMenu.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import BestPracticesIcon from '../../icons/best-practices.svg';
|
||||
import GuideIcon from '../../icons/guide.svg';
|
||||
import HomeIcon from '../../icons/home.svg';
|
||||
import RoadmapIcon from '../../icons/roadmap.svg';
|
||||
import UserIcon from '../../icons/user.svg';
|
||||
import VideoIcon from '../../icons/video.svg';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
|
||||
type PageType = {
|
||||
url: string;
|
||||
title: string;
|
||||
group: string;
|
||||
icon?: string;
|
||||
isProtected?: boolean;
|
||||
};
|
||||
|
||||
const defaultPages: PageType[] = [
|
||||
{ url: '/', title: 'Home', group: 'Pages', icon: HomeIcon },
|
||||
{
|
||||
url: '/account',
|
||||
title: 'Account',
|
||||
group: 'Pages',
|
||||
icon: UserIcon,
|
||||
isProtected: true,
|
||||
},
|
||||
{ url: '/roadmaps', title: 'Roadmaps', group: 'Pages', icon: RoadmapIcon },
|
||||
{
|
||||
url: '/best-practices',
|
||||
title: 'Best Practices',
|
||||
group: 'Pages',
|
||||
icon: BestPracticesIcon,
|
||||
},
|
||||
{ url: '/guides', title: 'Guides', group: 'Pages', icon: GuideIcon },
|
||||
{ url: '/videos', title: 'Videos', group: 'Pages', icon: VideoIcon },
|
||||
];
|
||||
|
||||
function shouldShowPage(page: PageType) {
|
||||
const isUser = isLoggedIn();
|
||||
|
||||
return !page.isProtected || isUser;
|
||||
}
|
||||
|
||||
export function CommandMenu() {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const modalRef = useRef<HTMLInputElement>(null);
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
const [allPages, setAllPages] = useState<PageType[]>([]);
|
||||
const [searchResults, setSearchResults] = useState<PageType[]>(defaultPages);
|
||||
const [searchedText, setSearchedText] = useState('');
|
||||
const [activeCounter, setActiveCounter] = useState(0);
|
||||
|
||||
useKeydown('mod_k', () => {
|
||||
setIsActive(true);
|
||||
});
|
||||
|
||||
useOutsideClick(modalRef, () => {
|
||||
setSearchedText('');
|
||||
setIsActive(false);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function handleToggleTopic(e: any) {
|
||||
setIsActive(true);
|
||||
}
|
||||
|
||||
getAllPages();
|
||||
window.addEventListener(`command.k`, handleToggleTopic);
|
||||
return () => {
|
||||
window.removeEventListener(`command.k`, handleToggleTopic);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isActive || !inputRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputRef.current.focus();
|
||||
}, [isActive]);
|
||||
|
||||
async function getAllPages() {
|
||||
if (allPages.length > 0) {
|
||||
return allPages;
|
||||
}
|
||||
const { error, response } = await httpGet<PageType[]>(`/pages.json`);
|
||||
if (!response) {
|
||||
return defaultPages.filter(shouldShowPage);
|
||||
}
|
||||
|
||||
setAllPages([...defaultPages, ...response].filter(shouldShowPage));
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchedText) {
|
||||
setSearchResults(defaultPages.filter(shouldShowPage));
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedSearchText = searchedText.trim().toLowerCase();
|
||||
getAllPages().then((unfilteredPages = defaultPages) => {
|
||||
const filteredPages = unfilteredPages
|
||||
.filter((currPage: PageType) => {
|
||||
return (
|
||||
currPage.title.toLowerCase().indexOf(normalizedSearchText) !== -1
|
||||
);
|
||||
})
|
||||
.slice(0, 10);
|
||||
|
||||
setActiveCounter(0);
|
||||
setSearchResults(filteredPages);
|
||||
});
|
||||
}, [searchedText]);
|
||||
|
||||
if (!isActive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed left-0 right-0 top-0 z-50 flex h-full justify-center overflow-y-auto overflow-x-hidden bg-black/50">
|
||||
<div className="relative top-0 h-full w-full max-w-lg p-2 sm:top-20 md:h-auto">
|
||||
<div className="relative rounded-lg bg-white shadow" ref={modalRef}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
autofocus={true}
|
||||
type="text"
|
||||
value={searchedText}
|
||||
className="w-full rounded-t-md border-b p-4 text-sm focus:bg-gray-50 focus:outline-none"
|
||||
placeholder="Search roadmaps, guides or pages .."
|
||||
autocomplete="off"
|
||||
onInput={(e) => {
|
||||
const value = (e.target as HTMLInputElement).value.trim();
|
||||
setSearchedText(value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'ArrowDown') {
|
||||
const canGoNext = activeCounter < searchResults.length - 1;
|
||||
setActiveCounter(canGoNext ? activeCounter + 1 : 0);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
const canGoPrev = activeCounter > 0;
|
||||
setActiveCounter(
|
||||
canGoPrev ? activeCounter - 1 : searchResults.length - 1
|
||||
);
|
||||
} else if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'Escape') {
|
||||
setSearchedText('');
|
||||
setIsActive(false);
|
||||
} else if (e.key === 'Enter') {
|
||||
const activePage = searchResults[activeCounter];
|
||||
if (activePage) {
|
||||
window.location.href = activePage.url;
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="px-2 py-2">
|
||||
<div className="flex flex-col">
|
||||
{searchResults.length === 0 && (
|
||||
<div class="p-5 text-center text-sm text-gray-400">
|
||||
No results found
|
||||
</div>
|
||||
)}
|
||||
|
||||
{searchResults.map((page, counter) => {
|
||||
const prevPage = searchResults[counter - 1];
|
||||
const groupChanged = prevPage && prevPage.group !== page.group;
|
||||
|
||||
return (
|
||||
<>
|
||||
{groupChanged && (
|
||||
<div class="border-b border-gray-100"></div>
|
||||
)}
|
||||
<a
|
||||
class={`flex w-full items-center rounded p-2 text-sm ${
|
||||
counter === activeCounter ? 'bg-gray-100' : ''
|
||||
}`}
|
||||
onMouseOver={() => setActiveCounter(counter)}
|
||||
href={page.url}
|
||||
>
|
||||
{!page.icon && (
|
||||
<span class="mr-2 text-gray-400">{page.group}</span>
|
||||
)}
|
||||
{page.icon && (
|
||||
<img src={page.icon} class="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{page.title}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,14 +5,13 @@ import './FrameRenderer.css';
|
||||
export interface Props {
|
||||
resourceType: 'roadmap' | 'best-practice';
|
||||
resourceId: string;
|
||||
jsonUrl: string;
|
||||
dimensions?: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
const { resourceId, resourceType, jsonUrl, dimensions = null } = Astro.props;
|
||||
const { resourceId, resourceType, dimensions = null } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
@@ -22,7 +21,6 @@ const { resourceId, resourceType, jsonUrl, dimensions = null } = Astro.props;
|
||||
: null}
|
||||
data-resource-type={resourceType}
|
||||
data-resource-id={resourceId}
|
||||
data-json-url={jsonUrl}
|
||||
>
|
||||
<div id='resource-loader'>
|
||||
<Loader />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { wireframeJSONToSVG } from 'roadmap-renderer';
|
||||
import { httpPost } from '../../lib/http';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import {
|
||||
renderResourceProgress,
|
||||
ResourceType,
|
||||
} from '../../lib/resource-progress';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { httpPost } from '../../lib/http';
|
||||
|
||||
export class Renderer {
|
||||
resourceId: string;
|
||||
@@ -51,7 +51,6 @@ export class Renderer {
|
||||
|
||||
this.resourceType = dataset.resourceType!;
|
||||
this.resourceId = dataset.resourceId!;
|
||||
this.jsonUrl = dataset.jsonUrl!;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -130,13 +129,19 @@ export class Renderer {
|
||||
this.trackVisit();
|
||||
|
||||
if (roadmapType) {
|
||||
this.switchRoadmap(`/jsons/roadmaps/${roadmapType}.json`);
|
||||
this.switchRoadmap(`/${roadmapType}.json`);
|
||||
} else {
|
||||
this.jsonToSvg(this.jsonUrl);
|
||||
this.jsonToSvg(
|
||||
this.resourceType === 'roadmap'
|
||||
? `/${this.resourceId}.json`
|
||||
: `/best-practices/${this.resourceId}.json`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
switchRoadmap(newJsonUrl: string) {
|
||||
this.containerEl?.setAttribute('style', '');
|
||||
|
||||
const newJsonFileSlug = newJsonUrl.split('/').pop()?.replace('.json', '');
|
||||
|
||||
// Update the URL and attach the new roadmap type
|
||||
@@ -145,25 +150,15 @@ export class Renderer {
|
||||
const type = this.resourceType[0]; // r for roadmap, b for best-practices
|
||||
|
||||
url.searchParams.delete(type);
|
||||
url.searchParams.set(type, newJsonFileSlug!);
|
||||
|
||||
if (newJsonFileSlug !== this.resourceId) {
|
||||
url.searchParams.set(type, newJsonFileSlug!);
|
||||
}
|
||||
|
||||
window.history.pushState(null, '', url.toString());
|
||||
}
|
||||
|
||||
const pageType = this.resourceType.replace(/\b\w/g, (l) => l.toUpperCase());
|
||||
|
||||
window.fireEvent({
|
||||
// RoadmapClick, BestPracticesClick, etc
|
||||
category: `${pageType.replace('-', '')}Click`,
|
||||
// roadmap/frontend/switch-version
|
||||
action: `${this.resourceId}/switch-version`,
|
||||
// roadmap/frontend/switch-version
|
||||
label: `${newJsonFileSlug}`,
|
||||
});
|
||||
|
||||
this.jsonToSvg(newJsonUrl)?.then(() => {
|
||||
this.containerEl?.setAttribute('style', '');
|
||||
});
|
||||
this.jsonToSvg(newJsonUrl)?.then(() => {});
|
||||
}
|
||||
|
||||
handleSvgClick(e: any) {
|
||||
@@ -176,7 +171,17 @@ export class Renderer {
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
if (/^ext_link/.test(groupId)) {
|
||||
window.open(`https://${groupId.replace('ext_link:', '')}`);
|
||||
const externalLink = groupId.replace('ext_link:', '');
|
||||
|
||||
if (!externalLink.startsWith('roadmap.sh')) {
|
||||
window.fireEvent({
|
||||
category: 'RoadmapExternalLink',
|
||||
action: `${this.resourceType} / ${this.resourceId}`,
|
||||
label: externalLink,
|
||||
});
|
||||
}
|
||||
|
||||
window.open(`https://${externalLink}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ const { author } = frontmatter;
|
||||
<span class='mx-1.5'>·</span>
|
||||
<a
|
||||
class='text-blue-400 hover:text-blue-500 hover:underline'
|
||||
href={`https://github.com/kamranahmedse/roadmap.sh/tree/master/src/data/guides/${guide.id}.md`}
|
||||
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.id}.md`}
|
||||
target='_blank'>Improve this Guide</a
|
||||
>
|
||||
</p>
|
||||
|
||||
@@ -24,10 +24,10 @@ import Icon from '../AstroIcon.astro';
|
||||
<ul>
|
||||
<li class='px-1'>
|
||||
<a
|
||||
href='/settings/update-profile'
|
||||
href='/account'
|
||||
class='block rounded px-4 py-2 text-sm font-medium text-slate-100 hover:bg-slate-700'
|
||||
>
|
||||
Settings
|
||||
Profile
|
||||
</a>
|
||||
</li>
|
||||
<li class='px-1'>
|
||||
|
||||
@@ -7,7 +7,6 @@ import AccountDropdown from './AccountDropdown.astro';
|
||||
<nav class='container flex items-center justify-between'>
|
||||
<a class='flex items-center text-lg font-medium text-white' href='/' aria-label="roadmap.sh">
|
||||
<Icon icon='logo' />
|
||||
<span class='ml-3 hidden md:block'>roadmap.sh</span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop navigation items -->
|
||||
@@ -20,11 +19,17 @@ import AccountDropdown from './AccountDropdown.astro';
|
||||
>Best Practices</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href='/guides' class='hidden lg:inline text-gray-400 hover:text-white'>Guides</a>
|
||||
<li class='hidden lg:inline'>
|
||||
<a href='/guides' class='text-gray-400 hover:text-white'>Guides</a>
|
||||
</li>
|
||||
<li class='hidden lg:inline'>
|
||||
<a href='/videos' class='text-gray-400 hover:text-white'>Videos</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='/videos' class='hidden lg:inline text-gray-400 hover:text-white'>Videos</a>
|
||||
<kbd data-command-menu class="hidden sm:flex items-center text-gray-400 border border-gray-800 rounded-md px-2.5 py-1 text-sm hover:bg-gray-800 hover:cursor-pointer">
|
||||
<Icon icon='search' class='h-3 w-3 mr-2' />
|
||||
<kbd class='font-sans mr-1'>⌘</kbd><kbd class='font-sans'>K</kbd>
|
||||
</kbd>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class='hidden h-8 w-[172px] items-center justify-end gap-5 sm:flex'>
|
||||
@@ -93,10 +98,10 @@ import AccountDropdown from './AccountDropdown.astro';
|
||||
<!-- Links for logged in users -->
|
||||
<li data-auth-required class='hidden'>
|
||||
<a
|
||||
href='/settings/update-profile'
|
||||
href='/account'
|
||||
class='text-xl hover:text-blue-300 md:text-lg'
|
||||
>
|
||||
Settings
|
||||
Account
|
||||
</a>
|
||||
</li>
|
||||
<li data-auth-required class='hidden'>
|
||||
@@ -110,7 +115,7 @@ import AccountDropdown from './AccountDropdown.astro';
|
||||
<li>
|
||||
<a
|
||||
data-guest-required
|
||||
href='/signup'
|
||||
href='/login'
|
||||
class='hidden text-xl text-white md:text-lg'
|
||||
>
|
||||
Login
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import { handleAuthRequired } from '../Authenticator/authenticator';
|
||||
import {TOKEN_COOKIE_NAME} from "../../lib/jwt";
|
||||
import { TOKEN_COOKIE_NAME } from "../../lib/jwt";
|
||||
|
||||
export function logout() {
|
||||
Cookies.remove(TOKEN_COOKIE_NAME);
|
||||
@@ -34,6 +33,12 @@ function bindEvents() {
|
||||
.querySelector('[data-account-dropdown]')
|
||||
?.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
document
|
||||
.querySelector('[data-command-menu]')
|
||||
?.addEventListener('click', () => {
|
||||
window.dispatchEvent(new CustomEvent('command.k'));
|
||||
});
|
||||
}
|
||||
|
||||
bindEvents();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useStore } from '@nanostores/preact';
|
||||
import { useIsFirstRender } from '../hooks/use-is-first-render';
|
||||
import SpinnerIcon from '../icons/spinner.svg';
|
||||
import { pageLoadingMessage } from '../stores/page';
|
||||
import { pageProgressMessage } from '../stores/page';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
|
||||
export interface Props {
|
||||
initialMessage: string;
|
||||
@@ -9,14 +9,20 @@ export interface Props {
|
||||
|
||||
export function PageProgress(props: Props) {
|
||||
const { initialMessage } = props;
|
||||
const [message, setMessage] = useState(initialMessage);
|
||||
|
||||
const isFirstRender = useIsFirstRender();
|
||||
const $pageLoadingMessage = useStore(pageLoadingMessage);
|
||||
const $pageProgressMessage = useStore(pageProgressMessage);
|
||||
|
||||
if (!$pageLoadingMessage) {
|
||||
if (!initialMessage || !isFirstRender) {
|
||||
return null;
|
||||
useEffect(() => {
|
||||
if ($pageProgressMessage === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
setMessage($pageProgressMessage);
|
||||
}, [$pageProgressMessage]);
|
||||
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -30,7 +36,7 @@ export function PageProgress(props: Props) {
|
||||
className="h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-4 sm:w-4"
|
||||
/>
|
||||
<h1 className="ml-2">
|
||||
{$pageLoadingMessage || initialMessage}
|
||||
{message}
|
||||
<span className="animate-pulse">...</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@ const roadmapTitle =
|
||||
class='mr-0.5 rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900'
|
||||
>New</span
|
||||
>
|
||||
Resources are here, try clicking nodes
|
||||
Track your progress and learn by clicking roadmap items.
|
||||
</p>
|
||||
|
||||
<a
|
||||
@@ -72,9 +72,6 @@ const roadmapTitle =
|
||||
<p
|
||||
class='relative block rounded-md border border-yellow-500 bg-white px-2 py-1.5 text-sm text-yellow-700 sm:hidden'
|
||||
>
|
||||
Click roadmap items for resources or visit{' '}
|
||||
<a href={`/${roadmapId}/topics`} class='text-blue-700 underline'>
|
||||
resources list</a
|
||||
>.
|
||||
Track your progress and learn about the topics by clicking the roadmap items.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
---
|
||||
import Icon from '../AstroIcon.astro';
|
||||
const { pageUrl, name } = Astro.props;
|
||||
|
||||
export interface Props {
|
||||
pageUrl: string;
|
||||
name: string;
|
||||
}
|
||||
---
|
||||
|
||||
<div
|
||||
class='container flex min-h-[calc(100vh-37px-70px)] items-stretch sm:min-h-[calc(100vh-37px-96px)]'
|
||||
>
|
||||
<aside class='hidden w-56 border-r border-slate-200 py-10 pr-5 md:block'>
|
||||
<nav>
|
||||
<ul class='space-y-1'>
|
||||
<li>
|
||||
<a
|
||||
href='/settings/update-profile'
|
||||
class=`block w-full rounded px-2 py-1.5 font-regular text-slate-900 hover:bg-slate-100 ${pageUrl === 'profile' ? 'bg-slate-100' : ''}`
|
||||
>Profile</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href='/settings/update-password'
|
||||
class=`block w-full rounded px-2 py-1.5 font-regular text-slate-900 hover:bg-slate-100 ${pageUrl === 'change-password' ? 'bg-slate-100' : ''}`
|
||||
>Security</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<div class='grow py-10 pl-0 md:p-10 md:pr-0'>
|
||||
<div class='relative mb-5 md:hidden'>
|
||||
<button
|
||||
class='flex h-10 w-full items-center justify-between rounded-md bg-slate-800 px-2 text-center font-medium text-slate-100'
|
||||
id='settings-menu'
|
||||
>
|
||||
{name}
|
||||
<Icon icon='dropdown' />
|
||||
</button>
|
||||
<ul
|
||||
id='settings-menu-dropdown'
|
||||
class='absolute mt-1 hidden w-full space-y-1.5 rounded-md bg-white p-2 shadow-lg'
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href='/settings/update-profile'
|
||||
class=`block w-full rounded px-2 py-1.5 font-medium text-slate-900 hover:bg-slate-200 ${pageUrl === 'profile' ? 'bg-slate-100' : ''}`
|
||||
>Profile</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href='/settings/update-password'
|
||||
class=`block w-full rounded px-2 py-1.5 font-medium text-slate-900 hover:bg-slate-200 ${pageUrl === 'change-password' ? 'bg-slate-100' : ''}`
|
||||
>Change password</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const menuButton = document.getElementById('settings-menu');
|
||||
const menuDropdown = document.getElementById('settings-menu-dropdown');
|
||||
|
||||
menuButton?.addEventListener('click', () => {
|
||||
menuDropdown?.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!menuButton?.contains(e.target as Node)) {
|
||||
menuDropdown?.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
226
src/components/TopicDetail/ContributionForm.tsx
Normal file
226
src/components/TopicDetail/ContributionForm.tsx
Normal file
@@ -0,0 +1,226 @@
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import { httpPost } from '../../lib/http';
|
||||
|
||||
type ContributionInputProps = {
|
||||
id: number;
|
||||
title: string;
|
||||
link: string;
|
||||
isLast: boolean;
|
||||
totalCount: number;
|
||||
onAdd: () => void;
|
||||
onRemove: () => void;
|
||||
onChange: (link: { id: number; title: string; link: string }) => void;
|
||||
};
|
||||
|
||||
function ContributionInput(props: ContributionInputProps) {
|
||||
const {
|
||||
isLast,
|
||||
totalCount,
|
||||
onAdd,
|
||||
onRemove,
|
||||
onChange,
|
||||
id,
|
||||
title: defaultTitle,
|
||||
link: defaultLink,
|
||||
} = props;
|
||||
const titleRef = useRef<HTMLInputElement>(null);
|
||||
const [focused, setFocused] = useState('');
|
||||
const [title, setTitle] = useState(defaultTitle);
|
||||
const [link, setLink] = useState(defaultLink);
|
||||
|
||||
useEffect(() => {
|
||||
if (!titleRef?.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
titleRef.current.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
onChange({ id, title, link });
|
||||
}, [title, link]);
|
||||
|
||||
const canAddMore = isLast && totalCount < 5;
|
||||
|
||||
return (
|
||||
<div className="relative mb-3 rounded-md border p-3">
|
||||
<p
|
||||
className={`mb-1 text-xs uppercase ${
|
||||
focused === 'title' ? 'text-black' : 'text-gray-400'
|
||||
}`}
|
||||
>
|
||||
Resource Title
|
||||
</p>
|
||||
<input
|
||||
ref={titleRef}
|
||||
type="text"
|
||||
required
|
||||
className="block w-full rounded-md border p-2 text-sm focus:border-gray-400 focus:outline-none"
|
||||
placeholder="e.g. Introduction to RESTful APIs"
|
||||
onFocus={() => setFocused('title')}
|
||||
onBlur={() => setFocused('')}
|
||||
onChange={(e) => setTitle((e.target as any).value)}
|
||||
/>
|
||||
<p
|
||||
className={`mb-1 mt-3 text-xs uppercase ${
|
||||
focused === 'link' ? 'text-black' : 'text-gray-400'
|
||||
}`}
|
||||
>
|
||||
Resource Link
|
||||
</p>
|
||||
<input
|
||||
type="url"
|
||||
required
|
||||
className="block w-full rounded-md border p-2 text-sm focus:border-gray-400 focus:outline-none"
|
||||
placeholder="e.g. https://roadmap.sh/guides/some-url"
|
||||
onFocus={() => setFocused('link')}
|
||||
onBlur={() => setFocused('')}
|
||||
onChange={(e) => setLink((e.target as any).value)}
|
||||
/>
|
||||
|
||||
<div className="mb-0 mt-3 flex gap-3">
|
||||
{totalCount !== 1 && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onRemove();
|
||||
}}
|
||||
className="rounded-md text-sm font-semibold text-red-500 underline underline-offset-2 hover:text-red-800"
|
||||
>
|
||||
- Remove Link
|
||||
</button>
|
||||
)}
|
||||
|
||||
{canAddMore && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onAdd();
|
||||
}}
|
||||
className="rounded-md text-sm font-semibold text-gray-600 underline underline-offset-2 hover:text-black"
|
||||
>
|
||||
+ Add another Link
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ContributionFormProps = {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
topicId: string;
|
||||
onClose: (message?: string) => void;
|
||||
};
|
||||
|
||||
export function ContributionForm(props: ContributionFormProps) {
|
||||
const { onClose, resourceType, resourceId, topicId } = props;
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [links, setLinks] = useState<
|
||||
{ id: number; title: string; link: string }[]
|
||||
>([
|
||||
{
|
||||
id: new Date().getTime(),
|
||||
title: '',
|
||||
link: '',
|
||||
},
|
||||
]);
|
||||
|
||||
async function onSubmit(e: any) {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
const { response, error } = await httpPost(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-contribute-link`,
|
||||
{
|
||||
resourceType,
|
||||
resourceId,
|
||||
topicId,
|
||||
links,
|
||||
}
|
||||
);
|
||||
|
||||
setIsSubmitting(false);
|
||||
|
||||
if (!response || error) {
|
||||
alert(error?.message || 'Something went wrong. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
onClose('Thanks for your contribution! We will review it shortly.');
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-2 mt-2 rounded-md border bg-gray-100 p-3">
|
||||
<h1 className="mb-2 text-2xl font-bold">Guidelines</h1>
|
||||
<ul class="flex flex-col gap-1 text-sm text-gray-700">
|
||||
<li>Content should only be in English.</li>
|
||||
<li>Do not add things you have not evaluated personally.</li>
|
||||
<li>It should strictly be relevant to the topic.</li>
|
||||
<li>It should not be paid or behind a signup.</li>
|
||||
<li>
|
||||
Quality over quantity. Smaller set of quality links is preferred.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form onSubmit={onSubmit}>
|
||||
{links.map((link, counter) => (
|
||||
<ContributionInput
|
||||
key={link.id}
|
||||
id={link.id}
|
||||
title={link.title}
|
||||
link={link.link}
|
||||
isLast={counter === links.length - 1}
|
||||
totalCount={links.length}
|
||||
onChange={(newLink) => {
|
||||
setLinks(
|
||||
links.map((l) => {
|
||||
if (l.id === link.id) {
|
||||
return newLink;
|
||||
}
|
||||
|
||||
return l;
|
||||
})
|
||||
);
|
||||
}}
|
||||
onRemove={() => {
|
||||
setLinks(links.filter((l) => l.id !== link.id));
|
||||
}}
|
||||
onAdd={() => {
|
||||
setLinks([
|
||||
...links,
|
||||
{
|
||||
id: new Date().getTime(),
|
||||
title: '',
|
||||
link: '',
|
||||
},
|
||||
]);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className="block w-full rounded-md bg-gray-800 p-2 text-sm text-white hover:bg-black disabled:cursor-not-allowed disabled:bg-gray-400"
|
||||
>
|
||||
{isSubmitting ? 'Please wait ...' : 'Submit'}
|
||||
</button>
|
||||
<button
|
||||
className="block w-full rounded-md border border-red-500 p-2 text-sm text-red-600 hover:bg-red-600 hover:text-white"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,12 +14,15 @@ import {
|
||||
ResourceType,
|
||||
updateResourceProgress as updateResourceProgressApi,
|
||||
} from '../../lib/resource-progress';
|
||||
import { pageLoadingMessage, sponsorHidden } from '../../stores/page';
|
||||
import { pageProgressMessage, sponsorHidden } from '../../stores/page';
|
||||
import { TopicProgressButton } from './TopicProgressButton';
|
||||
import { ContributionForm } from './ContributionForm';
|
||||
|
||||
export function TopicDetail() {
|
||||
const [contributionAlertMessage, setContributionAlertMessage] = useState('');
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isContributing, setIsContributing] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [topicHtml, setTopicHtml] = useState('');
|
||||
|
||||
@@ -45,14 +48,15 @@ export function TopicDetail() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Close the topic detail when user clicks outside the topic detail
|
||||
useOutsideClick(topicRef, () => {
|
||||
setIsActive(false);
|
||||
setIsContributing(false);
|
||||
});
|
||||
|
||||
useKeydown('Escape', () => {
|
||||
setIsActive(false);
|
||||
setIsContributing(false);
|
||||
});
|
||||
|
||||
// Toggle topic is available even if the component UI is not active
|
||||
@@ -64,7 +68,7 @@ export function TopicDetail() {
|
||||
return;
|
||||
}
|
||||
|
||||
pageLoadingMessage.set('Updating');
|
||||
pageProgressMessage.set('Updating');
|
||||
|
||||
// Toggle the topic status
|
||||
isTopicDone({ topicId, resourceId, resourceType })
|
||||
@@ -89,7 +93,7 @@ export function TopicDetail() {
|
||||
console.error(err);
|
||||
})
|
||||
.finally(() => {
|
||||
pageLoadingMessage.set('');
|
||||
pageProgressMessage.set('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,6 +103,7 @@ export function TopicDetail() {
|
||||
setIsActive(true);
|
||||
sponsorHidden.set(true);
|
||||
|
||||
setContributionAlertMessage('');
|
||||
setTopicId(topicId);
|
||||
setResourceType(resourceType);
|
||||
setResourceId(resourceId);
|
||||
@@ -142,10 +147,6 @@ export function TopicDetail() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const contributionDir =
|
||||
resourceType === 'roadmap' ? 'roadmaps' : 'best-practices';
|
||||
const contributionUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/${contributionDir}/${resourceId}/content`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
@@ -162,7 +163,22 @@ export function TopicDetail() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !error && (
|
||||
{!isLoading && isContributing && (
|
||||
<ContributionForm
|
||||
resourceType={resourceType}
|
||||
resourceId={resourceId}
|
||||
topicId={topicId}
|
||||
onClose={(message?: string) => {
|
||||
if (message) {
|
||||
setContributionAlertMessage(message);
|
||||
}
|
||||
|
||||
setIsContributing(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isContributing && !isLoading && !error && (
|
||||
<>
|
||||
{/* Actions for the topic */}
|
||||
<div className="mb-2">
|
||||
@@ -173,6 +189,7 @@ export function TopicDetail() {
|
||||
onShowLoginPopup={showLoginPopup}
|
||||
onClose={() => {
|
||||
setIsActive(false);
|
||||
setIsContributing(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -180,7 +197,10 @@ export function TopicDetail() {
|
||||
type="button"
|
||||
id="close-topic"
|
||||
className="absolute right-2.5 top-2.5 inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900"
|
||||
onClick={() => setIsActive(false)}
|
||||
onClick={() => {
|
||||
setIsActive(false);
|
||||
setIsContributing(false);
|
||||
}}
|
||||
>
|
||||
<img alt="Close" class="h-5 w-5" src={CloseIcon} />
|
||||
</button>
|
||||
@@ -193,20 +213,29 @@ export function TopicDetail() {
|
||||
dangerouslySetInnerHTML={{ __html: topicHtml }}
|
||||
></div>
|
||||
|
||||
<p
|
||||
id="contrib-meta"
|
||||
class="mt-10 border-t pt-3 text-sm leading-relaxed text-gray-400"
|
||||
>
|
||||
Contribute links to learning resources about this topic{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
class="text-blue-700 underline"
|
||||
href={contributionUrl}
|
||||
{/* Contribution */}
|
||||
<div className="mt-8 flex-1 border-t">
|
||||
<p class="mb-2 mt-2 text-sm leading-relaxed text-gray-400">
|
||||
Help others learn by submitting links to learn more about this topic{' '}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (isGuest) {
|
||||
setIsActive(false);
|
||||
showLoginPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
setIsContributing(true);
|
||||
}}
|
||||
disabled={!!contributionAlertMessage}
|
||||
className="block w-full rounded-md bg-gray-800 p-2 text-sm text-white transition-colors hover:bg-black hover:text-white disabled:bg-green-200 disabled:text-black"
|
||||
>
|
||||
on GitHub repository.
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
{contributionAlertMessage
|
||||
? contributionAlertMessage
|
||||
: 'Submit a Link'}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { httpGet, httpPost } from '../../lib/http';
|
||||
import { pageLoadingMessage } from '../../stores/page';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
|
||||
export default function UpdatePasswordForm() {
|
||||
const [authProvider, setAuthProvider] = useState('');
|
||||
@@ -72,15 +72,17 @@ export default function UpdatePasswordForm() {
|
||||
|
||||
useEffect(() => {
|
||||
loadProfile().finally(() => {
|
||||
pageLoadingMessage.set('');
|
||||
pageProgressMessage.set('');
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h2 className="text-3xl font-bold sm:text-4xl">Password</h2>
|
||||
<p className="mt-2">Use the form below to update your password.</p>
|
||||
<div className="mt-8 space-y-4">
|
||||
<div class="hidden md:block mb-8">
|
||||
<h2 className="text-3xl font-bold sm:text-4xl">Password</h2>
|
||||
<p className="mt-2">Use the form below to update your password.</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{authProvider === 'email' && (
|
||||
<div className="flex w-full flex-col">
|
||||
<label
|
||||
@@ -132,7 +134,7 @@ export default function UpdatePasswordForm() {
|
||||
for="new-password-confirmation"
|
||||
className="text-sm leading-none text-slate-500"
|
||||
>
|
||||
New Password Confirm
|
||||
Confirm New Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
@@ -141,7 +143,7 @@ export default function UpdatePasswordForm() {
|
||||
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
required
|
||||
minLength={6}
|
||||
placeholder="New password confirm"
|
||||
placeholder="Confirm New Password"
|
||||
value={newPasswordConfirmation}
|
||||
onInput={(e) =>
|
||||
setNewPasswordConfirmation((e.target as HTMLInputElement).value)
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { httpGet, httpPost } from '../../lib/http';
|
||||
import { pageLoadingMessage } from '../../stores/page';
|
||||
import UploadProfilePicture from '../Profile/UploadProfilePicture';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import UploadProfilePicture from './UploadProfilePicture';
|
||||
|
||||
export function UpdateProfileForm() {
|
||||
const [name, setName] = useState('');
|
||||
@@ -75,14 +75,16 @@ export function UpdateProfileForm() {
|
||||
// Make a request to the backend to fill in the form with the current values
|
||||
useEffect(() => {
|
||||
loadProfile().finally(() => {
|
||||
pageLoadingMessage.set('');
|
||||
pageProgressMessage.set('');
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold sm:text-4xl">Profile</h2>
|
||||
<p className="mt-2">Update your profile details below.</p>
|
||||
<div className="mb-8 hidden md:block">
|
||||
<h2 className="text-3xl font-bold sm:text-4xl">Profile</h2>
|
||||
<p className="mt-2">Update your profile details below.</p>
|
||||
</div>
|
||||
<UploadProfilePicture
|
||||
avatarUrl={
|
||||
avatar
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import Cookies from 'js-cookie';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
|
||||
import { httpCall, httpPost } from '../../lib/http';
|
||||
|
||||
interface PreviewFile extends File {
|
||||
preview: string;
|
||||
@@ -131,7 +130,7 @@ export default function UploadProfilePicture(props: UploadProfilePictureProps) {
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
encType="multipart/form-data"
|
||||
className="mt-8 flex flex-col gap-2"
|
||||
className="flex flex-col gap-2"
|
||||
>
|
||||
<label htmlFor="avatar" className="text-sm leading-none text-slate-500">
|
||||
Profile Picture
|
||||
@@ -1,4 +1,4 @@
|
||||
# Disable Entinty Parsing in XML
|
||||
# Disable Entity Parsing in XML
|
||||
|
||||
> Disable entity parsing if you are parsing XML to avoid XXE attacks
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
> Get your alerts to become notifications.
|
||||
|
||||
If you've set everyting up correctly, your health checks should automatically destroy bad instances and spawn new ones. There's usually no action to take when getting a CloudWatch alert, as everything should be automated. If you're getting alerts where manual intervention is required, do a post-mortem and figure out if there's a way you can automate the action in future. The last time I had an actionable alert from CloudWatch was about a year ago, and it's extremely awesome not to be woken up at 4am for ops alerts any more.
|
||||
If you've set everything up correctly, your health checks should automatically destroy bad instances and spawn new ones. There's usually no action to take when getting a CloudWatch alert, as everything should be automated. If you're getting alerts where manual intervention is required, do a post-mortem and figure out if there's a way you can automate the action in future. The last time I had an actionable alert from CloudWatch was about a year ago, and it's extremely awesome not to be woken up at 4am for ops alerts any more.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# App Changes for AWS
|
||||
|
||||
While a lot of applications can probably just be deployed to an EC2 instance and work well, if you're coming from a physical environment, you may need to re-architect your application in order to accomodate changes. Don't just think you can copy the files over and be done with it.
|
||||
While a lot of applications can probably just be deployed to an EC2 instance and work well, if you're coming from a physical environment, you may need to re-architect your application in order to accommodate changes. Don't just think you can copy the files over and be done with it.
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
> Don't give servers static/elastic IPs.
|
||||
|
||||
For a typical web application, you should put things behind a load balancer, and balance them between AZs. There are a few cases where Elastic IPs will probably need to be used, but in order to make best use of auto-scaling you'll want to use a load balancer instad of giving every instance their own unique IP.
|
||||
For a typical web application, you should put things behind a load balancer, and balance them between AZs. There are a few cases where Elastic IPs will probably need to be used, but in order to make best use of auto-scaling you'll want to use a load balancer instead of giving every instance their own unique IP.
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
> Set up granular billing alerts.
|
||||
|
||||
You should always have at least one billing alert set up, but that will only tell you on a monthly basis once you've exceeded your allowance. If you want to catch runaway billing early, you need a more fine grained approach. The way I do it is to set up an alert for my expected usage each week. So the first week's alert for say $1,000, the second for $2,000, third for $3,000, etc. If the week-2 alarm goes off before the 14th/15th of the month, then I know something is probably going wrong. For even more fine-grained control, you can set this up for each individual service, that way you instantly know which service is causing the problem. This could be useful if your usage on one service is quite steady month-to-month, but another is more erratic. Have the indidividual weekly alerts for the steady one, but just an overall one for the more erratic one. If everything is steady, then this is probably overkill, as looking at CloudWatch will quickly tell you which service is the one causing the problem.
|
||||
You should always have at least one billing alert set up, but that will only tell you on a monthly basis once you've exceeded your allowance. If you want to catch runaway billing early, you need a more fine grained approach. The way I do it is to set up an alert for my expected usage each week. So the first week's alert for say $1,000, the second for $2,000, third for $3,000, etc. If the week-2 alarm goes off before the 14th/15th of the month, then I know something is probably going wrong. For even more fine-grained control, you can set this up for each individual service, that way you instantly know which service is causing the problem. This could be useful if your usage on one service is quite steady month-to-month, but another is more erratic. Have the individual weekly alerts for the steady one, but just an overall one for the more erratic one. If everything is steady, then this is probably overkill, as looking at CloudWatch will quickly tell you which service is the one causing the problem.
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
> Scale down on INSUFFICIENT_DATA as well as ALARM.
|
||||
|
||||
For your scale-down action, make sure to trigger a scale-down event when there's no metric data, as well as when your trigger goes off. For example, if you have an app which usually has very low traffic, but experiences occasional spikes, you want to be sure that it scales down once the spike is over and the traffic stops. If there's no traffic, you'll get `INSUFFIFIENT_DATA` instead of `ALARM` for your low traffic threshold and it won't trigger a scale-down action.
|
||||
For your scale-down action, make sure to trigger a scale-down event when there's no metric data, as well as when your trigger goes off. For example, if you have an app which usually has very low traffic, but experiences occasional spikes, you want to be sure that it scales down once the spike is over and the traffic stops. If there's no traffic, you'll get `INSUFFICIENT_DATA` instead of `ALARM` for your low traffic threshold and it won't trigger a scale-down action.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Use Official SDKs
|
||||
|
||||
> If you need to interact with AWS, use the SDK for your langauge.
|
||||
> If you need to interact with AWS, use the SDK for your language.
|
||||
|
||||
Don't try to roll your own, I did this at first as I only needed a simple upload to S3, but then you add more services and it's just an all around bad idea. [The AWS SDKs](http://aws.amazon.com/tools/) are well written, handle authentication automatically, handle retry logic, and they're maintained and iterated on by Amazon. Also, if you use EC2 IAM roles (which you absolutely should, more on this later) then the SDK will automatically grab the correct credentials for you.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Use VPC
|
||||
|
||||
Setting up a VPC seems like a pain at first, but once you get stuck in and play with it, it's suprising easy to set up and get going. It provides all sorts of extra features over EC2 that are well worth the extra time it takes to set up a VPC. First, you can control traffic at the network level using ACLs, you can modify instance size, security groups, etc. without needing to terminate an instance. You can specify egress firewall rules (you cannot control outbound traffic from normal EC2). But the biggest thing is that you have your own private subnet where your instances are completely cut off from everyone else, so it adds an extra layer of protection.
|
||||
Setting up a VPC seems like a pain at first, but once you get stuck in and play with it, it's surprising easy to set up and get going. It provides all sorts of extra features over EC2 that are well worth the extra time it takes to set up a VPC. First, you can control traffic at the network level using ACLs, you can modify instance size, security groups, etc. without needing to terminate an instance. You can specify egress firewall rules (you cannot control outbound traffic from normal EC2). But the biggest thing is that you have your own private subnet where your instances are completely cut off from everyone else, so it adds an extra layer of protection.
|
||||
|
||||
If you're interested in the internals of VPC, I highly recommend watching [A Day in the Life of Billion Packets](http://www.youtube.com/watch?v=Zd5hsL-JNY4) ([Slides](https://www.slideshare.net/AmazonWebServices/a-day-in-the-life-of-a-billion-packets-cpn401-aws-reinvent-2013)).
|
||||
|
||||
@@ -7,6 +7,6 @@ When CSS files are minified, the content is loaded faster and less data is sent
|
||||
Use tools to minify your files automatically before or during your build or your deployment.
|
||||
|
||||
- [cssnano: A modular minifier based on the PostCSS ecosystem. - cssnano](https://cssnano.co/)
|
||||
- [CSS Minfier](https://goonlinetools.com/css-minifier/)
|
||||
- [CSS Minifier](https://goonlinetools.com/css-minifier/)
|
||||
- [@neutrinojs/style-minify - npm](https://www.npmjs.com/package/@neutrinojs/style-minify)
|
||||
- [Online CSS Compressor](http://refresh-sf.com)
|
||||
|
||||
@@ -8,7 +8,7 @@ author:
|
||||
seo:
|
||||
title: 'Consistency Patterns - roadmap.sh'
|
||||
description: 'Everything you need to know about Week, Strong and Eventual Consistency'
|
||||
isNew: true
|
||||
isNew: false
|
||||
canonicalUrl: 'https://cs.fyi/guide/consistency-patterns-week-strong-eventual/'
|
||||
type: 'textual'
|
||||
date: 2023-01-18
|
||||
@@ -61,7 +61,7 @@ An example of strong consistency is a financial system where users can transfer
|
||||
|
||||
In a weakly consistent system, updates to the data may not be immediately propagated. This can lead to inconsistencies and conflicts between different versions of the data, but it also allows for **high availability and low latency**.
|
||||
|
||||
Another example of weak consistency is a gaming platform where users can play online multiplayer games. When a user plays a game, their actions are immediately visible to other players in the same data center, but if there was a lag or temporary connectoin loss, the actions may not be seen by some of the users and the game will continue. This can lead to inconsistencies between different versions of the game state, but it also allows for a high level of availability and low latency.
|
||||
Another example of weak consistency is a gaming platform where users can play online multiplayer games. When a user plays a game, their actions are immediately visible to other players in the same data center, but if there was a lag or temporary connection loss, the actions may not be seen by some of the users and the game will continue. This can lead to inconsistencies between different versions of the game state, but it also allows for a high level of availability and low latency.
|
||||
|
||||
### Eventual Consistency
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ author:
|
||||
seo:
|
||||
title: 'Jump Servers: What, Why and How - roadmap.sh'
|
||||
description: 'Learn what is a Jump Server and how to set it up for SSH access.'
|
||||
isNew: true
|
||||
isNew: false
|
||||
type: 'textual'
|
||||
date: 2023-03-20
|
||||
sitemap:
|
||||
|
||||
@@ -8,7 +8,7 @@ author:
|
||||
seo:
|
||||
title: "Guide to Let's Encrypt SSL Setup - roadmap.sh"
|
||||
description: "Learn how to protect your website using Let's Encrypt SSL Certificates."
|
||||
isNew: true
|
||||
isNew: false
|
||||
type: 'textual'
|
||||
date: 2023-03-13
|
||||
sitemap:
|
||||
|
||||
@@ -8,7 +8,7 @@ author:
|
||||
seo:
|
||||
title: 'Single Command Database Setup - roadmap.sh'
|
||||
description: 'Learn how to run MySQL, PostgreSQL, or MongoDB in Docker with single Command'
|
||||
isNew: true
|
||||
isNew: false
|
||||
type: 'textual'
|
||||
date: 2023-02-27
|
||||
sitemap:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
byte-byte-go: https://blog.bytebytego.com/archive
|
||||
speedup-js: https://marvinh.dev/blog/speeding-up-javascript-ecosystem/
|
||||
23-min-ts: https://www.youtube.com/watch?v=YmxwicpROps
|
||||
bun-vs-node: https://www.youtube.com/watch?v=qCX8rw4qOSA
|
||||
|
||||
@@ -7,4 +7,4 @@ Use pipes to transform strings, currency amounts, dates, and other data for disp
|
||||
Visit the following resources to learn more:
|
||||
|
||||
- [Understanding BuiltIn Pipes](https://angular.io/guide/pipes)
|
||||
- [BuiltIn Pipes - exampls](https://codecraft.tv/courses/angular/pipes/built-in-pipes/)
|
||||
- [BuiltIn Pipes - examples](https://codecraft.tv/courses/angular/pipes/built-in-pipes/)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Forms are used to handle user inputs in many applications. It enables users from entering sensitive information to performing several data entry tasks.
|
||||
|
||||
Angular provides two approachs to handle user inputs trough forms: reactive and template-driven forms.
|
||||
Angular provides two approaches to handle user inputs trough forms: reactive and template-driven forms.
|
||||
|
||||
Visit the following resources to learn more:
|
||||
|
||||
|
||||
@@ -11,3 +11,4 @@ Visit the following resources to learn more:
|
||||
- [HTTP/3 From A To Z: Core Concepts](https://www.smashingmagazine.com/2021/08/http3-core-concepts-part1/)
|
||||
- [HTTP/1 to HTTP/2 to HTTP/3](https://www.youtube.com/watch?v=a-sBfyiXysI)
|
||||
- [HTTP Crash Course & Exploration](https://www.youtube.com/watch?v=iYM2zFP3Zn0)
|
||||
- [SSL, TLS, HTTPS Explained](https://www.youtube.com/watch?v=j9qmmewmcfo)
|
||||
@@ -6,3 +6,4 @@ Visit the following resources to learn more:
|
||||
|
||||
- [Command line crash course](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line)
|
||||
- [Basic Terminal Usage - Cheat Sheet to make the command line EASY](https://www.youtube.com/watch?v=jDINUSK7rXE)
|
||||
- [50+ Linux Commands You Must Know](https://www.digitalocean.com/community/tutorials/linux-commands)
|
||||
@@ -7,3 +7,5 @@ Visit the following resources to learn more:
|
||||
- [Git & GitHub Crash Course For Beginners](https://www.youtube.com/watch?v=SWYqp7iY_Tc)
|
||||
- [Learn Git with Tutorials, News and Tips - Atlassian](https://www.atlassian.com/git)
|
||||
- [Git Cheat Sheet](https://cs.fyi/guide/git-cheatsheet)
|
||||
- [Learn Git Branching](https://learngitbranching.js.org/)
|
||||
- [Git Tutorial](https://www.w3schools.com/git/)
|
||||
@@ -7,3 +7,4 @@ Visit the following resources to learn more:
|
||||
- [MySQL website](https://www.mysql.com/)
|
||||
- [W3Schools - MySQL tutorial ](https://www.w3schools.com/mySQl/default.asp)
|
||||
- [MySQL tutorial for beginners](https://www.youtube.com/watch?v=7S_tz1z_5bA)
|
||||
- [MySQL for Developers](https://planetscale.com/courses/mysql-for-developers/introduction/course-introduction)
|
||||
@@ -1,6 +1,6 @@
|
||||
# Observability
|
||||
|
||||
In sofware development, observability is the measure of how well we can understand a system from the work it does, and how to make it better.
|
||||
In software development, observability is the measure of how well we can understand a system from the work it does, and how to make it better.
|
||||
|
||||
So what makes a system to be "observable"? It is its ability of producing and collecting metrics, logs and traces in order for us to understand what happens under the hood and identify issues and bottlenecks faster.
|
||||
|
||||
|
||||
1
src/data/roadmaps/code-review/code-review.json
Normal file
1
src/data/roadmaps/code-review/code-review.json
Normal file
File diff suppressed because one or more lines are too long
47
src/data/roadmaps/code-review/code-review.md
Normal file
47
src/data/roadmaps/code-review/code-review.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
jsonUrl: '/jsons/roadmaps/code-review.json'
|
||||
pdfUrl: '/pdfs/roadmaps/code-review.pdf'
|
||||
order: 14
|
||||
briefTitle: 'Code Review'
|
||||
briefDescription: 'Learn what to focus on when conducting a code review.'
|
||||
title: 'Code Review Pyramid'
|
||||
description: 'Learn what to focus on when conducting a code review.'
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
dimensions:
|
||||
width: 968
|
||||
height: 506.06
|
||||
schema:
|
||||
headline: 'Code Review Guide'
|
||||
description: 'Learn what to review when conducting Code Reviews with this interactive guide. We also have resources and short descriptions attached to the roadmap items so you can get everything you want to learn in one place.'
|
||||
imageUrl: 'https://roadmap.sh/roadmaps/code-review.png'
|
||||
datePublished: '2023-06-03'
|
||||
dateModified: '2023-06-03'
|
||||
seo:
|
||||
title: 'Code Review - roadmap.sh'
|
||||
description: 'Learn what to review when conducting code reviews. We also have questions to ask yourself under each node of the pyramid to guide you further.'
|
||||
keywords:
|
||||
- 'code review tutorial'
|
||||
- 'code review guide'
|
||||
- 'code review for beginners'
|
||||
- 'code reviews'
|
||||
- 'code review best practices'
|
||||
- 'code review roadmap 2023'
|
||||
- 'guide to learning code review'
|
||||
- 'code review roadmap'
|
||||
- 'code review learning path'
|
||||
- 'code review learning roadmap'
|
||||
- 'what is code review'
|
||||
relatedRoadmaps:
|
||||
- 'backend'
|
||||
- 'frontend'
|
||||
- 'devops'
|
||||
- 'system-design'
|
||||
sitemap:
|
||||
priority: 1
|
||||
changefreq: 'monthly'
|
||||
tags:
|
||||
- 'roadmap'
|
||||
- 'main-sitemap'
|
||||
- 'skill-roadmap'
|
||||
---
|
||||
6
src/data/roadmaps/code-review/content/100-code-style.md
Normal file
6
src/data/roadmaps/code-review/content/100-code-style.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Questions to Ask
|
||||
|
||||
- Is the project's formatting style applied?
|
||||
- Does it adhere to the agreed-upon naming conventions?
|
||||
- Is it DRY?
|
||||
- Is the code sufficiently "readable" (method lengths, etc.)?
|
||||
7
src/data/roadmaps/code-review/content/101-tests.md
Normal file
7
src/data/roadmaps/code-review/content/101-tests.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Questions to Ask
|
||||
|
||||
- Are all tests passing?
|
||||
- Are new features reasonably tested?
|
||||
- Are corner cases tested?
|
||||
- Is it using unit tests where possible, integration tests where necessary?
|
||||
- Are there tests for NFRs, e.g. performance?
|
||||
@@ -0,0 +1,5 @@
|
||||
# Questions to Ask
|
||||
|
||||
- New features reasonably documented?
|
||||
- Are the relevant kinds of does covered: README, API docs, user quide, reference docs, etc?
|
||||
- Are docs understandable, are there no signiticant typos and grammar mistakes?
|
||||
@@ -0,0 +1,10 @@
|
||||
# Questions to Ask
|
||||
|
||||
- Does it satisfy the original requirements?
|
||||
- Is it logically correct?
|
||||
- Is there no unnecessary complexity?
|
||||
- Is it robust (no concurrency issues, proper error handling, etc.)?
|
||||
- Is it performant?
|
||||
- Is it secure e.g., no SQL injections, etc?
|
||||
- Is it observable e.g., metrics, logging, tracing, etc.?
|
||||
- Do newly added dependencies pull their weight? Is their license acceptable?
|
||||
@@ -0,0 +1,8 @@
|
||||
# Questions to Ask
|
||||
|
||||
- API as small as possible, as large as needed?
|
||||
- Is there one way of doing one thing, not multiple ones?
|
||||
- Is it consistent, does it follow the principle of least surprise?
|
||||
- Clean split of API/internals without internals leaking into the API?
|
||||
- Are there no breaking changes to user-facing parts (API classes, configuration, metrics, log formats, etc)?
|
||||
- Is a new API generally useful and not overly specific to a single use case?
|
||||
1
src/data/roadmaps/code-review/content/index.md
Normal file
1
src/data/roadmaps/code-review/content/index.md
Normal file
@@ -0,0 +1 @@
|
||||
#
|
||||
0
src/data/roadmaps/code-review/faqs.astro
Normal file
0
src/data/roadmaps/code-review/faqs.astro
Normal file
@@ -7,5 +7,5 @@ Visit the following resources to learn more:
|
||||
- [Quick Sort Algorithm](https://www.programiz.com/dsa/quick-sort)
|
||||
- [Quick Sort Algorithm - Geeks for Geeks](https://www.geeksforgeeks.org/quick-sort/)
|
||||
- [Quick Sort in 4 Minutes](https://www.youtube.com/watch?v=Hoixgm4-P4M&feature=youtu.be)
|
||||
- [Quick Sort Implementaiton in C](http://www.cs.yale.edu/homes/aspnes/classes/223/examples/randomization/quick.c)
|
||||
- [Quick Sort Implementation in C](http://www.cs.yale.edu/homes/aspnes/classes/223/examples/randomization/quick.c)
|
||||
- [Quick Sort Implementation in Python](https://github.com/jwasham/practice-python/blob/master/quick_sort/quick_sort.py)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# What is C++?
|
||||
|
||||
C++ is a general-purpose programming language created by Bjarne Stroustrup as an extension of the C programming language. It was first introduced in 1985 and provides object-oriented features like classes and inheritance. C++ is widely used in various applications like game development, system programming, embedded systems, and high-performance computing.
|
||||
|
||||
C++ is a statically-typed language, meaning that the type of a variable is determined during compilation, and has an extensive library called the C++ Standard Library, which provides a rich set of functions, algorithms, and data structures for various tasks.
|
||||
|
||||
C++ builds upon the features of C, and thus, most C programs can be compiled and run with a C++ compiler.
|
||||
|
||||
## Code Example
|
||||
|
||||
Here's a simple example of a C++ program that demonstrates some essential features of the language:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
// A simple function to add two numbers
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
class Calculator {
|
||||
public:
|
||||
// A member function to multiply two numbers
|
||||
int multiply(int a, int b) {
|
||||
return a * b;
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
int x = 5;
|
||||
int y = 3;
|
||||
|
||||
// Using the standalone function 'add'
|
||||
int sum = add(x, y);
|
||||
std::cout << "Sum: " << sum << std::endl;
|
||||
|
||||
// Using a class and member function
|
||||
Calculator calc;
|
||||
int product = calc.multiply(x, y);
|
||||
std::cout << "Product: " << product << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
In the above program, we define a simple function `add` and a class `Calculator` with a member function `multiply`. The `main` function demonstrates how to use these to perform basic arithmetic.
|
||||
|
||||
- [C++ Tutorial for Beginners - Full Course](https://youtu.be/vlnpwxzdw4y)
|
||||
@@ -0,0 +1,63 @@
|
||||
# Why C++
|
||||
C++ is a popular and widely used programming language for various reasons. Here are some of the reasons why you might choose to utilize C++:
|
||||
|
||||
## Performance
|
||||
|
||||
C++ is designed to provide high performance and efficiency. It offers fine-grained control over system resources, making it easier to optimize your software.
|
||||
|
||||
## Portability
|
||||
|
||||
C++ is supported on different computer architectures and operating systems, allowing you to write portable code that runs on various platforms without making major modifications.
|
||||
|
||||
## Object-Oriented Programming
|
||||
|
||||
C++ supports object-oriented programming (OOP) - a paradigm that allows you to design programs using classes and objects, leading to better code organization and reusability.
|
||||
|
||||
```cpp
|
||||
class MyClass {
|
||||
public:
|
||||
void myFunction() {
|
||||
// Code here
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
MyClass obj;
|
||||
obj.myFunction();
|
||||
}
|
||||
```
|
||||
|
||||
## Support for low-level and high-level programming
|
||||
|
||||
C++ allows you to write both low-level code, like memory manipulation, as well as high-level abstractions, like creating classes and using the Standard Template Library (STL).
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
// Low-level programming
|
||||
int number = 42;
|
||||
int* ptr_number = &number;
|
||||
|
||||
// High-level programming
|
||||
std::vector<int> myVector = {1, 2, 3};
|
||||
for(const auto &i: myVector) {
|
||||
std::cout << i << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Extensive Libraries
|
||||
|
||||
C++ offers a vast range of libraries and tools, such as the Standard Template Library (STL), Boost, and Qt, among others, that can aid in the development of your projects and make it more efficient.
|
||||
|
||||
## Combination with C language
|
||||
|
||||
C++ can be combined with C, offering the capabilities of both languages and allowing you to reuse your existing C code. By incorporating C++ features, you can enhance your code and improve its functionality.
|
||||
|
||||
## Active Community
|
||||
|
||||
C++ has been around for a long time and has a large, active community of users who contribute to the growth of the language, express new ideas, and engage in discussions that help develop the language further. This makes finding solutions to any problems you experience much easier.
|
||||
|
||||
In summary, C++ offers a great balance of performance, portability, and feature set, making it a versatile and powerful programming language suitable for many applications. With its extensive libraries, active community, and continuous development, C++ is an excellent choice for any software development project.
|
||||
@@ -0,0 +1,71 @@
|
||||
# C vs C++
|
||||
C and C++ are two popular programming languages with some similarities, but they also have key differences. C++ is an extension of the C programming language, with added features such as object-oriented programming, classes, and exception handling. Although both languages are used for similar tasks, they have their own syntax and semantics, which makes them distinct from each other.
|
||||
|
||||
## Syntax and Semantics
|
||||
|
||||
### C
|
||||
- C is a procedural programming language.
|
||||
- Focuses on functions and structured programming.
|
||||
- Does not support objects or classes.
|
||||
- Memory management is manual, using functions like `malloc` and `free`.
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
void printHello() {
|
||||
printf("Hello, World!\n");
|
||||
}
|
||||
|
||||
int main() {
|
||||
printHello();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### C++
|
||||
- C++ is both procedural and object-oriented.
|
||||
- Supports both functions and classes.
|
||||
- Incorporates different programming paradigms.
|
||||
- Memory management can be manual (like C) or rely on constructors/destructors and smart pointers.
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
class HelloWorld {
|
||||
public:
|
||||
void printHello() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
HelloWorld obj;
|
||||
obj.printHello();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Code Reusability and Modularity
|
||||
|
||||
### C
|
||||
- Code reusability is achieved through functions and modular programming.
|
||||
- High cohesion and low coupling are achieved via structured design.
|
||||
- Function libraries can be created and included through headers.
|
||||
|
||||
### C++
|
||||
- Offers better code reusability with classes, inheritance, and polymorphism.
|
||||
- Code modularity is enhanced through namespaces and well-designed object-oriented hierarchy.
|
||||
|
||||
## Error Handling
|
||||
|
||||
### C
|
||||
- Error handling in C is done primarily through return codes.
|
||||
- Lacks support for exceptions or any built-in error handling mechanism.
|
||||
|
||||
### C++
|
||||
- Offers exception handling, which can be used to handle errors that may occur during program execution.
|
||||
- Enables catching and handling exceptions with `try`, `catch`, and `throw` keywords, providing more control over error handling.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Both C and C++ are powerful languages with unique features and capabilities. While C is simpler and focuses on procedural programming, C++ offers the versatility of using different programming paradigms and improved code organization. Understanding the differences between these two languages can help you decide which one is more suitable for your specific needs and programming style.
|
||||
132
src/data/roadmaps/cpp/content/100-introduction/index.md
Normal file
132
src/data/roadmaps/cpp/content/100-introduction/index.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Introduction to C++
|
||||
|
||||
C++ is a general-purpose, high-performance programming language. It was developed by Bjarne Stroustrup at Bell Labs starting in 1979. C++ is an extension of the C programming language, adding features such as classes, objects, and exceptions.
|
||||
|
||||
## Basics of C++ Programming
|
||||
|
||||
Here are some basic components and concepts in C++ programming:
|
||||
|
||||
## Including Libraries
|
||||
|
||||
In C++, we use the `#include` directive to include libraries or header files into our program. For example, to include the standard input/output library, we write:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
```
|
||||
|
||||
## Main Function
|
||||
|
||||
The entry point of a C++ program is the `main` function. Every C++ program must have a `main` function:
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
// Your code goes here
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Input/Output
|
||||
|
||||
To perform input and output operations in C++, we can use the built-in objects `std::cin` for input and `std::cout` for output, available in the `iostream` library. Here's an example of reading an integer and printing its value:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int number;
|
||||
std::cout << "Enter an integer: ";
|
||||
std::cin >> number;
|
||||
std::cout << "You entered: " << number << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Variables and Data Types
|
||||
|
||||
C++ has several basic data types for representing integer, floating-point, and character values:
|
||||
|
||||
- `int`: integer values
|
||||
- `float`: single-precision floating-point values
|
||||
- `double`: double-precision floating-point values
|
||||
- `char`: single characters
|
||||
|
||||
Variables must be declared with a data type before they can be used:
|
||||
|
||||
```cpp
|
||||
int x;
|
||||
float y;
|
||||
double z;
|
||||
char c;
|
||||
```
|
||||
|
||||
## Control Structures
|
||||
|
||||
C++ provides control structures for conditional execution and iteration, such as `if`, `else`, `while`, `for`, and `switch` statements.
|
||||
|
||||
### If-Else Statement
|
||||
```cpp
|
||||
if (condition) {
|
||||
// Code to execute if the condition is true
|
||||
} else {
|
||||
// Code to execute if the condition is false
|
||||
}
|
||||
```
|
||||
|
||||
### While Loop
|
||||
```cpp
|
||||
while (condition) {
|
||||
// Code to execute while the condition is true
|
||||
}
|
||||
```
|
||||
|
||||
### For Loop
|
||||
```cpp
|
||||
for (initialization; condition; update) {
|
||||
// Code to execute while the condition is true
|
||||
}
|
||||
```
|
||||
|
||||
### Switch Statement
|
||||
```cpp
|
||||
switch (variable) {
|
||||
case value1:
|
||||
// Code to execute if variable == value1
|
||||
break;
|
||||
case value2:
|
||||
// Code to execute if variable == value2
|
||||
break;
|
||||
// More cases...
|
||||
default:
|
||||
// Code to execute if variable does not match any case value
|
||||
}
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
Functions are reusable blocks of code that can be called with arguments to perform a specific task. Functions are defined with a return type, a name, a parameter list, and a body.
|
||||
|
||||
```cpp
|
||||
ReturnType functionName(ParameterType1 parameter1, ParameterType2 parameter2) {
|
||||
// Function body
|
||||
// ...
|
||||
return returnValue;
|
||||
}
|
||||
```
|
||||
|
||||
For example, here's a function that adds two integers and returns the result:
|
||||
|
||||
```cpp
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int result = add(3, 4);
|
||||
std::cout << "3 + 4 = " << result << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
This basic introduction to C++ should provide you with a good foundation for further learning. Explore more topics such as classes, objects, inheritance, polymorphism, templates, and the Standard Template Library (STL) to deepen your understanding of C++ and start writing more advanced programs.
|
||||
|
||||
- [LearnC++](https://www.learncpp.com/)
|
||||
@@ -0,0 +1,49 @@
|
||||
# Installing C++
|
||||
Before you can start programming in C++, you will need to have a compiler installed on your system. A compiler is a program that converts the C++ code you write into an executable file that your computer can run. There are several popular C++ compilers to choose from, depending on your operating system and preference.
|
||||
|
||||
### Windows
|
||||
For Windows, one popular option is to install the [Microsoft Visual Studio IDE](https://visualstudio.microsoft.com/vs/), which includes the Microsoft Visual C++ compiler.
|
||||
|
||||
Alternatively, you can also install the [MinGW-w64](https://mingw-w64.org/doku.php) compiler, which is a Windows port of the GNU Compiler Collection (GCC). To install MinGW-w64, follow these steps:
|
||||
|
||||
- Download the installer from [here](https://sourceforge.net/projects/mingw-w64/files/).
|
||||
- Run the installer and select your desired architecture, version, and install location.
|
||||
- Add the `bin` folder inside the installation directory to your system's `PATH` environment variable.
|
||||
|
||||
### macOS
|
||||
For macOS, you can install the Apple LLVM `clang` compiler which is part of the Xcode Command Line Tools. To do this, open a terminal and enter:
|
||||
|
||||
```
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
This will prompt a dialog to install the Command Line Tools, which includes the `clang` compiler.
|
||||
|
||||
### Linux
|
||||
On Linux, you can install the GNU Compiler Collection (GCC) through your distribution's package manager. Here are some examples for popular Linux distributions:
|
||||
|
||||
- Ubuntu, Debian, and derivatives:
|
||||
```
|
||||
sudo apt-get install g++ build-essential
|
||||
```
|
||||
|
||||
- Fedora, CentOS, RHEL, and derivatives:
|
||||
```
|
||||
sudo dnf install gcc-c++ make
|
||||
```
|
||||
|
||||
- Arch Linux and derivatives:
|
||||
```
|
||||
sudo pacman -S gcc make
|
||||
```
|
||||
|
||||
### Checking the Installation
|
||||
To confirm that the compiler is installed and available on your system, open a terminal/command prompt, and enter the following command:
|
||||
|
||||
```
|
||||
g++ --version
|
||||
```
|
||||
|
||||
You should see output displaying the version of your installed C++ compiler.
|
||||
|
||||
Now you're ready to start writing and compiling your C++ code!
|
||||
@@ -0,0 +1,13 @@
|
||||
# Code Editors
|
||||
|
||||
Code editors are programs specifically designed for editing, managing and writing source code. They offer a wide range of features that make the development process easier and faster. Here's a brief introduction to some of the most popular code editors for C++:
|
||||
|
||||
- **Visual Studio Code (VSCode)**: Visual Studio Code is a popular, free, open-source, and lightweight code editor developed by Microsoft. It has built-in support for C++, along with an extensive library of extensions and plugins.
|
||||
|
||||
- **Sublime Text**: Sublime Text is a cross-platform text editor that is quite popular among developers due to its speed and minimalist design. It supports C++ with the help of plugins and has a variety of themes and packages available for customization.
|
||||
|
||||
- **CLion**: CLion is an Integrated Development Environment (IDE) developed by JetBrains specifically for C and C++ developers. It provides advanced features like code completion, refactoring support, debugging, and more.
|
||||
|
||||
These are just a few examples, and there are many other code editors available, including Atom, Notepad++, and Geany. They all have their features and may suit different developers' needs. Finding the right code editor is often a matter of personal preference and workflow.
|
||||
|
||||
To work with C++ in your chosen code editor, you often need to install some additional tools and add-ons, such as compilers, linters, and debugger support. Make sure to follow the instructions provided by the editor's documentation to set up C++ correctly.
|
||||
@@ -0,0 +1,58 @@
|
||||
# First Program in C++
|
||||
|
||||
In this section, we'll discuss the basic structure of a C++ program, walk you through your first program (the "Hello, World!" example), and provide additional explanations of its syntax.
|
||||
|
||||
## Hello, World!
|
||||
|
||||
The first program that most people learn to write in any programming language is often a simple one that displays the message "Hello, World!" on the screen. Here's the classic "Hello, World!" program in C++:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Let's break down the different components of this program:
|
||||
|
||||
## Header Files & Preprocessor Directives
|
||||
|
||||
The first line of the program `#include <iostream>` is a [preprocessor directive](https://en.cppreference.com/w/cpp/preprocessor) that tells the compiler to include the header file `iostream`. Header files provide function and class declarations that we can use in our C++ programs.
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
```
|
||||
|
||||
##`main()` Function
|
||||
|
||||
In C++, the `main()` function serves as the entry point of your program. The operating system runs your program by calling this `main()` function. It should be defined only once in your program and must return an integer.
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
// Your code goes here.
|
||||
}
|
||||
```
|
||||
|
||||
## Output to the Console
|
||||
|
||||
To output text to the console, we use the `std::cout` object and the insertion operator `<<`. In the "Hello, World!" example, we used the following line to print "Hello, World!" to the console:
|
||||
|
||||
```cpp
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
```
|
||||
|
||||
- `std::cout`: The standard "character output" stream that writes to the console
|
||||
- `"Hello, World!"`: The string literal to print
|
||||
- `std::endl`: The "end line" manipulator that inserts a newline character and flushes the output buffer
|
||||
|
||||
## Return Statement
|
||||
|
||||
Lastly, the `return 0;` statement informs the operating system that the program executed successfully. Returning any other integer value indicates that an error occurred:
|
||||
|
||||
```cpp
|
||||
return 0;
|
||||
```
|
||||
|
||||
Now that you understand the basic components of a C++ program, you can write your first program, compile it, and run it to see the "Hello, World!" message displayed on the screen.
|
||||
57
src/data/roadmaps/cpp/content/101-setting-up/index.md
Normal file
57
src/data/roadmaps/cpp/content/101-setting-up/index.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Setting Up C++
|
||||
|
||||
Setting up C++ requires a few steps, including installing a compiler, configuring an Integrated Development Environment (IDE), and creating a new C++ project.
|
||||
|
||||
## 1. Installing a Compiler
|
||||
|
||||
A compiler is required to convert C++ code into machine language. Some popular C++ compilers include:
|
||||
|
||||
- GCC (GNU Compiler Collection) for Linux and macOS
|
||||
- MinGW (Minimalist GNU for Windows) for Windows
|
||||
- Microsoft Visual C++ for Windows
|
||||
|
||||
To install a compiler, simply follow the instructions provided by the respective websites.
|
||||
|
||||
## 2. Configuring an IDE
|
||||
|
||||
An IDE is a software application that provides facilities for programming, such as code editing, debugging, and building. Some popular C++ IDEs include:
|
||||
|
||||
- [Visual Studio](https://visualstudio.microsoft.com/vs/features/cplusplus/) (Windows, macOS)
|
||||
- [Eclipse](https://eclipse.org) (Windows, macOS, Linux)
|
||||
- [Code::Blocks](http://www.codeblocks.org) (Windows, macOS, Linux)
|
||||
|
||||
After downloading and installing an IDE, you might need to configure it to use the installed compiler. Check the documentation of the respective IDE for instructions on how to do this.
|
||||
|
||||
## 3. Creating a New C++ Project
|
||||
|
||||
Once you have your IDE and compiler set up, you can create a new C++ project and start writing code. In general, follow these steps to create a new C++ project:
|
||||
|
||||
- Open the IDE and create a new project.
|
||||
- Select the project type (C++ Application or Console Application).
|
||||
- Specify the project name and location.
|
||||
- Let the IDE generate the main.cpp and build files (such as Makefile or CMakeLists.txt) for you.
|
||||
|
||||
## Example: Hello World in C++
|
||||
|
||||
Create a new file called `main.cpp` within your project and include this code:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Then, follow the IDE's instructions to build and run your program. You should see "Hello, World!" displayed in the console.
|
||||
|
||||
## Summary
|
||||
|
||||
Setting up C++ involves:
|
||||
|
||||
- Installing a compiler (e.g. GCC, MinGW, or MSVC)
|
||||
- Configuring an IDE (e.g. Visual Studio, Eclipse, or Code::Blocks)
|
||||
- Creating a new C++ project and writing code
|
||||
|
||||
By following these steps, you'll be ready to start developing C++ applications!
|
||||
@@ -0,0 +1,66 @@
|
||||
# Arithmetic Operators in C++
|
||||
|
||||
Arithmetic operators are used to perform mathematical operations with basic variables such as integers and floating-point numbers. Here is a brief summary of the different arithmetic operators in C++:
|
||||
|
||||
## 1. Addition Operator (`+`)
|
||||
|
||||
It adds two numbers together.
|
||||
|
||||
```cpp
|
||||
int sum = a + b;
|
||||
```
|
||||
|
||||
## 2. Subtraction Operator (`-`)
|
||||
|
||||
It subtracts one number from another.
|
||||
|
||||
```cpp
|
||||
int difference = a - b;
|
||||
```
|
||||
|
||||
## 3. Multiplication Operator (`*`)
|
||||
|
||||
It multiplies two numbers together.
|
||||
|
||||
```cpp
|
||||
int product = a * b;
|
||||
```
|
||||
|
||||
## 4. Division Operator (`/`)
|
||||
|
||||
It divides one number by another. Note that if both operands are integers, it will perform integer division and the result will be an integer.
|
||||
|
||||
```cpp
|
||||
int quotient = a / b; // integer division
|
||||
float quotient = float(a) / float(b); // floating-point division
|
||||
```
|
||||
|
||||
## 5. Modulus Operator (`%`)
|
||||
|
||||
It calculates the remainder of an integer division.
|
||||
|
||||
```cpp
|
||||
int remainder = a % b;
|
||||
```
|
||||
|
||||
## 6. Increment Operator (`++`)
|
||||
|
||||
It increments the value of a variable by 1. There are two ways to use this operator: prefix (`++x`) and postfix (`x++`). Prefix increments the value before returning it, whereas postfix returns the value first and then increments it.
|
||||
|
||||
```cpp
|
||||
int x = 5;
|
||||
int y = ++x; // x = 6, y = 6
|
||||
int z = x++; // x = 7, z = 6
|
||||
```
|
||||
|
||||
## 7. Decrement Operator (`--`)
|
||||
|
||||
It decrements the value of a variable by 1. It can also be used in prefix (`--x`) and postfix (`x--`) forms.
|
||||
|
||||
```cpp
|
||||
int x = 5;
|
||||
int y = --x; // x = 4, y = 4
|
||||
int z = x--; // x = 3, z = 4
|
||||
```
|
||||
|
||||
These are the basic arithmetic operators in C++ that allow you to perform mathematical operations on your variables. Use them in combination with other control structures, such as loops and conditionals, to build more complex programs.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Logical Operators in C++
|
||||
|
||||
Logical operators are used to perform logical operations on the given expressions, mostly to test the relationship between different variables or values. They return a boolean value i.e., either true (1) or false (0) based on the result of the evaluation.
|
||||
|
||||
C++ provides the following logical operators:
|
||||
|
||||
- **AND Operator (&&)**
|
||||
The AND operator checks if both the operands/conditions are true, then the expression is true. If any one of the conditions is false, the whole expression will be false.
|
||||
```
|
||||
(expression1 && expression2)
|
||||
```
|
||||
Example:
|
||||
```cpp
|
||||
int a = 5, b = 10;
|
||||
if (a > 0 && b > 0) {
|
||||
cout << "Both values are positive." << endl;
|
||||
}
|
||||
```
|
||||
- **OR Operator (||)**
|
||||
The OR operator checks if either of the operands/conditions are true, then the expression is true. If both the conditions are false, it will be false.
|
||||
```
|
||||
(expression1 || expression2)
|
||||
```
|
||||
Example:
|
||||
```cpp
|
||||
int a = 5, b = -10;
|
||||
if (a > 0 || b > 0) {
|
||||
cout << "At least one value is positive." << endl;
|
||||
}
|
||||
```
|
||||
|
||||
- **NOT Operator (!)**
|
||||
The NOT operator reverses the result of the condition/expression it is applied on. If the condition is true, the NOT operator will make it false and vice versa.
|
||||
```
|
||||
!(expression)
|
||||
```
|
||||
Example:
|
||||
```cpp
|
||||
int a = 5;
|
||||
if (!(a < 0)) {
|
||||
cout << "The value is not negative." << endl;
|
||||
}
|
||||
```
|
||||
|
||||
Using these operators, you can create more complex logical expressions, for example:
|
||||
|
||||
```cpp
|
||||
int a = 5, b = -10, c = 15;
|
||||
|
||||
if (a > 0 && (b > 0 || c > 0)) {
|
||||
cout << "At least two values are positive." << endl;
|
||||
}
|
||||
```
|
||||
|
||||
This covers the essential information about logical operators in C++.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Loops in C++
|
||||
|
||||
Loops are an essential concept in programming that allow you to execute a block of code repeatedly until a specific condition is met. In C++, there are three main types of loops: `for`, `while`, and `do-while`.
|
||||
|
||||
## For Loop
|
||||
|
||||
A `for` loop is used when you know the number of times you want to traverse through a block of code. It consists of an initialization statement, a condition, and an increment/decrement operation.
|
||||
|
||||
Here's the syntax for a `for` loop:
|
||||
|
||||
```cpp
|
||||
for (initialization; condition; increment/decrement) {
|
||||
// block of code to execute
|
||||
}
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
cout << "Iteration: " << i << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## While Loop
|
||||
|
||||
A `while` loop runs as long as a specified condition is `true`. The loop checks for the condition before entering the body of the loop.
|
||||
|
||||
Here's the syntax for a `while` loop:
|
||||
|
||||
```cpp
|
||||
while (condition) {
|
||||
// block of code to execute
|
||||
}
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
int i = 0;
|
||||
while (i < 5) {
|
||||
cout << "Iteration: " << i << endl;
|
||||
i++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Do-While Loop
|
||||
|
||||
A `do-while` loop is similar to a `while` loop, with the key difference being that the loop body is executed at least once, even when the condition is `false`.
|
||||
|
||||
Here's the syntax for a `do-while` loop:
|
||||
|
||||
```cpp
|
||||
do {
|
||||
// block of code to execute
|
||||
} while (condition);
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
int i = 0;
|
||||
do {
|
||||
cout << "Iteration: " << i << endl;
|
||||
i++;
|
||||
} while (i < 5);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
In summary, loops are an integral part of C++ programming that allow you to execute a block of code multiple times. The three types of loops in C++ are `for`, `while`, and `do-while`. Each type has its own specific use case and can be chosen depending on the desired behavior.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Bitwise Operations
|
||||
|
||||
Bitwise operations are operations that directly manipulate the bits of a number. Bitwise operations are useful for various purposes, such as optimizing algorithms, performing certain calculations, and manipulating memory in lower-level programming languages like C and C++.
|
||||
|
||||
Here is a quick summary of common bitwise operations in C++:
|
||||
|
||||
## Bitwise AND (`&`)
|
||||
|
||||
The bitwise AND operation (`&`) is a binary operation that takes two numbers, compares them bit by bit, and returns a new number where each bit is set (1) if the corresponding bits in both input numbers are set (1); otherwise, the bit is unset (0).
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
int result = 5 & 3; // result will be 1 (0000 0101 & 0000 0011 = 0000 0001)
|
||||
```
|
||||
|
||||
## Bitwise OR (`|`)
|
||||
|
||||
The bitwise OR operation (`|`) is a binary operation that takes two numbers, compares them bit by bit, and returns a new number where each bit is set (1) if at least one of the corresponding bits in either input number is set (1); otherwise, the bit is unset (0).
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
int result = 5 | 3; // result will be 7 (0000 0101 | 0000 0011 = 0000 0111)
|
||||
```
|
||||
|
||||
## Bitwise XOR (`^`)
|
||||
|
||||
The bitwise XOR (exclusive OR) operation (`^`) is a binary operation that takes two numbers, compares them bit by bit, and returns a new number where each bit is set (1) if the corresponding bits in the input numbers are different; otherwise, the bit is unset (0).
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
int result = 5 ^ 3; // result will be 6 (0000 0101 ^ 0000 0011 = 0000 0110)
|
||||
```
|
||||
|
||||
## Bitwise NOT (`~`)
|
||||
|
||||
The bitwise NOT operation (`~`) is a unary operation that takes a single number, and returns a new number where each bit is inverted (1 becomes 0, and 0 becomes 1).
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
int result = ~5; // result will be -6 (1111 1010)
|
||||
```
|
||||
|
||||
## Bitwise Left Shift (`<<`)
|
||||
|
||||
The bitwise left shift operation (`<<`) is a binary operation that takes two numbers, a value and a shift amount, and returns a new number by shifting the bits of the value to the left by the specified shift amount. The vacated bits are filled with zeros.
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
int result = 5 << 1; // result will be 10 (0000 0101 << 1 = 0000 1010)
|
||||
```
|
||||
|
||||
## Bitwise Right Shift (`>>`)
|
||||
|
||||
The bitwise right shift operation (`>>`) is a binary operation that takes two numbers, a value and a shift amount, and returns a new number by shifting the bits of the value to the right by the specified shift amount. The vacated bits are filled with zeros or sign bit depending on the input value being signed or unsigned.
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
int result = 5 >> 1; // result will be 2 (0000 0101 >> 1 = 0000 0010)
|
||||
```
|
||||
|
||||
These were the most common bitwise operations in C++. Remember to use them carefully and understand their behavior when applied to specific data types and scenarios.
|
||||
108
src/data/roadmaps/cpp/content/102-basic-operations/index.md
Normal file
108
src/data/roadmaps/cpp/content/102-basic-operations/index.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Basic Operations in C++
|
||||
|
||||
Basic operations in C++ refer to the fundamental arithmetic, relational, and logical operations that can be performed using C++ programming language, which are essential for any kind of program or calculation in a real-world scenario.
|
||||
|
||||
Here's a summary of the basic operations in C++
|
||||
|
||||
## Arithmetic Operations
|
||||
|
||||
These operations are used for performing calculations in C++ and include the following:
|
||||
|
||||
- **Addition (+)**: Adds two numbers.
|
||||
```cpp
|
||||
int a = 5;
|
||||
int b = 6;
|
||||
int sum = a + b; // sum is 11
|
||||
```
|
||||
|
||||
- **Subtraction (-)**: Subtracts one number from the other.
|
||||
```cpp
|
||||
int a = 10;
|
||||
int b = 6;
|
||||
int diff = a - b; // diff is 4
|
||||
```
|
||||
|
||||
- **Multiplication (*)**: Multiplies two numbers.
|
||||
```cpp
|
||||
int a = 3;
|
||||
int b = 4;
|
||||
int product = a * b; // product is 12
|
||||
```
|
||||
|
||||
- **Division (/)**: Divides one number by another, yields quotient.
|
||||
```cpp
|
||||
int a = 12;
|
||||
int b = 4;
|
||||
int quotient = a / b; // quotient is 3
|
||||
```
|
||||
|
||||
- **Modulus (%)**: Divides one number by another, yields remainder.
|
||||
```cpp
|
||||
int a = 15;
|
||||
int b = 4;
|
||||
int remainder = a % b; // remainder is 3
|
||||
```
|
||||
|
||||
## Relational Operators
|
||||
|
||||
These operations compare two values and return a boolean value (true/false) depending on the comparison. The relational operations are:
|
||||
|
||||
- **Equal to (==)**: Returns true if both operands are equal.
|
||||
```cpp
|
||||
5 == 5 // true
|
||||
3 == 4 // false
|
||||
```
|
||||
|
||||
- **Not equal to (!=)**: Returns true if operands are not equal.
|
||||
```cpp
|
||||
5 != 2 // true
|
||||
1 != 1 // false
|
||||
```
|
||||
|
||||
- **Greater than (>)**: Returns true if the first operand is greater than the second.
|
||||
```cpp
|
||||
5 > 3 // true
|
||||
2 > 3 // false
|
||||
```
|
||||
|
||||
- **Less than (<)**: Returns true if the first operand is less than the second.
|
||||
```cpp
|
||||
3 < 5 // true
|
||||
6 < 5 // false
|
||||
```
|
||||
|
||||
- **Greater than or equal to (>=)**: Returns true if the first operand is greater than or equal to the second.
|
||||
```cpp
|
||||
5 >= 5 // true
|
||||
6 >= 2 // true
|
||||
3 >= 4 // false
|
||||
```
|
||||
|
||||
- **Less than or equal to (<=)**: Returns true if the first operand is less than or equal to the second.
|
||||
```cpp
|
||||
4 <= 4 // true
|
||||
2 <= 3 // true
|
||||
5 <= 4 // false
|
||||
```
|
||||
|
||||
## Logical Operators
|
||||
|
||||
Logical operators are used for combining multiple conditions or boolean values.
|
||||
|
||||
- **AND (&&)**: Returns true if both operands are true.
|
||||
```cpp
|
||||
true && true // true
|
||||
true && false // false
|
||||
```
|
||||
|
||||
- **OR (||)**: Returns true if any one of the operands is true.
|
||||
```cpp
|
||||
true || false // true
|
||||
false || false // false
|
||||
```
|
||||
|
||||
- **NOT (!)**: Returns true if the operand is false and vice versa.
|
||||
```cpp
|
||||
!true // false
|
||||
!false // true
|
||||
```
|
||||
62
src/data/roadmaps/cpp/content/103-functions/100-lambda.md
Normal file
62
src/data/roadmaps/cpp/content/103-functions/100-lambda.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Lambda Functions in C++
|
||||
|
||||
A lambda function, or simply "lambda", is an anonymous (unnamed) function that is defined in place, within your source code, and with a concise syntax. Lambda functions were introduced in C++11 and have since become a widely used feature, especially in combination with the Standard Library algorithms.
|
||||
|
||||
## Syntax
|
||||
|
||||
Here is a basic syntax of a lambda function in C++:
|
||||
|
||||
```cpp
|
||||
[capture-list](parameters) -> return_type {
|
||||
// function body
|
||||
};
|
||||
```
|
||||
|
||||
- **capture-list**: A list of variables from the surrounding scope that the lambda function can access.
|
||||
- **parameters**: The list of input parameters, just like in a regular function. Optional.
|
||||
- **return_type**: The type of the value that the lambda function will return. This part is optional, and the compiler can deduce it in many cases.
|
||||
- **function body**: The code that defines the operation of the lambda function.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
Here are a few examples to demonstrate the use of lambda functions in C++:
|
||||
|
||||
- Lambda function with no capture, parameters, or return type.
|
||||
|
||||
```cpp
|
||||
auto printHello = []() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
};
|
||||
printHello(); // Output: Hello, World!
|
||||
```
|
||||
|
||||
- Lambda function with parameters.
|
||||
|
||||
```cpp
|
||||
auto add = [](int a, int b) {
|
||||
return a + b;
|
||||
};
|
||||
int result = add(3, 4); // result = 7
|
||||
```
|
||||
|
||||
- Lambda function with capture-by-value.
|
||||
|
||||
```cpp
|
||||
int multiplier = 3;
|
||||
auto times = [multiplier](int a) {
|
||||
return a * multiplier;
|
||||
};
|
||||
int result = times(5); // result = 15
|
||||
```
|
||||
|
||||
- Lambda function with capture-by-reference.
|
||||
|
||||
```cpp
|
||||
int expiresInDays = 45;
|
||||
auto updateDays = [&expiresInDays](int newDays) {
|
||||
expiresInDays = newDays;
|
||||
};
|
||||
updateDays(30); // expiresInDays = 30
|
||||
```
|
||||
|
||||
Note that, when using the capture by reference, any change made to the captured variable *inside* the lambda function will affect its value in the surrounding scope.
|
||||
104
src/data/roadmaps/cpp/content/103-functions/101-operators.md
Normal file
104
src/data/roadmaps/cpp/content/103-functions/101-operators.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Operators in C++
|
||||
|
||||
Operators in C++ are symbols that perform various operations on data, such as arithmetic, comparison, and logical operations. They are used to manipulate and evaluate expressions and variables.
|
||||
|
||||
Here is a list of the commonly used operator types in C++:
|
||||
|
||||
- **Arithmetic Operators**: These are used for performing arithmetic operations like addition, subtraction, multiplication, and division.
|
||||
|
||||
- `+`: addition
|
||||
```cpp
|
||||
int sum = 5 + 3; // sum will be 8
|
||||
```
|
||||
- `-`: subtraction
|
||||
```cpp
|
||||
int difference = 5 - 3; // difference will be 2
|
||||
```
|
||||
- `*`: multiplication
|
||||
```cpp
|
||||
int product = 5 * 3; // product will be 15
|
||||
```
|
||||
- `/`: division
|
||||
```cpp
|
||||
int quotient = 15 / 3; // quotient will be 5
|
||||
```
|
||||
- `%`: modulo (remainder)
|
||||
```cpp
|
||||
int remainder = 7 % 3; // remainder will be 1
|
||||
```
|
||||
|
||||
- **Comparison (Relational) Operators**: These are used to compare two values and return true or false based on the comparison.
|
||||
|
||||
- `==`: equal to
|
||||
```cpp
|
||||
bool isEqual = (5 == 3); // isEqual will be false
|
||||
```
|
||||
- `!=`: not equal to
|
||||
```cpp
|
||||
bool isNotEqual = (5 != 3); // isNotEqual will be true
|
||||
```
|
||||
- `<`: less than
|
||||
```cpp
|
||||
bool isLess = (5 < 3); // isLess will be false
|
||||
```
|
||||
- `>`: greater than
|
||||
```cpp
|
||||
bool isGreater = (5 > 3); // isGreater will be true
|
||||
```
|
||||
- `<=`: less than or equal to
|
||||
```cpp
|
||||
bool isLessOrEqual = (5 <= 3); // isLessOrEqual will be false
|
||||
```
|
||||
- `>=`: greater than or equal to
|
||||
```cpp
|
||||
bool isGreaterOrEqual = (5 >= 3); // isGreaterOrEqual will be true
|
||||
```
|
||||
|
||||
- **Logical Operators**: These operators are used to perform logical operations such as AND (&&), OR (||), and NOT (!) on boolean values.
|
||||
|
||||
- `&&`: logical AND
|
||||
```cpp
|
||||
bool result = (true && false); // result will be false
|
||||
```
|
||||
- `||`: logical OR
|
||||
```cpp
|
||||
bool result = (true || false); // result will be true
|
||||
```
|
||||
- `!`: logical NOT
|
||||
```cpp
|
||||
bool result = !false; // result will be true
|
||||
```
|
||||
|
||||
- **Assignment Operators**: These are used to assign values to variables.
|
||||
|
||||
- `=`: simple assignment
|
||||
```cpp
|
||||
int x = 5; // x gets the value 5
|
||||
```
|
||||
- `+=`: addition assignment
|
||||
```cpp
|
||||
int x = 5;
|
||||
x += 3; // x gets the value 8 (5 + 3)
|
||||
```
|
||||
- `-=`: subtraction assignment
|
||||
```cpp
|
||||
int x = 5;
|
||||
x -= 3; // x gets the value 2 (5 - 3)
|
||||
```
|
||||
- `*=`: multiplication assignment
|
||||
```cpp
|
||||
int x = 5;
|
||||
x *= 3; // x gets the value 15 (5 * 3)
|
||||
```
|
||||
- `/=`: division assignment
|
||||
```cpp
|
||||
int x = 15;
|
||||
x /= 3; // x gets the value 5 (15 / 3)
|
||||
```
|
||||
- `%=`: modulo assignment
|
||||
```cpp
|
||||
int x = 7;
|
||||
x %= 3; // x gets the value 1 (7 % 3)
|
||||
```
|
||||
|
||||
These are some of the main operator categories in C++. Each operator allows you to perform specific operations, making your code more efficient and concise.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user