Compare commits

...

24 Commits

Author SHA1 Message Date
Kamran Ahmed
bf7b1b02bd Styling for the topic page 2023-01-25 01:54:30 +04:00
Kamran Ahmed
7182312a18 Add pdfs for best practices 2023-01-25 01:36:46 +04:00
Kamran Ahmed
06fdfd780f Implement the state loading in checklists 2023-01-25 01:33:32 +04:00
Kamran Ahmed
3e56e83ece Disable user selection on the rectangles 2023-01-24 20:02:08 +04:00
Kamran Ahmed
516c5fac3a External links 2023-01-24 19:59:02 +04:00
Kamran Ahmed
525cad3189 Handle mark done/pending functionality in best practices 2023-01-24 19:57:42 +04:00
Kamran Ahmed
c8d15f37dd Remove sorting information from best practices content 2023-01-24 19:07:35 +04:00
Kamran Ahmed
17ad153583 Add forntend performance content 2023-01-24 18:36:14 +04:00
Kamran Ahmed
b7237cc2dc Toipc pages rendering 2023-01-24 02:11:38 +04:00
Kamran Ahmed
4d0e0e3cd7 Rearrange best practices pages 2023-01-24 02:03:15 +04:00
Kamran Ahmed
f50aefd5a5 Implement best-practice click handling 2023-01-24 02:01:28 +04:00
Kamran Ahmed
1928b89d71 Handle rendering of the roadmap topics 2023-01-24 01:58:21 +04:00
Kamran Ahmed
dbcf06244b Make topic overlay renderer agnostic 2023-01-24 01:34:20 +04:00
Kamran Ahmed
035e6a7abf Refactor the topic loading 2023-01-24 01:26:01 +04:00
Kamran Ahmed
d88c87bf52 Refactor sharer icons 2023-01-24 01:18:49 +04:00
Kamran Ahmed
87a50af927 Refactor share icons 2023-01-24 01:15:18 +04:00
Kamran Ahmed
0558c56fce Add rendering of best practices 2023-01-24 00:48:04 +04:00
Kamran Ahmed
1406458583 Refactor roadmap topic path 2023-01-23 19:25:27 +04:00
Kamran Ahmed
26f36a05f2 Add rendering for best practices lists 2023-01-23 19:04:03 +04:00
Kamran Ahmed
ffa8de84a6 Refactor roadmaps 2023-01-23 18:19:41 +04:00
Kamran Ahmed
7fee35237a Refactor markdown content 2023-01-23 18:06:07 +04:00
Kamran Ahmed
16eef91b30 Update best practices 2023-01-23 17:41:33 +04:00
Kamran Ahmed
7355818e49 Add best practices page 2023-01-23 17:36:55 +04:00
Kamran Ahmed
d197b91c0f Rearrange pdfs and images 2023-01-23 15:58:18 +04:00
142 changed files with 1196 additions and 459 deletions

View File

@@ -0,0 +1,144 @@
const fs = require('fs');
const path = require('path');
const CONTENT_DIR = path.join(__dirname, '../content');
// Directory containing the best-practices
const BEST_PRACTICE_CONTENT_DIR = path.join(__dirname, '../src/best-practices');
const bestPracticeId = process.argv[2];
const allowedBestPracticeId = fs.readdirSync(BEST_PRACTICE_CONTENT_DIR);
if (!bestPracticeId) {
console.error('bestPractice is required');
process.exit(1);
}
if (!allowedBestPracticeId.includes(bestPracticeId)) {
console.error(`Invalid best practice key ${bestPracticeId}`);
console.error(`Allowed keys are ${allowedBestPracticeId.join(', ')}`);
process.exit(1);
}
// Directory holding the best parctice content files
const bestPracticeDirName = fs
.readdirSync(BEST_PRACTICE_CONTENT_DIR)
.find((dirName) => dirName.replace(/\d+-/, '') === bestPracticeId);
if (!bestPracticeDirName) {
console.error('Best practice directory not found');
process.exit(1);
}
const bestPracticeDirPath = path.join(BEST_PRACTICE_CONTENT_DIR, bestPracticeDirName);
const bestPracticeContentDirPath = path.join(
BEST_PRACTICE_CONTENT_DIR,
bestPracticeDirName,
'content'
);
// If best practice content already exists do not proceed as it would override the files
if (fs.existsSync(bestPracticeContentDirPath)) {
console.error(`Best Practice content already exists @ ${bestPracticeContentDirPath}`);
process.exit(1);
}
function prepareDirTree(control, dirTree) {
// Directories are only created for groups
if (control.typeID !== '__group__') {
return;
}
// e.g. 104-testing-your-apps:other-options
const controlName = control?.properties?.controlName || '';
// No directory for a group without control name
if (!controlName || controlName.startsWith('check:') || controlName.startsWith('ext_link:')) {
return;
}
// e.g. ['testing-your-apps', 'other-options']
const dirParts = controlName.split(':');
// Nest the dir path in the dirTree
let currDirTree = dirTree;
dirParts.forEach((dirPart) => {
currDirTree[dirPart] = currDirTree[dirPart] || {};
currDirTree = currDirTree[dirPart];
});
const childrenControls = control.children.controls.control;
// No more children
if (childrenControls.length) {
childrenControls.forEach((childControl) => {
prepareDirTree(childControl, dirTree);
});
}
return { dirTree };
}
const bestPractice = require(path.join(__dirname, `../public/jsons/best-practices/${bestPracticeId}`));
const controls = bestPractice.mockup.controls.control;
// Prepare the dir tree that we will be creating
const dirTree = {};
controls.forEach((control) => {
prepareDirTree(control, dirTree);
});
/**
* @param parentDir Parent directory in which directory is to be created
* @param dirTree Nested dir tree to be created
* @param filePaths The mapping from groupName to file path
*/
function createDirTree(parentDir, dirTree, filePaths = {}) {
const childrenDirNames = Object.keys(dirTree);
const hasChildren = childrenDirNames.length !== 0;
// @todo write test for this, yolo for now
const groupName = parentDir
.replace(bestPracticeContentDirPath, '') // Remove base dir path
.replace(/(^\/)|(\/$)/g, '') // Remove trailing slashes
.replaceAll('/', ':') // Replace slashes with `:`
.replace(/:\d+-/, ':');
const humanizedGroupName = groupName
.split(':')
.pop()
?.replaceAll('-', ' ')
.replace(/^\w/, ($0) => $0.toUpperCase());
// If no children, create a file for this under the parent directory
if (!hasChildren) {
let fileName = `${parentDir}.md`;
fs.writeFileSync(fileName, `# ${humanizedGroupName}`);
filePaths[groupName || 'home'] = fileName.replace(CONTENT_DIR, '');
return filePaths;
}
// There *are* children, so create the parent as a directory
// and create `index.md` as the content file for this
fs.mkdirSync(parentDir);
let readmeFilePath = path.join(parentDir, 'index.md');
fs.writeFileSync(readmeFilePath, `# ${humanizedGroupName}`);
filePaths[groupName || 'home'] = readmeFilePath.replace(CONTENT_DIR, '');
// For each of the directory names, create a
// directory inside the given directory
childrenDirNames.forEach((dirName) => {
createDirTree(
path.join(parentDir, dirName),
dirTree[dirName],
filePaths
);
});
return filePaths;
}
// Create directories and get back the paths for created directories
createDirTree(bestPracticeContentDirPath, dirTree);
console.log('Created best practice content directory structure');

28
bin/readme.md Normal file
View File

@@ -0,0 +1,28 @@
## CLI Tools
> A bunch of CLI scripts to make the development easier
## `roadmap-links.cjs`
Generates a list of all the resources links in any roadmap file.
## `compress-jsons.cjs`
Compresses all the JSON files in the `public/jsons` folder
## `roadmap-content.cjs`
This command is used to create the content folders and files for the interactivity of the roadmap. You can use the below command to generate the roadmap skeletons inside a roadmap directory:
```bash
npm run roadmap-content [frontend|backend|devops|...]
```
For the content skeleton to be generated, we should have proper grouping, and the group names in the project files. You can follow the steps listed below in order to add the meta information to the roadmap.
- Remove all the groups from the roadmaps through the project editor. Select all and press `cmd+shift+g`
- Identify the boxes that should be clickable and group them together with `cmd+shift+g`
- Assign the name to the groups.
- Group names have the format of `[sort]-[slug]` e.g. `100-internet`. Each group name should start with a number starting from 100 which helps with sorting of the directories and the files. Groups at the same level have the sequential sorting information.
- Each groups children have a separate group and have the name similar to `[sort]-[parent-slug]:[child-slug]` where sort refers to the sorting of the `child-slug` and not the parent. Also parent-slug does not need to have the sorting information as a part of slug e.g. if parent was `100-internet` the children would be `100-internet:how-does-the-internet-work`, `101-internet:what-is-http`, `102-internet:browsers`.

View File

@@ -14,6 +14,7 @@
"upgrade": "ncu -u",
"roadmap-links": "node bin/roadmap-links.cjs",
"roadmap-content": "node bin/roadmap-content.cjs",
"best-practice-content": "node bin/best-practice-content.cjs",
"test:e2e": "playwright test"
},
"dependencies": {
@@ -24,7 +25,7 @@
"node-html-parser": "^6.1.4",
"npm-check-updates": "^16.6.2",
"rehype-external-links": "^2.0.1",
"roadmap-renderer": "^1.0.1",
"roadmap-renderer": "^1.0.4",
"tailwindcss": "^3.2.4"
},
"devDependencies": {

8
pnpm-lock.yaml generated
View File

@@ -15,7 +15,7 @@ specifiers:
prettier: ^2.8.3
prettier-plugin-astro: ^0.7.2
rehype-external-links: ^2.0.1
roadmap-renderer: ^1.0.1
roadmap-renderer: ^1.0.4
tailwindcss: ^3.2.4
dependencies:
@@ -26,7 +26,7 @@ dependencies:
node-html-parser: 6.1.4
npm-check-updates: 16.6.2
rehype-external-links: 2.0.1
roadmap-renderer: 1.0.1
roadmap-renderer: 1.0.4
tailwindcss: 3.2.4
devDependencies:
@@ -4729,8 +4729,8 @@ packages:
glob: 7.2.3
dev: false
/roadmap-renderer/1.0.1:
resolution: {integrity: sha512-f71DLNMfBNtwNwa5ffkXVRBL24loYJ7YMcyyeAUhbJMzEQYp9vWaArVGualylBIw95APy/UIgBZ9KuqiW1Y4UA==}
/roadmap-renderer/1.0.4:
resolution: {integrity: sha512-TS9jDZu/CzTqxv7QWnMZHgB89WzgLpaExXKcBIWQEKtXm9g9E45t7gijZst9qtRQ2E2+iplAxQz/eMuttq4wAQ==}
dev: false
/roarr/2.15.4:

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1 @@
# Analyse stylesheets complexity

View File

@@ -0,0 +1 @@
# Analyze js for perf issues

View File

@@ -0,0 +1 @@
# Avoid 404 files

View File

@@ -0,0 +1 @@
# Avoid base64 images

View File

@@ -0,0 +1 @@
# Avoid inline css

View File

@@ -0,0 +1 @@
# Avoid multiple inline js snippets

View File

@@ -0,0 +1 @@
# Bundlephobia

View File

@@ -0,0 +1 @@
# Check dependency size

View File

@@ -0,0 +1 @@
# Choose image format approprietly

View File

@@ -0,0 +1 @@
# Chrome dev tools

View File

@@ -0,0 +1 @@
# Compress your images

View File

@@ -0,0 +1 @@
# Concatenate css single file

View File

@@ -0,0 +1 @@
# Cookie size less 4096 bytes

View File

@@ -0,0 +1 @@
# Enable compression

View File

@@ -0,0 +1 @@
# Framework guides

View File

@@ -0,0 +1 @@
#

View File

@@ -0,0 +1 @@
# Inline critical css

View File

@@ -0,0 +1 @@
# Keep cookie count below 20

View File

@@ -0,0 +1 @@
# Keep dependencies up to date

View File

@@ -0,0 +1 @@
# Keep ttfb less 1 3s

View File

@@ -0,0 +1 @@
# Keep web font under 300k

View File

@@ -0,0 +1 @@
# Lighthouse

View File

@@ -0,0 +1 @@
# Load offscreen images lazily

View File

@@ -0,0 +1 @@
# Make css files non blocking

View File

@@ -0,0 +1 @@
# Minify css

View File

@@ -0,0 +1 @@
# Minify html

View File

@@ -0,0 +1 @@
# Minify your javascript

View File

@@ -0,0 +1 @@
# Minimize http requests

View File

@@ -0,0 +1,3 @@
# Avoid iframes
Use iframes only if you don't have any other technical possibility. Try to avoid iframes as much as you can. Iframes are not only bad for performance, but also for accessibility and usability. Iframes are also not indexed by search engines.

View File

@@ -0,0 +1 @@
# Page load time below 3s

View File

@@ -0,0 +1 @@
# Page speed insights

View File

@@ -0,0 +1 @@
# Page weight below 1500

View File

@@ -0,0 +1 @@
# Pre load urls where possible

View File

@@ -0,0 +1 @@
# Prefer vector images

View File

@@ -0,0 +1 @@
# Prevent flash text

View File

@@ -0,0 +1 @@
# Recommended guides

View File

@@ -0,0 +1 @@
# Remove unused css

View File

@@ -0,0 +1 @@
# Serve exact size images

View File

@@ -0,0 +1 @@
# Set width height images

View File

@@ -0,0 +1 @@
# Squoosh ap

View File

@@ -0,0 +1 @@
# Use cdn

View File

@@ -0,0 +1 @@
# Use http cache headers

View File

@@ -0,0 +1 @@
# Use https on your website

View File

@@ -0,0 +1 @@
# Use non blocking javascript

View File

@@ -0,0 +1 @@
# Use preconnect to load fonts

View File

@@ -0,0 +1 @@
# Use same protocol

View File

@@ -0,0 +1 @@
# Use service workers for caching

View File

@@ -0,0 +1 @@
# Use woff2 font format

View File

@@ -0,0 +1 @@
# Web page test

View File

@@ -0,0 +1,29 @@
---
jsonUrl: "/jsons/best-practices/frontend-performance.json"
pdfUrl: "/pdfs/best-practices/frontend-performance.pdf"
order: 1
featuredTitle: "Frontend Performance"
featuredDescription: "Frontend Performance Best Practices"
isNew: true
isUpcoming: false
title: "Frontend Performance"
description: "Detailed list of best practices to improve your frontend performance"
dimensions:
width: 968
height: 1270.89
schema:
headline: "Frontend Performance Best Practices"
description: "Detailed list of best practices to improve the frontend performance of your website. Each best practice carries further details and how to implement that best practice."
imageUrl: "https://roadmap.sh/best-practices/frontend-performance.png"
datePublished: "2023-01-23"
dateModified: "2023-01-23"
seo:
title: "Frontend Performance Best Practices"
description: "Detailed list of best practices to improve the frontend performance of your website. Each best practice carries further details and how to implement that best practice."
keywords:
- "frontend performance"
- "frontend performance best practices"
- "frontend performance checklist"
- "frontend checklist"
- "make performant frontends"
---

View File

@@ -0,0 +1,87 @@
---
import BestPracticeHint from './BestPracticeHint.astro';
import DownloadPopup from './DownloadPopup.astro';
import Icon from './Icon.astro';
import SubscribePopup from './SubscribePopup.astro';
export interface Props {
title: string;
description: string;
bestPracticeId: string;
isUpcoming?: boolean;
}
const { title, description, bestPracticeId, isUpcoming = false } = Astro.props;
const isBestPracticeReady = !isUpcoming;
---
<DownloadPopup />
<SubscribePopup />
<div class='border-b'>
<div class='py-5 sm:py-12 container relative'>
<div class='mt-0 mb-3 sm:mb-6'>
<h1 class='text-2xl sm:text-4xl mb-0.5 sm:mb-2 font-bold'>
{title}
</h1>
<p class='text-gray-500 text-sm sm:text-lg'>{description}</p>
</div>
<div class='flex justify-between'>
<div class='flex gap-1 sm:gap-2'>
<a
href='/best-practices'
class='bg-gray-500 py-1.5 px-3 rounded-md text-white text-xs sm:text-sm font-medium hover:bg-gray-600'
aria-label='Back to All Best Practices'
>
&larr;<span class='hidden sm:inline'>&nbsp;All Best Practices</span>
</a>
{
isBestPracticeReady && (
<button
data-popup='download-popup'
class='inline-flex items-center justify-center bg-yellow-400 py-1.5 px-3 text-xs sm:text-sm font-medium rounded-md hover:bg-yellow-500'
aria-label='Download Best Practice'
ga-category='Subscription'
ga-action='Clicked Popup Opener'
ga-label='Download Best Practice Popup'
>
<Icon icon='download' />
<span class='hidden sm:inline ml-2'>Download</span>
</button>
)
}
<button
data-popup='subscribe-popup'
class='inline-flex items-center justify-center bg-yellow-400 py-1.5 px-3 text-xs sm:text-sm font-medium rounded-md hover:bg-yellow-500'
aria-label='Subscribe for Updates'
ga-category='Subscription'
ga-action='Clicked Popup Opener'
ga-label='Subscribe Best Practice Popup'
>
<Icon icon='email' />
<span class='ml-2'>Subscribe</span>
</button>
</div>
{
isBestPracticeReady && (
<a
href={`https://github.com/kamranahmedse/developer-roadmap/issues/new?title=[Suggestion] ${title}`}
target='_blank'
class='inline-flex items-center justify-center bg-gray-500 text-white py-1.5 px-3 text-xs sm:text-sm font-medium rounded-md hover:bg-gray-600'
aria-label='Suggest Changes'
>
<Icon icon='comment' class='h-3 w-3' />
<span class='ml-2 hidden sm:inline'>Suggest Changes</span>
<span class='ml-2 inline sm:hidden'>Suggest</span>
</a>
)
}
</div>
<BestPracticeHint bestPracticeId={bestPracticeId} />
</div>
</div>

View File

@@ -0,0 +1,20 @@
---
export interface Props {
bestPracticeId: string;
}
---
<div class='mt-4 sm:mt-7 border-0 sm:border rounded-md mb-0 sm:-mb-[65px]'>
<!-- Desktop: Roadmap Resources - Alert -->
<div class='hidden sm:flex justify-between px-2 bg-white items-center rounded-md p-1.5'>
<p class='text-sm'>
<span class='text-yellow-900 bg-yellow-200 py-0.5 px-1 text-xs rounded-sm font-medium uppercase mr-0.5'>Tip</span>
Click the best practices for details and resources
</p>
</div>
<!-- Mobile - Roadmap resources alert -->
<p class='block sm:hidden text-sm border border-yellow-500 text-yellow-700 rounded-md py-1.5 px-2 bg-white relative'>
Click the best practices for details and resources
</p>
</div>

View File

@@ -1,5 +1,5 @@
---
import type { BreadcrumbItem } from '../lib/topic';
import type { BreadcrumbItem } from '../lib/roadmap-topic';
export interface Props {
breadcrumbs: BreadcrumbItem[];

View File

@@ -5,7 +5,7 @@ import CaptchaFields from './Captcha/CaptchaFields.astro';
<Popup
id='download-popup'
title='Download Roadmap'
title='Download'
subtitle='Enter your email below to receive the download link.'
>
<form

View File

@@ -9,11 +9,9 @@ export interface Props {
const { featuredItems, heading } = Astro.props;
---
<div class='py-4 sm:py-14 border-b border-b-slate-900 relative'>
<div class='py-4 sm:py-14 border-b border-b-[#1e293c] relative'>
<div class='container'>
<h2
class='hidden sm:flex absolute rounded-lg -top-[17px] left-1/2 -translate-x-1/2 bg-slate-900 py-1 px-3 border border-slate-900 text-md text-slate-400 font-regular'
>
<h2 class='hidden sm:flex absolute rounded-lg -top-[17px] left-1/2 -translate-x-1/2 bg-slate-900 py-1 px-3 border border-[#1e293c] text-md text-slate-400 font-regular'>
{heading}
</h2>

View File

@@ -0,0 +1,29 @@
---
import Loader from '../Loader.astro';
import TopicOverlay from '../TopicOverlay/TopicOverlay.astro';
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;
---
<div
id='resource-svg'
style={dimensions ? `--aspect-ratio:${dimensions.width}/${dimensions.height}` : null}
data-resource-type={resourceType}
data-resource-id={resourceId}
data-json-url={jsonUrl}
>
<Loader />
</div>
<script src='./renderer.js'></script>

View File

@@ -0,0 +1,94 @@
svg text tspan {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeSpeed;
}
code {
background: #1e1e3f;
color: #9efeff;
padding: 3px 5px;
font-size: 14px;
border-radius: 3px;
}
svg .clickable-group {
cursor: pointer;
}
svg .clickable-group:hover > [fill='rgb(65,53,214)'] {
fill: #232381;
stroke: #232381;
}
svg .clickable-group:hover > [fill='rgb(255,255,0)'] {
fill: #d6d700;
}
svg .clickable-group:hover > [fill='rgb(255,229,153)'] {
fill: #f3c950;
}
svg .clickable-group:hover > [fill='rgb(153,153,153)'] {
fill: #646464;
}
svg .clickable-group:hover > [fill='rgb(255,255,255)'] {
fill: #d7d7d7;
}
svg .clickable-group:hover > [fill='rgb(255,255,221)'] {
fill: #e5e5be;
}
svg .clickable-group:hover > [fill='rgb(255,217,102)'] {
fill: #d9b443;
}
svg .done rect {
fill: #cbcbcb !important;
}
svg .done text {
text-decoration: line-through;
}
svg .clickable-group.done[data-group-id^='check:'] rect {
fill: gray !important;
stroke: gray;
}
.clickable-group rect {
user-select: none;
}
/************************************
Aspect ratio implementation
*************************************/
[style*='--aspect-ratio'] > :first-child {
width: 100%;
}
[style*='--aspect-ratio'] > img {
height: auto;
}
@supports (--custom: property) {
[style*='--aspect-ratio'] {
position: relative;
}
[style*='--aspect-ratio']::before {
content: '';
display: block;
/*noinspection CssUnresolvedCustomProperty*/
padding-bottom: calc(100% / (var(--aspect-ratio)));
}
[style*='--aspect-ratio'] > :first-child {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
}

View File

@@ -1,25 +1,17 @@
import { wireframeJSONToSVG } from 'roadmap-renderer';
import { Topic } from './topic';
import { Sharer } from './sharer';
/**
* @typedef {{ roadmapId: string, jsonUrl: string }} RoadmapConfig
*/
export class Roadmap {
/**
* @param {RoadmapConfig} config
*/
export class Renderer {
constructor() {
this.roadmapId = '';
this.resourceId = '';
this.resourceType = '';
this.jsonUrl = '';
this.containerId = 'roadmap-svg';
this.containerId = 'resource-svg';
this.init = this.init.bind(this);
this.onDOMLoaded = this.onDOMLoaded.bind(this);
this.fetchRoadmapSvg = this.fetchRoadmapSvg.bind(this);
this.handleRoadmapClick = this.handleRoadmapClick.bind(this);
this.jsonToSvg = this.jsonToSvg.bind(this);
this.handleSvgClick = this.handleSvgClick.bind(this);
this.prepareConfig = this.prepareConfig.bind(this);
}
@@ -34,7 +26,8 @@ export class Roadmap {
const dataset = this.containerEl.dataset;
this.roadmapId = dataset.roadmapId;
this.resourceType = dataset.resourceType;
this.resourceId = dataset.resourceId;
this.jsonUrl = dataset.jsonUrl;
return true;
@@ -44,7 +37,7 @@ export class Roadmap {
* @param { string } jsonUrl
* @returns {Promise<SVGElement>}
*/
fetchRoadmapSvg(jsonUrl) {
jsonToSvg(jsonUrl) {
if (!jsonUrl) {
console.error('jsonUrl not defined in frontmatter');
return null;
@@ -66,14 +59,14 @@ export class Roadmap {
return;
}
this.fetchRoadmapSvg(this.jsonUrl)
this.jsonToSvg(this.jsonUrl)
.then((svg) => {
document.getElementById(this.containerId).replaceChildren(svg);
})
.catch(console.error);
}
handleRoadmapClick(e) {
handleSvgClick(e) {
const targetGroup = e.target.closest('g') || {};
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
if (!groupId) {
@@ -82,11 +75,32 @@ export class Roadmap {
e.stopImmediatePropagation();
if (/^ext_link/.test(groupId)) {
window.open(`https://${groupId.replace('ext_link:', '')}`);
return;
}
if (/^check:/.test(groupId)) {
window.dispatchEvent(
new CustomEvent(`${this.resourceType}.topic.toggle`, {
detail: {
topicId: groupId.replace('check:', ''),
resourceType: this.resourceType,
resourceId: this.resourceId,
},
})
);
return;
}
// Remove sorting prefix from groupId
const normalizedGroupId = groupId.replace(/^\d+-/, '');
window.dispatchEvent(
new CustomEvent('topic.click', {
new CustomEvent(`${this.resourceType}.topic.click`, {
detail: {
topicId: groupId,
roadmapId: this.roadmapId,
topicId: normalizedGroupId,
resourceId: this.resourceId,
},
})
);
@@ -94,17 +108,9 @@ export class Roadmap {
init() {
window.addEventListener('DOMContentLoaded', this.onDOMLoaded);
window.addEventListener('click', this.handleRoadmapClick);
window.addEventListener('click', this.handleSvgClick);
}
}
const roadmap = new Roadmap();
roadmap.init();
// Initialize the topic loader
const topic = new Topic();
topic.init();
// Handles the share icons on the roadmap page
const sharer = new Sharer();
sharer.init();
const renderer = new Renderer();
renderer.init();

View File

@@ -0,0 +1,32 @@
---
import type { RoadmapFileType } from '../lib/roadmap';
export interface Props {
url: string;
title: string;
description: string;
isNew: boolean;
}
const { url, title, description, isNew } = Astro.props;
---
<a
href={url}
class='bg-gradient-to-r from-slate-900 to-amber-900 hover:from-stone-900 hover:to-stone-900 hover:bg-gray-100 flex flex-col p-2.5 sm:p-5 rounded-md sm:rounded-lg border border-gray-200 relative h-full'
>
<span
class='font-regular sm:font-medium text-md sm:text-xl hover:text-gray-50 text-gray-200 sm:text-gray-100 mb-0 sm:mb-1.5'
>
{title}
</span>
<span class='text-sm leading-normal text-gray-400 hidden sm:block'>{description}</span>
{
isNew && (
<span class='absolute bottom-1 right-1 bg-yellow-300 text-yellow-900 text-xs font-medium px-1 sm:px-1.5 py-0.5 rounded-sm uppercase'>
New
</span>
)
}
</a>

View File

@@ -1,31 +0,0 @@
---
import type { RoadmapFileType } from '../lib/roadmap';
export interface Props {
roadmap: RoadmapFileType;
}
const { roadmap } = Astro.props;
const frontmatter = roadmap.frontmatter;
---
<a
href={`/${roadmap.id}`}
class="bg-gradient-to-r from-slate-900 to-amber-900 hover:from-stone-900 hover:to-stone-900 hover:bg-gray-100 flex flex-col p-2.5 sm:p-5 rounded-md sm:rounded-lg border border-gray-200 relative h-full"
>
<span
class="font-regular sm:font-medium text-md sm:text-xl hover:text-gray-50 text-gray-200 sm:text-gray-100 mb-0 sm:mb-1.5"
>{frontmatter.title}</span
>
<span class="text-sm leading-normal text-gray-400 hidden sm:block"
>{frontmatter.description}</span
>
{
frontmatter.isNew && (
<span class="absolute bottom-1 right-1 bg-yellow-300 text-yellow-900 text-xs font-medium px-1 sm:px-1.5 py-0.5 rounded-sm uppercase">
New
</span>
)
}
</a>

View File

@@ -1,53 +0,0 @@
---
import DownloadPopup from '../DownloadPopup.astro';
import Loader from '../Loader.astro';
import ShareIcons from '../ShareIcons.astro';
import SubscribePopup from '../SubscribePopup.astro';
import TopicOverlay from '../TopicOverlay.astro';
import './InteractiveRoadmap.css';
export interface Props {
roadmapId: string;
description: string;
jsonUrl: string;
dimensions?: {
width: number;
height: number;
};
}
const { roadmapId, jsonUrl, dimensions = null, description } = Astro.props;
---
<link
rel='preload'
href='/fonts/balsamiq.woff2'
as='font'
type='font/woff2'
crossorigin
slot='after-header'
/>
<div class='bg-gray-50 py-4 sm:py-12'>
<div class='max-w-[1000px] container relative'>
<ShareIcons
description={description}
pageUrl={`https://roadmap.sh/${roadmapId}`}
/>
<DownloadPopup />
<SubscribePopup />
<TopicOverlay roadmapId={roadmapId} />
<div
id='roadmap-svg'
style={dimensions
? `--aspect-ratio:${dimensions.width}/${dimensions.height}`
: null}
data-roadmap-id={roadmapId}
data-json-url={jsonUrl}
>
<Loader />
</div>
</div>
</div>
<script src='./roadmap.js'></script>

View File

@@ -1,86 +0,0 @@
svg text tspan {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeSpeed;
}
code {
background: #1e1e3f;
color: #9efeff;
padding: 3px 5px;
font-size: 14px;
border-radius: 3px;
}
svg .clickable-group {
cursor: pointer;
}
svg .clickable-group:hover > [fill='rgb(65,53,214)'] {
fill: #232381;
stroke: #232381;
}
svg .clickable-group:hover > [fill='rgb(255,255,0)'] {
fill: #d6d700;
}
svg .clickable-group:hover > [fill='rgb(255,229,153)'] {
fill: #f3c950;
}
svg .clickable-group:hover > [fill='rgb(153,153,153)'] {
fill: #646464;
}
svg .clickable-group:hover > [fill='rgb(255,255,255)'] {
fill: #d7d7d7;
}
svg .clickable-group:hover > [fill='rgb(255,255,221)'] {
fill: #e5e5be;
}
svg .clickable-group:hover > [fill='rgb(255,217,102)'] {
fill: #d9b443;
}
svg .done rect {
fill: #cbcbcb !important;
}
svg .done text {
text-decoration: line-through;
}
/************************************
Aspect ratio implementation
*************************************/
[style*="--aspect-ratio"] > :first-child {
width: 100%;
}
[style*="--aspect-ratio"] > img {
height: auto;
}
@supports (--custom:property) {
[style*="--aspect-ratio"] {
position: relative;
}
[style*="--aspect-ratio"]::before {
content: "";
display: block;
/*noinspection CssUnresolvedCustomProperty*/
padding-bottom: calc(100% / (var(--aspect-ratio)));
}
[style*="--aspect-ratio"] > :first-child {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
}

View File

@@ -0,0 +1,5 @@
<div
class='prose-blockquote:font-normal prose container prose-code:bg-transparent prose-h2:text-3xl prose-h2:mt-4 prose-h2:mb-2 prose-h3:mt-2 prose-img:mt-1'
>
<slot />
</div>

View File

@@ -1,22 +0,0 @@
---
import DownloadPopup from './DownloadPopup.astro';
import SubscribePopup from './SubscribePopup.astro';
export interface Props {
roadmapId: string;
description: string;
}
const { roadmapId, description } = Astro.props;
---
<div class='bg-gray-50 py-2'>
<div
class='container prose prose-headings:mt-4 prose-headings:mb-2 prose-p:mb-0.5 relative prose-code:text-white'
>
<DownloadPopup />
<SubscribePopup />
<slot />
</div>
</div>

View File

@@ -14,6 +14,9 @@ import Icon from './Icon.astro';
<li>
<a href='/roadmaps' class='text-gray-400 hover:text-white'>Roadmaps</a>
</li>
<li>
<a href='/best-practices' class='text-gray-400 hover:text-white'>Best Practices</a>
</li>
<li>
<a href='/guides' class='text-gray-400 hover:text-white'>Guides</a>
</li>
@@ -48,6 +51,9 @@ import Icon from './Icon.astro';
<li>
<a href='/roadmaps' class='text-xl md:text-lg hover:text-blue-300'>Roadmaps</a>
</li>
<li>
<a href='/best-practices' class='text-xl md:text-lg hover:text-blue-300'>Best Practices</a>
</li>
<li>
<a href='/guides' class='text-xl md:text-lg hover:text-blue-300'>Guides</a>
</li>

View File

@@ -1,7 +1,9 @@
---
import DownloadPopup from './DownloadPopup.astro';
import Icon from './Icon.astro';
import ResourcesAlert from './ResourcesAlert.astro';
import RoadmapHint from './RoadmapHint.astro';
import RoadmapNote from './RoadmapNote.astro';
import SubscribePopup from './SubscribePopup.astro';
import TopicSearch from './TopicSearch/TopicSearch.astro';
import YouTubeAlert from './YouTubeAlert.astro';
@@ -20,6 +22,9 @@ const { title, description, roadmapId, isUpcoming = false, hasSearch = false, no
const isRoadmapReady = !isUpcoming;
---
<DownloadPopup />
<SubscribePopup />
<div class='border-b'>
<div class='py-5 sm:py-12 container relative'>
<YouTubeAlert />
@@ -104,7 +109,7 @@ const isRoadmapReady = !isUpcoming;
</div>
<!-- Desktop: Roadmap Resources - Alert -->
{hasTopics && <ResourcesAlert roadmapId={roadmapId} />}
{hasTopics && <RoadmapHint roadmapId={roadmapId} />}
{hasSearch && <TopicSearch />}
</div>

View File

@@ -1,43 +0,0 @@
---
import Icon from "./Icon.astro";
export interface Props {
pageUrl: string;
description: string;
}
const { pageUrl, description } = Astro.props;
const twitterUrl = `https://twitter.com/intent/tweet?text=${description}&url=${pageUrl}`;
const fbUrl = `https://www.facebook.com/sharer/sharer.php?quote=${description}&u=${pageUrl}`;
const hnUrl = `https://news.ycombinator.com/submitlink?t=${description}&u=${pageUrl}`;
const redditUrl = `https://www.reddit.com/submit?title=${description}&url=${pageUrl}`;
---
<div
class="absolute left-[-18px] top-[110px] h-full hidden"
id="page-share-icons"
>
<div class="flex sticky top-[100px] flex-col gap-1.5">
<a
href={twitterUrl}
target="_blank"
class="text-gray-500 hover:text-gray-700"
>
<Icon icon="twitter" />
</a>
<a href={fbUrl} target="_blank" class="text-gray-500 hover:text-gray-700">
<Icon icon="facebook" />
</a>
<a href={hnUrl} target="_blank" class="text-gray-500 hover:text-gray-700">
<Icon icon="hackernews" />
</a>
<a
href={redditUrl}
target="_blank"
class="text-gray-500 hover:text-gray-700"
>
<Icon icon="reddit" />
</a>
</div>
</div>

View File

@@ -0,0 +1,34 @@
---
import Icon from '../Icon.astro';
export interface Props {
pageUrl: string;
description: string;
}
const { pageUrl, description } = Astro.props;
const twitterUrl = `https://twitter.com/intent/tweet?text=${description}&url=${pageUrl}`;
const fbUrl = `https://www.facebook.com/sharer/sharer.php?quote=${description}&u=${pageUrl}`;
const hnUrl = `https://news.ycombinator.com/submitlink?t=${description}&u=${pageUrl}`;
const redditUrl = `https://www.reddit.com/submit?title=${description}&url=${pageUrl}`;
---
<div class='absolute left-[-18px] top-[110px] h-full hidden' id='page-share-icons'>
<div class='flex sticky top-[100px] flex-col gap-1.5'>
<a href={twitterUrl} target='_blank' class='text-gray-500 hover:text-gray-700'>
<Icon icon='twitter' />
</a>
<a href={fbUrl} target='_blank' class='text-gray-500 hover:text-gray-700'>
<Icon icon='facebook' />
</a>
<a href={hnUrl} target='_blank' class='text-gray-500 hover:text-gray-700'>
<Icon icon='hackernews' />
</a>
<a href={redditUrl} target='_blank' class='text-gray-500 hover:text-gray-700'>
<Icon icon='reddit' />
</a>
</div>
</div>
<script src='./sharer.js'></script>

View File

@@ -27,3 +27,6 @@ export class Sharer {
window.addEventListener('scroll', this.onScroll, { passive: true });
}
}
const sharer = new Sharer();
sharer.init();

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