Compare commits

..

55 Commits

Author SHA1 Message Date
Kamran Ahmed
a7238171fa Merge branch 'master' into feat/open-graph 2024-03-19 03:32:56 +00:00
RMN-001
ec9d2d4c74 Fix typo in JavaScript roadmap (#5369)
Correcting the typo.
2024-03-18 17:28:57 +00:00
Siddharth Vijay Sai
1eb5a9c49d Fix typos in the SQL Roadmap (#5365)
Updated the row query definition. Row queries can return not just one but multiple rows.
2024-03-18 15:43:48 +00:00
Arik Chakma
b5b34504c6 fix: special character issue 2024-03-15 10:48:58 +06:00
Arik Chakma
1e044405a4 fix: external author image 2024-03-15 09:36:39 +06:00
Kamran Ahmed
c3a61e7f34 Add json ld for author pages 2024-03-15 03:13:53 +00:00
Arik Chakma
72d697f6d7 fix: generate images on build time 2024-03-15 09:04:10 +06:00
Arik Chakma
57d86542e0 fix: remove guide id 2024-03-14 22:39:07 +06:00
Arik Chakma
36e8e6051b fix: open graph query params 2024-03-14 14:06:02 +06:00
Arik Chakma
4f9917bc5c fix: open graph function 2024-03-13 13:37:02 +06:00
Arik Chakma
d1b27854ea chore: add open graph images 2024-03-13 13:17:12 +06:00
Kamran Ahmed
f838b5dac7 UI changes for logged out users 2024-03-12 15:36:37 +00:00
Kamran Ahmed
64cfe503af Block UI for guest users 2024-03-12 15:26:13 +00:00
Kamran Ahmed
5b7e7b9767 Change alert color 2024-03-12 00:28:49 +00:00
Kamran Ahmed
58a5d27854 Add AI announcement on homepage 2024-03-11 12:57:16 +00:00
Kamran Ahmed
18db62748c Update autofouc on roadmap generator 2024-03-11 12:28:10 +00:00
Kamran Ahmed
c9703c8589 Fix flicker on AI page 2024-03-11 12:12:06 +00:00
boc-the-git
da526fa684 Remove duplication (#5311) 2024-03-11 05:14:52 +00:00
Arik Chakma
cd6232035f Refactor AI roadmap generator (#5300)
* fix: roadmap refetching

* fix: remove current roadmap

* feat: explore ai roadmaps

* feat: generate roadmap content

* fix: roadmap topic details

* fix: make roadmap link

* feat: add visit cookie

* chore: update naming

* Update UI for roadmap search

* Update

* Update

* UI updates

* fix: expire visit cookie in 1 hour

* chore: limit roadmap topic content generation

* Add alert on generate roadmap

* UI for search

* Refactor nodesg

* Refactor

* Load roadmap on click

* Refactor UI for ai

* Allow overriding with own API key

* Allow overriding keys

* Add configuration for open ai key

* Add open ai saving

* Fix responsiveness issues

* Fix responsiveness issues

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2024-03-11 05:14:32 +00:00
Kamran Ahmed
09cb1ea827 Update custom roadmap message 2024-03-05 18:39:51 +00:00
Kamran Ahmed
d5fdc62343 Adds AI roadmap generator (#5289)
* feat: implement roadmap generator

* feat: add roadmap stream

* Update UI

* fix: add fingerprint visitor id

* feat: implement edit generated roadmap

* feat: implement ai roadmap download

* feat: add limit count

* fix: add limit check

* fix: download image button

* feat: implement roadmap generator

* feat: add roadmap stream

* Update UI

* fix: add fingerprint visitor id

* feat: implement edit generated roadmap

* feat: implement ai roadmap download

* feat: add limit count

* fix: add limit check

* fix: download image button

* UI Updates

* Update UI for roadmap search

* Update UI for roadmap limit

* Update UI for roadmap

* UI responsiveness on AI roadmap generator

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2024-03-04 17:56:15 +00:00
Reyes Rondón
44d3724880 fix: output length of string (#5227) 2024-03-04 19:46:22 +06:00
boc-the-git
9d4aae10b5 Fix formatting (#5247) 2024-03-04 19:41:10 +06:00
Alex
070d04334b fix: blockchain typo (#5282)
Co-authored-by: Alex Marmolejo <alex97marmolej@gmail.com>
2024-03-03 22:38:37 +06:00
Kamran Ahmed
7040b6637c Persist query parameters on ai and custom roadmap page 2024-03-01 18:10:21 +00:00
Kamran Ahmed
cfa8d2a986 Page sponsor banner fix 2024-03-01 17:46:54 +00:00
Kamran Ahmed
d2f372fd6f Add community roadmap alert 2024-03-01 17:21:06 +00:00
Kamran Ahmed
f6cd6419be Fix typing error 2024-02-29 11:41:05 +00:00
Kamran Ahmed
dcef07d7c6 Remove custom roadmaps from google indexing 2024-02-29 11:39:09 +00:00
Kamran Ahmed
bd3fd8bfe2 Add backend developer skills article 2024-02-27 18:32:00 +00:00
Kamran Ahmed
44b62c2b2d Add author pages 2024-02-27 10:57:25 +00:00
Kamran Ahmed
d958a29862 Add author page 2024-02-27 00:55:38 +00:00
Arik Chakma
37ffc2cc62 fix: set cookie's SameSite and Secure (#5186)
* fix: set cookie's `SameSite` and `Secure`

* fix: remove caddy file
2024-02-26 12:43:32 +00:00
Kamran Ahmed
dd3a46e972 Update footer spacing 2024-02-22 15:50:10 +00:00
devhindo
9f8fcb8265 Remove dead link (#5230)
This video isn't available any more
2024-02-22 15:36:28 +00:00
Kamran Ahmed
6c99127fc4 Add links to new roadmaps 2024-02-22 15:34:45 +00:00
Kamran Ahmed
9157e18ec7 Add datastructures and algorithms links 2024-02-22 15:32:08 +00:00
Kamran Ahmed
84093e3525 Minor space fix 2024-02-22 15:17:23 +00:00
Kamran Ahmed
04d0f7c0b1 Opensource banner responsiveness 2024-02-22 15:16:05 +00:00
Kamran Ahmed
f1f56408d5 Update opensource banner 2024-02-22 13:54:15 +00:00
Kamran Ahmed
d847eb0685 Add content for data structures 2024-02-20 23:36:51 +00:00
Kamran Ahmed
d697707384 Add data structures and algorithm roadmap 2024-02-20 16:33:00 +00:00
Kamran Ahmed
fdee813a0b Discord stats 2024-02-19 14:52:59 +00:00
Kamran Ahmed
707f0097dc Add opensource stats 2024-02-19 14:30:11 +00:00
Kamran Ahmed
27e98b0eba Add hiring page 2024-02-16 00:36:09 +00:00
Kamran Ahmed
8d677f3a22 Fix markdown rendering 2024-02-15 22:15:08 +00:00
Kamran Ahmed
a9734c7eeb Update dependencies 2024-02-15 18:12:34 +00:00
Ashish Singh
7ba48523da Fix broken link (#5156) 2024-02-14 11:20:33 +00:00
Micael Levi L. Cavalcante
c1ebe9ae47 Fix broken link (#5170)
remove unavailable youtube video
2024-02-14 11:15:42 +00:00
Henderson Dike-Benard
415a9c0fd0 Added two Rust courses (#5172)
* Added two rust courses

* Update src/data/roadmaps/game-developer/content/104-programming-languages/103-rust.md

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2024-02-14 11:15:27 +00:00
wheval
d3b8cbceaa Update react router tutorial link (#5175)
change the link to react router tutorial, previous link was outdated
2024-02-14 11:13:58 +00:00
Henderson Dike-Benard
c390f4428e Made Changes to quarternions (#5177)
* Made Changes to quarternions

* Update src/data/roadmaps/game-developer/content/101-game-mathematics/106-orientation/100-quaternion.md

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2024-02-14 11:13:35 +00:00
Abu Jafor Mohammad Saleh
2e890b1b25 Update MongoDB Documentation Date url (#5178) 2024-02-14 11:11:40 +00:00
Kunal Ukey
7b9be9377b MongoDB Roadmap: VS Analyzer broken link fix (#5182)
- changed broken link for VS Analyzer
- changed installation steps
2024-02-14 11:11:02 +00:00
Arik Chakma
4863f08a4c fix: disable login buttons (#5179)
* fix: disable login buttons

* refactor: remove dead code

* fix: add signup form
2024-02-14 11:10:09 +00:00
364 changed files with 11593 additions and 839 deletions

View File

@@ -19,51 +19,63 @@
"generate-renderer": "sh scripts/generate-renderer.sh",
"best-practice-dirs": "node scripts/best-practice-dirs.cjs",
"best-practice-content": "node scripts/best-practice-content.cjs",
"generate:og": "node ./scripts/generate-og-images.mjs",
"test:e2e": "playwright test"
},
"dependencies": {
"@astrojs/react": "^3.0.9",
"@astrojs/react": "^3.0.10",
"@astrojs/sitemap": "^3.0.5",
"@astrojs/tailwind": "^5.1.0",
"@fingerprintjs/fingerprintjs": "^4.2.1",
"@fingerprintjs/fingerprintjs": "^4.2.2",
"@nanostores/react": "^0.7.1",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"astro": "^4.2.1",
"astro-compress": "^2.2.8",
"@resvg/resvg-js": "^2.6.0",
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"astro": "^4.4.0",
"astro-compress": "^2.2.10",
"clsx": "^2.1.0",
"dracula-prism": "^2.1.13",
"jose": "^5.2.0",
"dom-to-image": "^2.6.0",
"dracula-prism": "^2.1.16",
"gray-matter": "^4.0.3",
"htm": "^3.1.1",
"image-size": "^1.1.1",
"jose": "^5.2.2",
"js-cookie": "^3.0.5",
"lucide-react": "^0.300.0",
"nanoid": "^5.0.4",
"lucide-react": "^0.334.0",
"nanoid": "^5.0.5",
"nanostores": "^0.9.5",
"node-html-parser": "^6.1.12",
"npm-check-updates": "^16.14.12",
"npm-check-updates": "^16.14.15",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-confetti": "^6.1.0",
"react-dom": "^18.2.0",
"reactflow": "^11.10.2",
"reactflow": "^11.10.4",
"rehype-external-links": "^3.0.0",
"remark-parse": "^11.0.0",
"roadmap-renderer": "^1.0.6",
"satori": "^0.10.13",
"satori-html": "^0.3.2",
"sharp": "^0.33.2",
"slugify": "^1.6.6",
"tailwind-merge": "^2.2.1",
"tailwindcss": "^3.4.1",
"zustand": "^4.5.0"
"unified": "^11.0.4",
"zustand": "^4.5.1"
},
"devDependencies": {
"@playwright/test": "^1.41.1",
"@playwright/test": "^1.41.2",
"@tailwindcss/typography": "^0.5.10",
"@types/dom-to-image": "^2.6.7",
"@types/js-cookie": "^3.0.6",
"@types/prismjs": "^1.26.3",
"csv-parser": "^3.0.0",
"gh-pages": "^6.1.1",
"js-yaml": "^4.1.0",
"markdown-it": "^14.0.0",
"openai": "^4.25.0",
"prettier": "^3.2.4",
"prettier-plugin-astro": "^0.12.3",
"openai": "^4.28.0",
"prettier": "^3.2.5",
"prettier-plugin-astro": "^0.13.0",
"prettier-plugin-tailwindcss": "^0.5.11"
}
}

668
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

3
public/images/graph.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="46" height="27" viewBox="0 0 46 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M43.354 0.9C42.184 0.9 41.2371 1.84684 41.2371 3.01686C41.2371 3.30867 41.3062 3.57708 41.4117 3.82435L33.4085 15.0163C33.38 15.0161 33.3514 15.0167 33.3248 15.0172C33.3051 15.0176 33.2864 15.018 33.2697 15.018C32.8703 15.018 32.484 15.1223 32.161 15.3186L25.2976 11.9024C25.1995 10.8219 24.2903 9.97585 23.1854 9.97585C22.0154 9.97585 21.0686 10.9227 21.0686 12.0927C21.0686 12.1865 21.0799 12.2794 21.0925 12.3656L13.8077 18.1561C13.5852 18.0783 13.3472 18.0433 13.1011 18.0433C12.0622 18.0433 11.2066 18.7882 11.0265 19.7732L4.26122 22.5041C3.91213 22.2447 3.48642 22.077 3.01686 22.077C1.84684 22.077 0.9 23.0238 0.9 24.1938C0.9 25.3639 1.84684 26.3107 3.01686 26.3107C4.06426 26.3107 4.92372 25.5497 5.0923 24.5492L11.8566 21.8497C12.2057 22.1092 12.6315 22.277 13.1011 22.277C14.2711 22.277 15.218 21.3301 15.218 20.1601C15.218 20.0663 15.2067 19.9735 15.194 19.8873L22.4789 14.0968C22.7013 14.1746 22.9393 14.2096 23.1854 14.2096C23.5848 14.2096 23.9711 14.1053 24.2941 13.909L31.1575 17.3252C31.2556 18.4057 32.1649 19.2517 33.2697 19.2517C34.4397 19.2517 35.3866 18.3049 35.3866 17.1348C35.3866 16.843 35.3175 16.5746 35.2119 16.3273L43.2151 5.13536C43.2437 5.13561 43.2723 5.13503 43.2989 5.13449C43.3186 5.13409 43.3373 5.13371 43.354 5.13371C44.524 5.13371 45.4708 4.18687 45.4708 3.01686C45.4708 1.84684 44.524 0.9 43.354 0.9Z" fill="black" stroke="black" stroke-width="0.2" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

View File

@@ -31,11 +31,13 @@ Roadmaps are now interactive, you can click the nodes to read more about the top
Here is the list of available roadmaps with more being actively worked upon.
- [Frontend Roadmap](https://roadmap.sh/frontend) / [Frontend Beginner Roadmap](https://roadmap.sh/frontend?r=frontend-beginner)
- [Backend Roadmap](https://roadmap.sh/backend)
- [Backend Roadmap](https://roadmap.sh/backend) / [Backend Beginner Roadmap](https://roadmap.sh/backend?r=backend-beginner)
- [DevOps Roadmap](https://roadmap.sh/devops) / [DevOps Beginner Roadmap](https://roadmap.sh/devops?r=devops-beginner)
- [Full Stack Roadmap](https://roadmap.sh/full-stack)
- [Computer Science Roadmap](https://roadmap.sh/computer-science)
- [Data Structures and Algorithms Roadmap](https://roadmap.sh/datastructures-and-algorithms)
- [AI and Data Scientist Roadmap](https://roadmap.sh/ai-data-scientist)
- [MLOps Roadmap](https://roadmap.sh/mlops)
- [QA Roadmap](https://roadmap.sh/qa)
- [Python Roadmap](https://roadmap.sh/python)
- [Software Architect Roadmap](https://roadmap.sh/software-architect)

View File

@@ -0,0 +1,554 @@
import path from 'node:path';
import fs from 'node:fs/promises';
import matter from 'gray-matter';
import { html } from 'satori-html';
import satori from 'satori';
import sharp from 'sharp';
import imageSize from 'image-size';
import { Resvg } from '@resvg/resvg-js';
const ALL_ROADMAP_DIR = path.join(process.cwd(), '/src/data/roadmaps');
const ALL_BEST_PRACTICE_DIR = path.join(
process.cwd(),
'/src/data/best-practices',
);
const ALL_GUIDE_DIR = path.join(process.cwd(), '/src/data/guides');
const ALl_AUTHOR_DIR = path.join(process.cwd(), '/src/data/authors');
const ALL_ROADMAP_IMAGE_DIR = path.join(process.cwd(), '/public/roadmaps');
const ALL_BEST_PRACTICE_IMAGE_DIR = path.join(
process.cwd(),
'/public/best-practices',
);
const ALL_AUTHOR_IMAGE_DIR = path.join(process.cwd(), '/public');
const alreadyGeneratedImages = await fs.readdir(
path.join(process.cwd(), '/public/og-images'),
{
recursive: true,
},
);
async function getAllRoadmaps() {
const allRoadmapDirNames = await fs.readdir(ALL_ROADMAP_DIR);
const allRoadmapFrontmatter = await Promise.all(
allRoadmapDirNames.map(async (roadmapDirName) => {
const roadmapDirPath = path.join(
ALL_ROADMAP_DIR,
roadmapDirName,
`${roadmapDirName}.md`,
);
const markdown = await fs.readFile(roadmapDirPath, 'utf8');
const { data } = matter(markdown);
return {
id: roadmapDirName,
title: data?.briefTitle,
description: data?.briefDescription,
};
}),
);
return allRoadmapFrontmatter;
}
async function getAllBestPractices() {
const allBestPracticeDirNames = await fs.readdir(ALL_BEST_PRACTICE_DIR);
const allBestPracticeFrontmatter = await Promise.all(
allBestPracticeDirNames.map(async (bestPracticeDirName) => {
const bestPracticeDirPath = path.join(
ALL_BEST_PRACTICE_DIR,
bestPracticeDirName,
`${bestPracticeDirName}.md`,
);
const markdown = await fs.readFile(bestPracticeDirPath, 'utf8');
const { data } = matter(markdown);
return {
id: bestPracticeDirName,
title: data?.briefTitle,
description: data?.briefDescription,
};
}),
);
return allBestPracticeFrontmatter;
}
async function getAllGuides() {
const allGuideDirNames = await fs.readdir(ALL_GUIDE_DIR);
const allGuideFrontmatter = await Promise.all(
allGuideDirNames.map(async (guideDirName) => {
const guideDirPath = path.join(ALL_GUIDE_DIR, guideDirName);
const markdown = await fs.readFile(guideDirPath, 'utf8');
const { data } = matter(markdown);
return {
id: guideDirName?.replace('.md', ''),
title: data?.title,
description: data?.description,
authorId: data?.authorId,
};
}),
);
return allGuideFrontmatter;
}
async function getAllAuthors() {
const allAuthorDirNames = await fs.readdir(ALl_AUTHOR_DIR);
const allAuthorFrontmatter = await Promise.all(
allAuthorDirNames.map(async (authorDirName) => {
const authorDirPath = path.join(ALl_AUTHOR_DIR, authorDirName);
const markdown = await fs.readFile(authorDirPath, 'utf8');
const { data } = matter(markdown);
return {
id: authorDirName?.replace('.md', ''),
name: data?.name,
imageUrl: data?.imageUrl,
};
}),
);
return allAuthorFrontmatter;
}
async function getAllRoadmapImageIds() {
const allRoadmapImageDirNames = await fs.readdir(ALL_ROADMAP_IMAGE_DIR);
return allRoadmapImageDirNames?.reduce((acc, image) => {
acc[image.replace(/(\.[^.]*)$/, '')] = image;
return acc;
}, {});
}
async function getAllBestPracticeImageIds() {
const allBestPracticeImageDirNames = await fs.readdir(
ALL_BEST_PRACTICE_IMAGE_DIR,
);
return allBestPracticeImageDirNames?.reduce((acc, image) => {
acc[image.replace(/(\.[^.]*)$/, '')] = image;
return acc;
}, {});
}
async function generateResourceOpenGraph() {
const allRoadmaps = (await getAllRoadmaps()).filter(
(roadmap) => !alreadyGeneratedImages.includes(`roadmaps/${roadmap.id}.png`),
);
const allBestPractices = (await getAllBestPractices()).filter(
(bestPractice) =>
!alreadyGeneratedImages.includes(`best-practices/${bestPractice.id}.png`),
);
const allRoadmapImageIds = await getAllRoadmapImageIds();
const allBestPracticeImageIds = await getAllBestPracticeImageIds();
const resources = [];
allRoadmaps.forEach((roadmap) => {
const hasImage = allRoadmapImageIds?.[roadmap.id];
resources.push({
type: 'roadmaps',
id: roadmap.id,
title: roadmap.title,
description: roadmap.description,
image: hasImage
? path.join(ALL_ROADMAP_IMAGE_DIR, allRoadmapImageIds[roadmap.id])
: null,
});
});
allBestPractices.forEach((bestPractice) => {
const hasImage = allBestPracticeImageIds?.[bestPractice.id];
resources.push({
type: 'best-practices',
id: bestPractice.id,
title: bestPractice.title,
description: bestPractice.description,
image: hasImage
? path.join(
ALL_BEST_PRACTICE_IMAGE_DIR,
allBestPracticeImageIds[bestPractice.id],
)
: null,
});
});
for (const resource of resources) {
if (!resource.image) {
let template = getRoadmapDefaultTemplate(resource);
if (
hasSpecialCharacters(resource.title) ||
hasSpecialCharacters(resource.description)
) {
// For some reason special characters are not being rendered properly
// https://github.com/natemoo-re/satori-html/issues/20
// So we need to unescape the html
template = JSON.parse(unescapeHtml(JSON.stringify(template)));
}
await generateOpenGraph(
template,
resource.type,
resource.id + '.png',
'resvg',
);
} else {
const image = await fs.readFile(resource.image);
const dimensions = imageSize(image);
const widthRatio = 1200 / dimensions.width;
let width = dimensions.width * widthRatio * 0.85;
let height = dimensions.height * widthRatio * 0.85;
let template = getRoadmapImageTemplate({
...resource,
image: `data:image/${dimensions.type};base64,${image.toString('base64')}`,
width,
height,
});
if (
hasSpecialCharacters(resource.title) ||
hasSpecialCharacters(resource.description)
) {
// For some reason special characters are not being rendered properly
// https://github.com/natemoo-re/satori-html/issues/20
// So we need to unescape the html
template = JSON.parse(unescapeHtml(JSON.stringify(template)));
}
await generateOpenGraph(template, resource.type, resource.id + '.png');
}
}
}
async function generateGuideOpenGraph() {
const allGuides = (await getAllGuides()).filter(
(guide) => !alreadyGeneratedImages.includes(`guides/${guide.id}.png`),
);
const allAuthors = await getAllAuthors();
for (const guide of allGuides) {
const author = allAuthors.find((author) => author.id === guide.authorId);
const image =
author?.imageUrl || 'https://roadmap.sh/images/default-avatar.png';
const isExternalImage = image?.startsWith('http');
let authorImageExtention = '';
let authorAvatar;
if (!isExternalImage) {
authorAvatar = await fs.readFile(path.join(ALL_AUTHOR_IMAGE_DIR, image));
authorImageExtention = image?.split('.')[1];
}
const template = getGuideTemplate({
...guide,
authorName: author.name,
authorAvatar: isExternalImage
? image
: `data:image/${authorImageExtention};base64,${authorAvatar.toString('base64')}`,
});
if (
hasSpecialCharacters(guide.title) ||
hasSpecialCharacters(guide.description)
) {
// For some reason special characters are not being rendered properly
// https://github.com/natemoo-re/satori-html/issues/20
// So we need to unescape the html
template = JSON.parse(unescapeHtml(JSON.stringify(template)));
}
await generateOpenGraph(template, 'guides', guide.id + '.png');
}
}
async function generateOpenGraph(
htmlString,
type,
fileName,
renderer = 'sharp',
) {
console.log('Started 🚀', `${type}/${fileName}`);
const svg = await satori(htmlString, {
width: 1200,
height: 630,
fonts: [
{
name: 'balsamiq',
data: await fs.readFile(
path.join(process.cwd(), '/public/fonts/BalsamiqSans-Regular.ttf'),
),
weight: 400,
style: 'normal',
},
],
});
await fs.mkdir(path.join(process.cwd(), '/public/og-images/' + type), {
recursive: true,
});
// It will be used to generate the default image
// for some reasone sharp is not working with this
// FIXME: Investigate why sharp is not working with this
if (renderer === 'resvg') {
const resvg = new Resvg(svg, {
fitTo: {
mode: 'width',
value: 2500,
},
});
const pngData = resvg.render();
const pngBuffer = pngData.asPng();
await fs.writeFile(
path.join(process.cwd(), '/public/og-images/' + `${type}/${fileName}`),
pngBuffer,
);
} else {
await sharp(Buffer.from(svg), { density: 150 })
.png()
.toFile(
path.join(process.cwd(), '/public/og-images/' + `${type}/${fileName}`),
);
}
console.log('Completed ✅', `${type}/${fileName}`);
}
await generateResourceOpenGraph();
await generateGuideOpenGraph();
function getRoadmapDefaultTemplate({ title, description }) {
return html`<div tw="bg-white relative flex flex-col h-full w-full">
<div
tw="absolute flex top-[90px] left-0 w-full h-px bg-black opacity-5"
></div>
<div tw="absolute flex top-0 left-0 w-full h-[18px] bg-black"></div>
<div tw="absolute flex bottom-0 left-0 w-full h-[18px] bg-black"></div>
<div
tw="absolute flex bottom-[90px] left-0 w-full h-px bg-black opacity-5"
></div>
<div
tw="absolute flex top-0 left-[90px] h-full w-px bg-black opacity-5"
></div>
<div
tw="absolute flex top-0 right-[90px] h-full w-px bg-black opacity-5"
></div>
<div tw="flex flex-col px-[100px] py-[90px] h-full">
<div tw="flex justify-between flex-col p-[30px] h-full">
<div tw="flex flex-col">
<div tw="text-[70px] leading-[70px] tracking-tight">${title}</div>
<div
tw="mt-[16px] text-[30px] leading-[36px] tracking-tight opacity-80"
>
${description}
</div>
</div>
<div tw="flex flex-col">
<div tw="flex items-center mt-2.5">
<div
tw="flex items-center justify-center w-[40px] h-[40px] mr-[24px]"
>
<svg
width="46"
height="27"
viewBox="0 0 46 27"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M43.354 0.9C42.184 0.9 41.2371 1.84684 41.2371 3.01686C41.2371 3.30867 41.3062 3.57708 41.4117 3.82435L33.4085 15.0163C33.38 15.0161 33.3514 15.0167 33.3248 15.0172C33.3051 15.0176 33.2864 15.018 33.2697 15.018C32.8703 15.018 32.484 15.1223 32.161 15.3186L25.2976 11.9024C25.1995 10.8219 24.2903 9.97585 23.1854 9.97585C22.0154 9.97585 21.0686 10.9227 21.0686 12.0927C21.0686 12.1865 21.0799 12.2794 21.0925 12.3656L13.8077 18.1561C13.5852 18.0783 13.3472 18.0433 13.1011 18.0433C12.0622 18.0433 11.2066 18.7882 11.0265 19.7732L4.26122 22.5041C3.91213 22.2447 3.48642 22.077 3.01686 22.077C1.84684 22.077 0.9 23.0238 0.9 24.1938C0.9 25.3639 1.84684 26.3107 3.01686 26.3107C4.06426 26.3107 4.92372 25.5497 5.0923 24.5492L11.8566 21.8497C12.2057 22.1092 12.6315 22.277 13.1011 22.277C14.2711 22.277 15.218 21.3301 15.218 20.1601C15.218 20.0663 15.2067 19.9735 15.194 19.8873L22.4789 14.0968C22.7013 14.1746 22.9393 14.2096 23.1854 14.2096C23.5848 14.2096 23.9711 14.1053 24.2941 13.909L31.1575 17.3252C31.2556 18.4057 32.1649 19.2517 33.2697 19.2517C34.4397 19.2517 35.3866 18.3049 35.3866 17.1348C35.3866 16.843 35.3175 16.5746 35.2119 16.3273L43.2151 5.13536C43.2437 5.13561 43.2723 5.13503 43.2989 5.13449C43.3186 5.13409 43.3373 5.13371 43.354 5.13371C44.524 5.13371 45.4708 4.18687 45.4708 3.01686C45.4708 1.84684 44.524 0.9 43.354 0.9Z"
fill="black"
stroke="black"
stroke-width="0.2"
/>
</svg>
</div>
<div tw="text-[30px] flex leading-[30px]">
6th most starred GitHub project
</div>
</div>
<div tw="flex items-center mt-2.5">
<div
tw="flex items-center justify-center w-[40px] h-[40px] mr-[24px]"
>
<svg
width="40"
height="27"
viewBox="0 0 40 27"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M24.8419 21.5546V23.347H37.3473V22.3072C37.3473 21.803 37.1644 21.3086 36.7814 20.9808C35.797 20.1382 34.0544 19.1021 31.4735 19.1021C28.1305 19.1021 25.8107 20.618 24.8419 21.5546ZM22.7297 19.8874C23.9917 18.5206 27.0669 16.4008 31.4735 16.4008C35.9173 16.4008 38.5374 18.7892 39.5092 19.9307C39.8516 20.3328 40 20.825 40 21.2875V26.0483H31.0946H22.1892V21.2978C22.1892 20.8197 22.349 20.2997 22.7297 19.8874Z"
fill="black"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.30026 21.0084C2.86588 21.3329 2.65267 21.8607 2.65267 22.4029V23.347H15.1581V21.5229C14.3747 20.6776 12.4668 19.1021 9.28433 19.1021C6.53917 19.1021 4.48401 20.1243 3.30026 21.0084ZM0.540477 19.8874C1.80253 18.5206 4.87765 16.4008 9.28433 16.4008C13.7281 16.4008 16.3482 18.7892 17.32 19.9307C17.6624 20.3328 17.8108 20.825 17.8108 21.2875V26.0483H8.90538H0V21.2978C0 20.8197 0.15977 20.2997 0.540477 19.8874Z"
fill="black"
/>
<rect
x="10.6122"
y="16.4008"
width="17.3655"
height="7.718"
fill="white"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.8062 19.6515C11.3801 19.9868 11.1665 20.5126 11.1665 21.0548V22.5365H27.4235V20.9495C27.4235 20.4454 27.2397 19.9534 26.8651 19.616C25.6227 18.4973 23.3035 17.0182 19.7876 17.0182C16.0572 17.0182 13.307 18.4702 11.8062 19.6515ZM8.42064 18.0391C10.0613 16.2623 14.059 13.5065 19.7876 13.5065C25.5645 13.5065 28.9707 16.6115 30.2341 18.0954C30.6791 18.6181 30.872 19.258 30.872 19.8592V26.0482H19.295H7.71802V19.8727C7.71802 19.2511 7.92572 18.5751 8.42064 18.0391Z"
fill="black"
/>
<circle
cx="20.2598"
cy="5.7885"
r="4.0385"
stroke="black"
stroke-width="3.5"
/>
<circle
cx="31.8367"
cy="9.64748"
r="3.07375"
stroke="black"
stroke-width="3.5"
/>
<circle
cx="8.68276"
cy="9.64748"
r="3.07375"
stroke="black"
stroke-width="3.5"
/>
</svg>
</div>
<div tw="text-[30px] flex leading-[30px]">
Created and maintained by community
</div>
</div>
<div tw="flex items-center mt-2.5">
<div
tw="flex items-center justify-center w-[40px] h-[40px] mr-[24px]"
>
<svg
width="38"
height="38"
viewBox="0 0 38 38"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 33.155C21.375 33.155 23.3541 34.8334 25.3333 34.8334C30.0833 34.8334 34.8333 22.1667 34.8333 15.485C34.7793 13.4342 33.9169 11.4878 32.434 10.0701C30.951 8.65243 28.9678 7.87839 26.9166 7.9167C23.4016 7.9167 20.5833 10.1967 19 11.0834C17.4166 10.1967 14.5983 7.9167 11.0833 7.9167C9.0309 7.8742 7.04532 8.64686 5.56147 10.0654C4.07761 11.484 3.21646 13.4328 3.16663 15.485C3.16663 22.1667 7.91663 34.8334 12.6666 34.8334C14.6458 34.8334 16.625 33.155 19 33.155Z"
stroke="black"
stroke-width="3.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M15.8334 3.16699C17.4167 3.95866 19 6.33366 19 11.0837"
stroke="black"
stroke-width="3.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
<div tw="text-[30px] flex leading-[30px]">Up-to-date roadmap</div>
</div>
</div>
</div>
</div>
</div> `;
}
function getRoadmapImageTemplate({ title, description, image, height, width }) {
return html`<div tw="bg-white relative flex flex-col h-full w-full">
<div tw="absolute flex top-0 left-0 w-full h-[18px] bg-black"></div>
<div tw="flex flex-col px-[90px] pt-[90px]">
<div tw="flex flex-col pb-0">
<div tw="text-[70px] leading-[70px] tracking-tight">
${title?.replace('&', `{"&"}`)}
</div>
<div
tw="mt-[16px] text-[30px] leading-[36px] tracking-tight opacity-80"
>
${description}
</div>
</div>
</div>
<img
src="${image}"
width="${width}"
height="${height}"
tw="mx-auto mt-[36px]"
/>
</div> `;
}
function getGuideTemplate({ title, description, authorName, authorAvatar }) {
return html`<div tw="bg-white relative flex flex-col h-full w-full">
<div
tw="absolute flex top-[90px] left-0 w-full h-px bg-black opacity-5"
></div>
<div tw="absolute flex top-0 left-0 w-full h-[18px] bg-black"></div>
<div tw="absolute flex bottom-0 left-0 w-full h-[18px] bg-black"></div>
<div
tw="absolute flex bottom-[90px] left-0 w-full h-px bg-black opacity-5"
></div>
<div
tw="absolute flex top-0 left-[90px] h-full w-px bg-black opacity-5"
></div>
<div
tw="absolute flex top-0 right-[90px] h-full w-px bg-black opacity-5"
></div>
<div tw="flex flex-col px-[100px] py-[90px] h-full">
<div tw="flex justify-center flex-col p-[30px] h-full">
<div tw="flex flex-col">
<div tw="flex items-center">
<img
src="${authorAvatar}"
width="30"
height="30"
tw="rounded-full"
/>
<div tw="text-[20px] leading-[20px] tracking-tight ml-3">
${authorName}
</div>
</div>
<div tw="mt-6 text-[48px] leading-tight tracking-tight">${title}</div>
<div tw="mt-3 text-[24px] leading-[30px] tracking-tight opacity-80">
${description}
</div>
</div>
</div>
</div>
</div> `;
}
function unescapeHtml(html) {
return html
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#039;/g, "'");
}
function hasSpecialCharacters(str) {
return /[&<>"]/.test(str);
}

View File

@@ -0,0 +1,16 @@
type AIAnnouncementProps = {};
export function AIAnnouncement(props: AIAnnouncementProps) {
return (
<a
className="rounded-md border border-dashed border-purple-600 px-3 py-1.5 text-purple-400 transition-colors hover:border-purple-400 hover:text-purple-200"
href="/ai"
>
<span className="relative -top-[1px] mr-1 text-xs font-semibold uppercase text-white">
New
</span>{' '}
<span className={'hidden sm:inline'}>Generate visual roadmaps with AI</span>
<span className={'inline text-sm sm:hidden'}>AI Roadmap Generator!</span>
</a>
);
}

View File

@@ -11,15 +11,16 @@ async function getSVG(name: string) {
const filepath = `/src/icons/${name}.svg`;
const files = import.meta.glob<string>('/src/icons/**/*.svg', {
query: '?raw',
eager: true,
as: 'raw',
});
if (!(filepath in files)) {
throw new Error(`${filepath} not found`);
}
const root = parse(files[filepath]);
const root = parse(files[filepath].default as string);
const svg = root.querySelector('svg');
@@ -35,4 +36,4 @@ const { attributes: baseAttributes, innerHTML } = await getSVG(icon);
const svgAttributes = { ...baseAttributes, ...attributes };
---
<svg {...svgAttributes} set:html={innerHTML}></svg>
<svg {...svgAttributes} set:html={innerHTML} />

View File

@@ -2,7 +2,7 @@ import Cookies from 'js-cookie';
import type { FormEvent } from 'react';
import { useState } from 'react';
import { httpPost } from '../../lib/http';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
type EmailLoginFormProps = {
isDisabled?: boolean;
@@ -34,11 +34,7 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
// Log the user in and reload the page
if (response?.token) {
Cookies.set(TOKEN_COOKIE_NAME, response.token, {
path: '/',
expires: 30,
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
setAuthToken(response.token);
window.location.reload();
return;

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import { httpGet } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner.tsx';
@@ -70,11 +70,7 @@ export function GitHubButton(props: GitHubButtonProps) {
localStorage.removeItem(GITHUB_REDIRECT_AT);
localStorage.removeItem(GITHUB_LAST_PAGE);
Cookies.set(TOKEN_COOKIE_NAME, response.token, {
path: '/',
expires: 30,
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
setAuthToken(response.token);
window.location.href = redirectUrl;
})
.catch((err) => {
@@ -105,7 +101,7 @@ export function GitHubButton(props: GitHubButtonProps) {
// For non authentication pages, we want to redirect back to the page
// the user was on before they clicked the social login button
if (!['/login', '/signup'].includes(window.location.pathname)) {
const pagePath = ['/respond-invite', '/befriend'].includes(
const pagePath = ['/respond-invite', '/befriend', '/r', '/ai'].includes(
window.location.pathname,
)
? window.location.pathname + window.location.search

Some files were not shown because too many files have changed in this diff Show More