Compare commits

..

1 Commits

Author SHA1 Message Date
Arik Chakma
eb0ab87d34 fix: progress nudge count 2024-09-16 17:19:30 +06:00
880 changed files with 14219 additions and 7497 deletions

8
.astro/settings.json Normal file
View File

@@ -0,0 +1,8 @@
{
"devToolbar": {
"enabled": false
},
"_variables": {
"lastUpdateCheck": 1725962974592
}
}

1
.astro/types.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="astro/client" />

1
.gitignore vendored
View File

@@ -33,4 +33,3 @@ tests-examples
!/editor/readonly-editor.tsx
!/editor/renderer/renderer.ts
!/editor/renderer/index.tsx
/.astro

View File

@@ -50,12 +50,11 @@
"jose": "^5.6.3",
"js-cookie": "^3.0.5",
"lucide-react": "^0.419.0",
"luxon": "^3.5.0",
"nanoid": "^5.0.7",
"nanostores": "^0.10.3",
"node-html-parser": "^6.1.13",
"npm-check-updates": "^17.0.0",
"playwright": "^1.47.1",
"playwright": "^1.45.3",
"prismjs": "^1.29.0",
"react": "^18.3.1",
"react-calendar-heatmap": "^1.9.0",
@@ -81,7 +80,6 @@
"@tailwindcss/typography": "^0.5.13",
"@types/dom-to-image": "^2.6.7",
"@types/js-cookie": "^3.0.6",
"@types/luxon": "^3.4.2",
"@types/prismjs": "^1.26.4",
"@types/react-calendar-heatmap": "^1.6.7",
"@types/turndown": "^5.0.5",

1608
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -620,19 +620,8 @@
},
"e3vHFaFFMV7kI9q6yf5e9": {
"title": "Cloud Messaging",
"description": "Firebase Cloud Messaging (FCM) is a powerful, battery-efficient messaging service that enables you to send messages reliably and securely to your Android applications. It enables you to send two types of messages: \"notification messages\" and \"data messages\". Notification messages are primarily meant for user notifications and will only be delivered when the application is in the foreground. On the other hand, data messages can handle even when the app is in the background or killed and can be used to send custom key-value pairs. FCM also supports various additional features, such as topic messaging to send messages to multiple devices subscribed to a common topic, device group messaging for sending messages to groups of user devices, and upstream messaging for sending messages from the client application to the FCM server.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Documentation",
"url": "https://firebase.google.com/docs/cloud-messaging/android/client",
"type": "article"
},
{
"title": "Firebase Cloud Messaging",
"url": "https://www.youtube.com/watch?v=sioEY4tWmLI&list=PLl-K7zZEsYLkuHRCtHTpi6JYHka8oHLft",
"type": "video"
}
]
"description": "Firebase Cloud Messaging (FCM) is a powerful, battery-efficient messaging service that enables you to send messages reliably and securely to your Android applications. It enables you to send two types of messages: \"notification messages\" and \"data messages\". Notification messages are primarily meant for user notifications and will only be delivered when the application is in the foreground. On the other hand, data messages can handle even when the app is in the background or killed and can be used to send custom key-value pairs. FCM also supports various additional features, such as topic messaging to send messages to multiple devices subscribed to a common topic, device group messaging for sending messages to groups of user devices, and upstream messaging for sending messages from the client application to the FCM server.",
"links": []
},
"3EEfKAd-ppIQpdQSEhbA1": {
"title": "FireStore",

View File

@@ -12,42 +12,12 @@
},
"DE3cMpeRYuUPw2ADtfS-3": {
"title": "Angular Architecture",
"description": "Angular follows a modular architecture pattern, dividing the application into distinct modules, components, services, and other elements, which enhances code organization and maintainability. The key building blocks include modules, which are containers grouping related components, services, directives, and other elements to ensure proper encapsulation and reusability. Components are the building blocks of Angular applications, representing parts of the user interface with associated logic, consisting of templates, styles, and a class defining behavior. Services encapsulate reusable business logic, data manipulation, and API communication, enabling data and functionality sharing across components. Directives are HTML attributes or elements that extend HTML functionality, allowing reusable behaviors across the application. Lastly, pipes transform data before displaying it in templates, providing convenient ways to format, filter, and sort data.\n\nVisit the following resources to learn more:",
"description": "Visit the following resources to learn more:",
"links": [
{
"title": "Angular coding style guide",
"url": "https://angular.dev/style-guide",
"type": "article"
},
{
"title": "The Ultimate Guide to Angular Architecture: Best Practices for efficient coding with Angular Framework",
"url": "https://angulardive.com/blog/the-ultimate-guide-to-angular-architecture-best-practices-for-efficient-coding-with-angular-framework/",
"type": "article"
},
{
"title": "Modern Architectures with Angular Part 1: Strategic design with Sheriff and Standalone Components",
"url": "https://www.angulararchitects.io/en/blog/modern-architectures-with-angular-part-1-strategic-design-with-sheriff-and-standalone-components/",
"type": "article"
},
{
"title": "Optimizing the architecture of large web applications with Angular",
"url": "https://albertobasalo.medium.com/optimizing-the-architecture-of-large-web-applications-with-angular-79d03b01a92b",
"type": "article"
},
{
"title": "Angular Architecture Concepts and Patterns",
"url": "https://www.bigscal.com/blogs/frontend/angular-architecture-concepts-and-patterns/",
"type": "article"
},
{
"title": "Top 10 Angular Architecture Mistakes",
"url": "https://angularexperts.io/blog/top-10-angular-architecture-mistakes",
"type": "article"
},
{
"title": "Architecting Angular: A Guide to effective project structure",
"url": "https://medium.com/@nile.bits/architecting-angular-a-guide-to-effective-project-structure-9756bae92262",
"type": "article"
}
]
},

View File

@@ -134,7 +134,7 @@
"links": [
{
"title": "Visit Dedicated Go Roadmap",
"url": "https://roadmap.sh/golang",
"url": "/golang",
"type": "article"
},
{
@@ -170,7 +170,7 @@
"links": [
{
"title": "Visit Dedicated JavaScript Roadmap",
"url": "https://roadmap.sh/javascript",
"url": "/javascript",
"type": "article"
},
{
@@ -201,7 +201,7 @@
"links": [
{
"title": "Visit Dedicated Java Roadmap",
"url": "https://roadmap.sh/java",
"url": "/java",
"type": "article"
},
{
@@ -237,7 +237,7 @@
},
{
"title": "Visit Dedicated Python Roadmap",
"url": "https://roadmap.sh/python",
"url": "/python",
"type": "article"
},
{
@@ -330,7 +330,7 @@
"links": [
{
"title": "Learn Git & GitHub",
"url": "https://roadmap.sh/git-github",
"url": "/git-github",
"type": "article"
},
{
@@ -366,7 +366,7 @@
"links": [
{
"title": "Learn Git & GitHub",
"url": "https://roadmap.sh/git-github",
"url": "/git-github",
"type": "article"
},
{
@@ -392,7 +392,7 @@
"links": [
{
"title": "Learn Git & GitHub",
"url": "https://roadmap.sh/git-github",
"url": "/git-github",
"type": "article"
},
{
@@ -506,7 +506,7 @@
"links": [
{
"title": "Visit Dedicated PostgreSQL DBA Roadmap",
"url": "https://roadmap.sh/postgresql-dba",
"url": "/postgresql-dba",
"type": "article"
},
{
@@ -542,7 +542,7 @@
"links": [
{
"title": "SQL Roadmap",
"url": "https://roadmap.sh/sql",
"url": "/sql",
"type": "article"
},
{
@@ -1105,7 +1105,7 @@
"links": [
{
"title": "GraphQL Roadmap",
"url": "https://roadmap.sh/graphql",
"url": "/graphql",
"type": "article"
},
{
@@ -1967,7 +1967,7 @@
"links": [
{
"title": "Visit Dedicated MongoDB Roadmap",
"url": "https://roadmap.sh/mongodb",
"url": "/mongodb",
"type": "article"
},
{
@@ -2907,19 +2907,8 @@
},
"5XGvep2qoti31bsyqNzrU": {
"title": "Real-Time Data",
"description": "Real-time data refers to information that is processed and made available immediately or with minimal delay, allowing users or systems to react promptly to current conditions. This type of data is essential in applications requiring immediate updates and responses, such as financial trading platforms, online gaming, real-time analytics, and monitoring systems. Real-time data processing involves capturing, analyzing, and delivering information as it is generated, often using technologies like stream processing frameworks (e.g., Apache Kafka, Apache Flink) and low-latency databases. Effective real-time data systems can handle high-speed data flows, ensuring timely and accurate decision-making.\n\nLearn more from the following resources:",
"links": [
{
"title": "Real-time data - Wiki",
"url": "https://en.wikipedia.org/wiki/Real-time_data",
"type": "article"
},
{
"title": "What is Real-time Data?",
"url": "https://www.qlik.com/us/streaming-data/real-time-data",
"type": "article"
}
]
"description": "Real-time data refers to information that is processed and made available immediately or with minimal delay, allowing users or systems to react promptly to current conditions. This type of data is essential in applications requiring immediate updates and responses, such as financial trading platforms, online gaming, real-time analytics, and monitoring systems. Real-time data processing involves capturing, analyzing, and delivering information as it is generated, often using technologies like stream processing frameworks (e.g., Apache Kafka, Apache Flink) and low-latency databases. Effective real-time data systems can handle high-speed data flows, ensuring timely and accurate decision-making.",
"links": []
},
"osvajAJlwGI3XnX0fE-kA": {
"title": "Long Polling",

View File

@@ -1953,7 +1953,7 @@
"links": [
{
"title": "Visit Dedicated Python Roadmap",
"url": "https://roadmap.sh/python",
"url": "/python",
"type": "article"
},
{
@@ -1994,7 +1994,7 @@
"links": [
{
"title": "Visit Dedicated Go Roadmap",
"url": "https://roadmap.sh/golang",
"url": "/golang",
"type": "article"
},
{
@@ -2056,7 +2056,7 @@
"links": [
{
"title": "Visit Dedicated React Roadmap",
"url": "https://roadmap.sh/react",
"url": "/react",
"type": "article"
},
{
@@ -2097,7 +2097,7 @@
"links": [
{
"title": "Visit Dedicated Angular Roadmap",
"url": "https://roadmap.sh/angular",
"url": "/angular",
"type": "article"
},
{
@@ -2118,7 +2118,7 @@
"links": [
{
"title": "Visit Dedicated Vue Roadmap",
"url": "https://roadmap.sh/vue",
"url": "/vue",
"type": "article"
},
{

View File

@@ -51,7 +51,7 @@
"links": [
{
"title": "Visit Dedicated Python Roadmap",
"url": "https://roadmap.sh/python",
"url": "/python",
"type": "article"
},
{
@@ -107,7 +107,7 @@
"links": [
{
"title": "Visit Dedicated Go Roadmap",
"url": "https://roadmap.sh/golang",
"url": "/golang",
"type": "article"
},
{
@@ -292,7 +292,7 @@
"links": [
{
"title": "Visit Dedicated Java Roadmap",
"url": "https://roadmap.sh/java",
"url": "/java",
"type": "article"
},
{

File diff suppressed because it is too large Load Diff

View File

@@ -84,7 +84,7 @@
]
},
"DFMR-0MbmVCCrJu0I9JWG": {
"title": "Prescriptive Analytics",
"title": "Prespective Analytics",
"description": "Prescriptive analytics, a crucial type of data analytics, is essential for making data-driven decisions in business and organizational contexts. As a data analyst, the goal of prescriptive analytics is to recommend various actions using predictions on the basis of known parameters to help decision makers understand likely outcomes. Prescriptive analytics employs a blend of techniques and tools such as algorithms, machine learning, computational modelling procedures, and decision-tree structures to enable automated decision making. Therefore, prescriptive analytics not only anticipates what will happen and when it will happen, but also explains why it will happen, contributing to the significance of a data analysts role in an organization.\n\nLearn more from the following resources:",
"links": [
{

File diff suppressed because one or more lines are too long

View File

@@ -137,11 +137,6 @@
"title": "What is hosting?",
"description": "Web hosting is an online service that allows you to publish your website files onto the internet. So, anyone who has access to the internet has access to your website.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Web Hosting Explained for Beginners",
"url": "https://www.hostinger.com/tutorials/what-is-web-hosting/",
"type": "article"
},
{
"title": "What Is Web Hosting? Explained",
"url": "https://www.youtube.com/watch?v=htbY9-yggB0",
@@ -547,7 +542,7 @@
"links": [
{
"title": "Visit Dedicated JavaScript Roadmap",
"url": "https://roadmap.sh/javascript",
"url": "/javascript",
"type": "article"
},
{
@@ -977,7 +972,7 @@
"links": [
{
"title": "Visit Dedicated Angular Roadmap",
"url": "https://roadmap.sh/angular",
"url": "/angular",
"type": "article"
},
{
@@ -1003,7 +998,7 @@
"links": [
{
"title": "Visit Dedicated Vue Roadmap",
"url": "https://roadmap.sh/vue",
"url": "/vue",
"type": "article"
},
{
@@ -1044,7 +1039,7 @@
"links": [
{
"title": "Visit Dedicated React Roadmap",
"url": "https://roadmap.sh/react",
"url": "/react",
"type": "article"
},
{
@@ -2068,7 +2063,7 @@
"links": [
{
"title": "Visit Dedicated React Roadmap",
"url": "https://roadmap.sh/react",
"url": "/react",
"type": "article"
},
{
@@ -2550,7 +2545,7 @@
"links": [
{
"title": "Visit Dedicated Flutter Roadmap",
"url": "https://roadmap.sh/flutter",
"url": "/flutter",
"type": "article"
},
{
@@ -2685,7 +2680,7 @@
"links": [
{
"title": "Visit Dedicated Flutter Roadmap",
"url": "https://roadmap.sh/flutter",
"url": "/flutter",
"type": "article"
},
{

View File

@@ -62,7 +62,7 @@
"links": [
{
"title": "Visit Dedicated JavaScript Roadmap",
"url": "https://roadmap.sh/javascript",
"url": "/javascript",
"type": "article"
},
{
@@ -257,7 +257,7 @@
"links": [
{
"title": "Visit Dedicated React Roadmap",
"url": "https://roadmap.sh/react",
"url": "/react",
"type": "article"
},
{
@@ -650,7 +650,7 @@
"links": [
{
"title": "Visit Dedicated PostgreSQL DBA Roadmap",
"url": "https://roadmap.sh/postgresql-dba",
"url": "/postgresql-dba",
"type": "article"
},
{

View File

@@ -1437,22 +1437,12 @@
},
"BKVA6Q7DXemAYjyQOA0nh": {
"title": "git filter-branch",
"description": "You can use `git filter-branch` to rewrite Git revision history by applying custom filters on each revision.\n\n* Filter types: You can modify trees (e.g., removing a file or running a Perl script) or information about each commit.\n* Preserving original data: The command preserves all original commit times, merge information, and other details unless specified otherwise.\n* Rewriting specific branches: Only the positive refs mentioned in the command line are rewritten; if no filters are specified, commits are recommitted without changes.\n\nNotably, there exists a simpler, safer, and more powerful alternative: `git filter-repo`. This tool is actively promoted by Git and offers a streamlined approach to filtering revisions, making it a preferred choice for rewriting your Git history, especially when managing large repositories.\n\nVisit the following resources to learn more:",
"description": "You can use `git filter-branch` to rewrite Git revision history by applying custom filters on each revision.\n\n* Filter types: You can modify trees (e.g., removing a file or running a Perl script) or information about each commit.\n* Preserving original data: The command preserves all original commit times, merge information, and other details unless specified otherwise.\n* Rewriting specific branches: Only the positive refs mentioned in the command line are rewritten; if no filters are specified, commits are recommitted without changes.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "git filter-branch",
"url": "https://git-scm.com/docs/git-filter-branch",
"type": "article"
},
{
"title": "git filter-repo",
"url": "https://github.com/newren/git-filter-repo",
"type": "article"
},
{
"title": "Removing sensitive data from a repository",
"url": "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository",
"type": "article"
}
]
},
@@ -2219,7 +2209,7 @@
},
"qrdOARfqGPF9xhF6snbAn": {
"title": "OAuth Apps",
"description": "GitHub OAuth Apps allow developers to integrate with GitHub using OAuth 2.0 authentication. They enable secure, token-based access to specific GitHub resources like repositories, issues, and pull requests. OAuth Apps can automate tasks, personalize interactions, and provide real-time notifications through webhooks, all while allowing users to approve only the necessary permissions without sharing their credentials.\n\nVisit the following resources to learn more:",
"description": "GitHub OAuth Apps are a way to integrate with the GitHub platform using OAuth authentication. They allow developers to create custom integrations that can automate tasks, provide real-time notifications, and build custom workflows.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Creating an OAuth app",

View File

@@ -965,11 +965,6 @@
"title": "SwiftUI",
"description": "SwiftUI is Apple's modern declarative framework for building user interfaces across all Apple platforms. Introduced in 2019, it allows developers to create UIs using Swift code, describing the desired layout and behavior rather than implementing them imperatively. SwiftUI offers a more concise and intuitive approach to UI development, with features like automatic support for Dark Mode, dynamic type, and localization. It uses a state-driven approach, automatically updating the UI when underlying data changes. While newer than UIKit, SwiftUI is rapidly evolving and gaining adoption, offering seamless integration with UIKit when needed.\n\nLearn more from the following resources:",
"links": [
{
"title": "HackingWithSwift - 100 Days of SwiftUI",
"url": "https://www.hackingwithswift.com/100/swiftui",
"type": "course"
},
{
"title": "SwiftUI Documentation",
"url": "https://developer.apple.com/xcode/swiftui/",

View File

@@ -503,7 +503,7 @@
},
"R6ICrk6vjoBxx5nRGo4Jg": {
"title": "Symbol",
"description": "Symbols are a unique and immutable primitive data type in JavaScript, introduced in ECMAScript 6 (ES6). They are often used to create unique property keys for objects, ensuring no property key collisions occur. Each Symbol value is distinct, even when multiple are created with the same description. Symbols can be created using the Symbol() function, and their primary use case is to add hidden or special properties to objects that wont interfere with other properties or methods.\n\nLearn more from the following resources:",
"description": "Symbols are a unique and immutable primitive data type in JavaScript, introduced in ECMAScript 6 (ES6). They are often used to create unique property keys for objects, ensuring that no property key collisions occur. Each Symbol value is unique, even if created with the same description. Symbols can be created using the Symbol() function, and their primary use case is to add hidden or special properties to objects that wont interfere with other properties or methods.\n\nLearn more from the following resources:",
"links": [
{
"title": "Symbol data type in JavaScript",
@@ -514,16 +514,6 @@
"title": "Symbol type",
"url": "https://javascript.info/symbol",
"type": "article"
},
{
"title": "Symbol",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol",
"type": "article"
},
{
"title": "Symbols in Javascript",
"url": "https://www.youtube.com/watch?v=E5Bblr-SFbA",
"type": "video"
}
]
},

View File

@@ -36,7 +36,7 @@
"links": [
{
"title": "Visit Dedicated Go Roadmap",
"url": "https://roadmap.sh/golang",
"url": "/golang",
"type": "article"
},
{
@@ -393,7 +393,7 @@
},
"o6GQ3-8DgDtHzdX6yeg1w": {
"title": "Flink",
"description": "Apache Flink is an open-source stream processing framework designed for real-time and batch data processing with low latency and high throughput. It supports event time processing, fault tolerance, and stateful operations, making it ideal for applications like real-time analytics, fraud detection, and event-driven systems. Flink is highly scalable, integrates with various data systems, and is widely used in industries for large-scale, real-time data processing tasks.\n\nVisit the following resources to learn more:",
"description": "Apache Flink is a distributed stream processing framework that is used to process large amounts of data in real-time. It is designed to be highly scalable and fault-tolerant. Flink is built on top of the Apache Kafka messaging system and is used to process data streams in real-time.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Apache Flink Documentation",
@@ -404,11 +404,6 @@
"title": "Explore top posts about Apache Flink",
"url": "https://app.daily.dev/tags/apache-flink?ref=roadmapsh",
"type": "article"
},
{
"title": "Apache Flink Tutorialpoint",
"url": "https://www.tutorialspoint.com/apache_flink/apache_flink_introduction.htm",
"type": "article"
}
]
},

View File

@@ -2085,58 +2085,7 @@
},
"M62lAWBOrTe99TfpFOQ-Y": {
"title": "Common Built-in Modules",
"description": "These are the core modules that come with `Node.js` out of the box. This module provides tools or APIs for performing out certain standard `Node.js` operations. like interacting with the file system, url parsing, or logging information to the console.\n\nLearn more from the following resources:",
"links": [
{
"title": "Nodejs fs module",
"url": "https://nodejs.org/api/fs.html",
"type": "article"
},
{
"title": "Nodejs url module",
"url": "https://nodejs.org/api/url.html",
"type": "article"
},
{
"title": "Nodejs console module",
"url": "https://nodejs.org/api/console.html",
"type": "article"
},
{
"title": "Nodejs util module",
"url": "https://nodejs.org/api/util.html",
"type": "article"
},
{
"title": "Nodejs events module",
"url": "https://nodejs.org/api/events.html",
"type": "article"
},
{
"title": "Nodejs os module",
"url": "https://nodejs.org/api/os.html",
"type": "article"
},
{
"title": "Nodejs worker threads module",
"url": "https://nodejs.org/api/worker_threads.html",
"type": "article"
},
{
"title": "Nodejs child process module",
"url": "https://nodejs.org/api/child_process.html",
"type": "article"
},
{
"title": "Nodejs process object",
"url": "https://nodejs.org/api/process.html",
"type": "article"
},
{
"title": "Nodejs crypto module",
"url": "https://nodejs.org/api/crypto.html",
"type": "article"
}
]
"description": "These are the common modules that come with `Node.js` out of the box. This module provides tools or APIs for performing out certain standard `Node.js` operations. like interacting with the file system, url parsing, or logging information to the console.",
"links": []
}
}

View File

@@ -550,7 +550,7 @@
"description": "PostgreSQL offers a comprehensive set of data types to cater to diverse data needs, including numeric types like `INTEGER`, `FLOAT`, and `SERIAL` for auto-incrementing fields; character types such as `VARCHAR` and `TEXT` for variable-length text; and temporal types like `DATE`, `TIME`, and `TIMESTAMP` for handling date and time data. Additionally, PostgreSQL supports `BOOLEAN` for true/false values, `ENUM` for enumerated lists, and composite types for complex structures. It also excels with `JSON` and `JSONB` for storing and querying semi-structured data, arrays for storing multiple values in a single field, and geometric types for spatial data. These data types ensure flexibility and robust data management for various applications.\n\nLearn more from the following resources:",
"links": [
{
"title": "PostgreSQL® Data Types: Mappings to SQL, JDBC, and Java Data Types",
"title": "",
"url": "https://www.instaclustr.com/blog/postgresql-data-types-mappings-to-sql-jdbc-and-java-data-types/",
"type": "article"
},
@@ -866,7 +866,7 @@
"type": "article"
},
{
"title": "Query Planning",
"title": "Query Planning@",
"url": "https://www.postgresql.org/docs/current/runtime-config-query.html",
"type": "article"
}
@@ -2091,16 +2091,6 @@
"url": "https://www.postgresql.org/docs/8.1/triggers.html",
"type": "article"
},
{
"title": "PostgreSQL Triggers",
"url": "https://www.postgresqltutorial.com/postgresql-triggers/",
"type": "article"
},
{
"title": "Understanding PostgreSQL Triggers",
"url": "https://hevodata.com/learn/postgresql-triggers/",
"type": "article"
},
{
"title": "Using PostgreSQL triggers to automate processes with Supabase",
"url": "https://www.youtube.com/watch?v=0N6M5BBe9AE",

View File

@@ -1120,7 +1120,7 @@
]
},
"_IXXTSwQOgYzYIUuKVWNE": {
"title": "virtualenv",
"title": "virutalenv",
"description": "`virtualenv` is a tool to create isolated Python environments. It creates a folder which contains all the necessary executables to use the packages that a Python project would need.\n\nLearn more about `virtualenv` by visiting the following resources:",
"links": [
{

View File

@@ -1,812 +0,0 @@
{
"-3pADOHMDQ0H6ZKNjURyn": {
"title": "What is Redis?",
"description": "",
"links": []
},
"M-EXrTDeAEMz_IkEi-ab4": {
"title": "In-memory Data Structure Store",
"description": "",
"links": []
},
"l2aXyO3STnhbFjvUXPpm2": {
"title": "Key-value Database",
"description": "",
"links": []
},
"eHuBz_zSZK3rubn7nkd7g": {
"title": "Cache",
"description": "",
"links": []
},
"mgGJTBU8ofvOzl9gYWhnG": {
"title": "Message Broker",
"description": "",
"links": []
},
"-TjnSOY8txYrhhxRV1OIl": {
"title": "Caching",
"description": "",
"links": []
},
"bVJASI7bfSYqYnNhX83ng": {
"title": "Real-time Analytics",
"description": "",
"links": []
},
"URxGmhZHr0Y8nyrYj0gJl": {
"title": "Session Management",
"description": "",
"links": []
},
"ZCyId3aIoLv3duxoJdk2P": {
"title": "Pub/Sub Messaging",
"description": "",
"links": []
},
"Fv1iGX22sApIEifM2IpJz": {
"title": "Leaderboards and Counters",
"description": "",
"links": []
},
"8uRpPJ0iD4XnQPKruQc8P": {
"title": "Data Persistence Options",
"description": "",
"links": []
},
"uVewcyaFi1Pt2Gs0KrkfA": {
"title": "Rich Data Structures",
"description": "",
"links": []
},
"5-3pd4rLDqRzMzSRVLdXh": {
"title": "High Performance and Scalability",
"description": "",
"links": []
},
"EvWiEx_AoxAht6sKxzW2l": {
"title": "Redis vs SQL/NoSQL DBs",
"description": "",
"links": []
},
"1Af5H0BgdAsRdBCNdHz5v": {
"title": "When to choose Redis?",
"description": "",
"links": []
},
"Bf_kLfmy7_uflqC9N0-jt": {
"title": "Using Package Managers",
"description": "",
"links": []
},
"yBZ79s6mzGdj5AnX2H_Hy": {
"title": "Pre-compiled Binaries",
"description": "",
"links": []
},
"TDxv0q7jlZ26uZYYlneem": {
"title": "Using Docker",
"description": "",
"links": []
},
"43LvShQhmoWQ8Nye7fLkz": {
"title": "Starting the Server",
"description": "",
"links": []
},
"BOGXTjmCLo6WI6mYDsqRu": {
"title": "Connecting using Redis CLI",
"description": "",
"links": []
},
"NhcZM4nUQoSBBf_1qXi6l": {
"title": "Basic Commands / SET, GET",
"description": "",
"links": []
},
"DOdNkTY1yIMipWA2CD9xH": {
"title": "Settings and Getting Keys",
"description": "",
"links": []
},
"lV_MnUNTB2h925idX0YWk": {
"title": "DEL",
"description": "",
"links": []
},
"U84XgBFPyIbY0W5afH4cx": {
"title": "Overview of Data Types",
"description": "",
"links": []
},
"ltF4vCT9ZA2XuUuHnuGnN": {
"title": "SET",
"description": "",
"links": []
},
"mQc4H2ZMMSVjh33LJY8mK": {
"title": "GET",
"description": "",
"links": []
},
"5K9qyC4mrhXYWOC8WSq8C": {
"title": "INCR",
"description": "",
"links": []
},
"t4BXPofF8OCqH5KHwdYVh": {
"title": "DECR",
"description": "",
"links": []
},
"cPWd53BO6tm-uy4gqLdtZ": {
"title": "APPEND",
"description": "",
"links": []
},
"eJQW986HM4Wf1o1i2FnXs": {
"title": "STRLEN",
"description": "",
"links": []
},
"0v8uLWRCbAqEmKKdYaRQW": {
"title": "More Commands",
"description": "",
"links": []
},
"2_E2VwbjTgk4xxTFWfHuV": {
"title": "Usecases",
"description": "",
"links": []
},
"nS0DHhfy4wxHItgOFhulA": {
"title": "EXPR",
"description": "",
"links": []
},
"Vll7VMmGWSI5XGZ9JpHyl": {
"title": "TTL",
"description": "",
"links": []
},
"Kq7G89KaZZMFkrH-9WZoS": {
"title": "LPUSH",
"description": "",
"links": []
},
"jC8G1o7yFj7D_PGmOIgcD": {
"title": "RPUSH",
"description": "",
"links": []
},
"voa61RTWMJD3Sk8DNJoVQ": {
"title": "LPOP",
"description": "",
"links": []
},
"brUGqWZ287EWtvl9uUbNt": {
"title": "RPOP",
"description": "",
"links": []
},
"8JTtBy6oD2wFYDizVkcVa": {
"title": "LRANGE",
"description": "",
"links": []
},
"hBFEUXtuzUTzWZKp2qWaZ": {
"title": "LINDEX",
"description": "",
"links": []
},
"4oCcP9FxDJSDMHCEVBCNa": {
"title": "LLEN",
"description": "",
"links": []
},
"9KvHcS5F4Jj5ZXgIAdOQY": {
"title": "LMOVE",
"description": "",
"links": []
},
"eBeEUYY-IL_CMkcm31lUL": {
"title": "More Commands",
"description": "",
"links": []
},
"XTwNCCtzXvZMdaex4gZEh": {
"title": "Usecases",
"description": "",
"links": []
},
"Qgkpr9vf9d6-vUg1o8XFj": {
"title": "Sets",
"description": "",
"links": []
},
"xUKoQps69FFQrJeuhD1pz": {
"title": "SADD",
"description": "",
"links": []
},
"mQ0ILns53n1By0Tq6xSZI": {
"title": "SMEMBERS",
"description": "",
"links": []
},
"WQWVL5GT_scHdgfCtI7WT": {
"title": "SREM",
"description": "",
"links": []
},
"Ji5ghlcGJtlmErHFqVf3d": {
"title": "SISMEMBER",
"description": "",
"links": []
},
"5aLfNBewK4Dx017qVNO3T": {
"title": "SINTER",
"description": "",
"links": []
},
"2gZL4a9aWGKWLa89iyHTc": {
"title": "SCARD",
"description": "",
"links": []
},
"6QoYa-N2BKNBaRtvNeVNm": {
"title": "SUNION",
"description": "",
"links": []
},
"JX5ajmcUmkshTO-mLF8lH": {
"title": "SDIFF",
"description": "",
"links": []
},
"2SG4Hr9Tuv6cxmGkrKjYZ": {
"title": "More Commands",
"description": "",
"links": []
},
"3hayYoSZepw7pppBubotg": {
"title": "Usecases",
"description": "",
"links": []
},
"b48EUyFGUeSjtT5fOa_m6": {
"title": "More Commands",
"description": "",
"links": []
},
"Wl23Jh-ASJOQ850yjaTIU": {
"title": "Strings",
"description": "",
"links": []
},
"4-C4XqACUp4nvcMIj6djF": {
"title": "Lists",
"description": "",
"links": []
},
"wY46Qj5Inw_ClBNI9PB_2": {
"title": "Hashes",
"description": "",
"links": []
},
"BOJzn9SWad9oRRdY_ub01": {
"title": "HSET",
"description": "",
"links": []
},
"MsKg9m5jFwHM2Bzjf-vdu": {
"title": "HGET",
"description": "",
"links": []
},
"TpR33sJ-tAjeG3jpGTvYR": {
"title": "HGETALL",
"description": "",
"links": []
},
"E7xFZkqqbzokD5KGTn9zJ": {
"title": "HDEL",
"description": "",
"links": []
},
"adhLMuSmfYMRyWTwIgnyE": {
"title": "HEXISTS",
"description": "",
"links": []
},
"jtVnUD-na-WffMaS8qYfu": {
"title": "Usecases",
"description": "",
"links": []
},
"QTbkWZ7BpqYmBhUivccPu": {
"title": "Sorted Sets",
"description": "",
"links": []
},
"0swsBD0sOY-o5lzibT999": {
"title": "ZADD",
"description": "",
"links": []
},
"3pFChX6YIItrBz9lxu4XM": {
"title": "ZRANGE",
"description": "",
"links": []
},
"OlbixGa5RmdqEt7snY04j": {
"title": "ZRANGEBYSCORE",
"description": "",
"links": []
},
"m0FZDPwNE71zcwM_gUwz0": {
"title": "ZREM",
"description": "",
"links": []
},
"W4v7FIQr2k-Vbm-HdfKog": {
"title": "ZINCRBY",
"description": "",
"links": []
},
"AF_kWM4V8n5Ux06IgEVTl": {
"title": "ZRANK",
"description": "",
"links": []
},
"O-fZM_U-tW0pYtNzN_8Ax": {
"title": "ZCOUNT",
"description": "",
"links": []
},
"P6TDUCroLlEI7qePBFHIH": {
"title": "More Commands",
"description": "",
"links": []
},
"lxevY15ZyP43s_JrEqMX7": {
"title": "Usecases",
"description": "",
"links": []
},
"o6e_CwxfPoU6qkfWkwKwj": {
"title": "More Commands",
"description": "",
"links": []
},
"jCaVx5wvsvuyqtwh6m8si": {
"title": "Naming Conventions",
"description": "",
"links": []
},
"UlQHqw1dbxZnAKbsWsOgU": {
"title": "Retrieval by Pattern",
"description": "",
"links": []
},
"OSIYDYPGz8Vgo9SU9GGH9": {
"title": "Expiration",
"description": "",
"links": []
},
"jrgaoDnt_RxTu79hk4hCD": {
"title": "Atomicity in Redis",
"description": "",
"links": []
},
"LHlwjN3WHYUBUafzzwsWQ": {
"title": "Pipelining",
"description": "",
"links": []
},
"7JzeyTrkZ_1_yxMVrqvZU": {
"title": "Batch Operations",
"description": "",
"links": []
},
"0Q3AkE8leWAyYsww3-BHX": {
"title": "Bitmaps",
"description": "",
"links": []
},
"B-YUFhPQNdr1KZNupmR5N": {
"title": "SETBIT",
"description": "",
"links": []
},
"0HFLJfcrcSnAVTecG3P8W": {
"title": "GETBIT",
"description": "",
"links": []
},
"jpcyXSSib7q4WBPmpgnXA": {
"title": "BITCOUNT",
"description": "",
"links": []
},
"tkrxArg_oYH0aQfM8NkD2": {
"title": "BITOP",
"description": "",
"links": []
},
"Df1Eu7CuA-ARYii9JVvnm": {
"title": "BITPOS",
"description": "",
"links": []
},
"s7PEr-5TAm5EGJm0RSjPJ": {
"title": "Usecases",
"description": "",
"links": []
},
"cszjT3YK8oyhGpqLTQzwX": {
"title": "HyperLogLog",
"description": "",
"links": []
},
"8a4DmPZrX2xGZ7zdWxS63": {
"title": "PFADD",
"description": "",
"links": []
},
"JWT30KIJQHVw0MXI5sGR6": {
"title": "PFCOUNT",
"description": "",
"links": []
},
"s50jr_XOUcxh65-tGCKf5": {
"title": "PFMERGE",
"description": "",
"links": []
},
"XPeCvikPuu6EJ8UcOLGPh": {
"title": "Usecases",
"description": "",
"links": []
},
"zXs_9n2yEb_eVi0WuOQKH": {
"title": "Streams",
"description": "",
"links": []
},
"7isWhgrUA6M5IGM2U2tm4": {
"title": "XADD",
"description": "",
"links": []
},
"4sKiAtX5aIL4NDsQkilNC": {
"title": "XREAD",
"description": "",
"links": []
},
"CiYFuYE8XudZkR6AW2NQ7": {
"title": "XRANGE",
"description": "",
"links": []
},
"DQJCMEw13lELcw_AwLfrT": {
"title": "XLEN",
"description": "",
"links": []
},
"zXlSBfa-Gi9_GhSYEzre3": {
"title": "Usecases",
"description": "",
"links": []
},
"4-z4hDKm86qQatYnmE21R": {
"title": "More Commands",
"description": "",
"links": []
},
"_NiUdVQ85qnvryI38k_vQ": {
"title": "Geospatial Indexes",
"description": "",
"links": []
},
"U3N1EgHFs1-YUaB_VrJfw": {
"title": "GEOADD",
"description": "",
"links": []
},
"OWWDLuGTbdNwME7v2jxVP": {
"title": "GEOSEARCH",
"description": "",
"links": []
},
"GNMjrLPkpTphneoQ0GoZF": {
"title": "Usecases",
"description": "",
"links": []
},
"FCbdKnkI1ZHGekT6yiGua": {
"title": "More Commands",
"description": "",
"links": []
},
"9W_jaK1DSEZHRKdPcUM7h": {
"title": "Pub/Sub",
"description": "",
"links": []
},
"55BCntuWlaQiLPqNtb-2i": {
"title": "SUBSCRIBE",
"description": "",
"links": []
},
"5gkZzm2F4vu6IxUoJLYbK": {
"title": "UNSUBSCRIBE",
"description": "",
"links": []
},
"gIPo-2CNqE1BsOaDzmkCU": {
"title": "PUBLISH",
"description": "",
"links": []
},
"V-d6q-3Sf0dl5v8xiCQwl": {
"title": "More Commands",
"description": "",
"links": []
},
"MvyE_JUJej0UB9xe8Anfj": {
"title": "Usecases",
"description": "",
"links": []
},
"YHbWlKrQqptUDbaQVy0_A": {
"title": "Transactions",
"description": "",
"links": []
},
"c-y5Eck8VtSyIf8RAW9p7": {
"title": "WATCH",
"description": "",
"links": []
},
"Ljy-Mc0EBBX4_vXfYZ5-4": {
"title": "EXEC",
"description": "",
"links": []
},
"U6hST1MkS16T2CHV3-Ise": {
"title": "MULTI",
"description": "",
"links": []
},
"msW0Wd2H-6FFNDnjC64t-": {
"title": "Optimistic Locking",
"description": "",
"links": []
},
"Veb30QrPYNjUn13dtGbUr": {
"title": "Lua Scripting",
"description": "",
"links": []
},
"rjeq3i9oX8IGyQzo--L3c": {
"title": "EVAL",
"description": "",
"links": []
},
"3X0x_PcJGWBVPL-LSVAln": {
"title": "EVALSHA",
"description": "",
"links": []
},
"kF_nGo845XDwLkwcQt008": {
"title": "Usecases",
"description": "",
"links": []
},
"bQaek7f3dAaZfSUhwovm1": {
"title": "Persistence Options",
"description": "",
"links": []
},
"D3pZdAjwPFMRxX1-iyu5-": {
"title": "How RDB Works?",
"description": "",
"links": []
},
"_pb2DPrFUUZabKxWsuFUo": {
"title": "Configuring Save Interval",
"description": "",
"links": []
},
"Z6yJwUkcDX08HoMyf1LwX": {
"title": "Usecases / Best Practices",
"description": "",
"links": []
},
"PTj6oxvpw8vP295WvAI80": {
"title": "How AOF Works?",
"description": "",
"links": []
},
"ibaZ34-laQtUyxAsERi7o": {
"title": "AOF rewrite & compaction",
"description": "",
"links": []
},
"9ToKTUqbi-NV5Wcwb21PT": {
"title": "Truncation / Corruption",
"description": "",
"links": []
},
"VvOQUO22ZF8VvDSqHENNU": {
"title": "Usecases",
"description": "",
"links": []
},
"vzp7DUpjklzIA0E9WxJQA": {
"title": "Usecases / Best Practices",
"description": "",
"links": []
},
"80035BzcB-fKCvD_3N8zE": {
"title": "No Persistence Option",
"description": "",
"links": []
},
"3S-qqOlfr60HR4VvDr4He": {
"title": "RDB vs AOF Tradeoffs",
"description": "",
"links": []
},
"S5Y26m1oHCQpB-oLCdtac": {
"title": "Hybrid Persistence",
"description": "",
"links": []
},
"rSD8nJ-uNpHJVe5Hn66h7": {
"title": "Replication Basics",
"description": "",
"links": []
},
"nci3OB1NE1zJHUPfZCOpT": {
"title": "Redis Sentinel",
"description": "",
"links": []
},
"AQiCcHS6dBAAAPloxiXub": {
"title": "Clustering",
"description": "",
"links": []
},
"Qy42paiTUsO8HIwbWTMui": {
"title": "Authentication",
"description": "",
"links": []
},
"wsuKH7YwGDV6GYQbdhA4o": {
"title": "Network Security",
"description": "",
"links": []
},
"gdiWwTQg6A-BFHdQBmgmH": {
"title": "SSL/TLS Encryption",
"description": "",
"links": []
},
"q2Jw49QUWCUGIfcEC1bZI": {
"title": "INFO",
"description": "",
"links": []
},
"jBtEiylcedtaE6E20Uk4V": {
"title": "MONITOR",
"description": "",
"links": []
},
"XBPwHgIsXupMsyoOFkJZ0": {
"title": "RedisInsight",
"description": "",
"links": []
},
"y5FPSAi6T-5X9SUfR58_-": {
"title": "RedisCommander",
"description": "",
"links": []
},
"xF0wQYmtwXYkrzvWg5gOO": {
"title": "Memory Management",
"description": "",
"links": []
},
"Sd1ENOXSFCz1YqccXjr2A": {
"title": "Max Memory Policy",
"description": "",
"links": []
},
"yaCWw2KjX58SaPajUAb0d": {
"title": "Slow Log Analysis",
"description": "",
"links": []
},
"kgHwK4N-sfh6dHjd_D_me": {
"title": "redis-benchmark",
"description": "",
"links": []
},
"2p5RF4lVYfRvYTo1Ofm-a": {
"title": "Monitoring",
"description": "",
"links": []
},
"hLIT00Iz7rV56ZBIUhWYn": {
"title": "Redis Modules",
"description": "",
"links": []
},
"jicsfYw56VrbRUt7M8c85": {
"title": "RedisJSON",
"description": "",
"links": []
},
"_GdTXcJO8uJlhPdfrmeXG": {
"title": "Search",
"description": "",
"links": []
},
"RBr8opWSh2TKXC8Fmdg0j": {
"title": "RedisTimeSeries",
"description": "",
"links": []
},
"GwVL5CvbnHsiWb1hVh7lK": {
"title": "RedisBloom",
"description": "",
"links": []
},
"giyKPtQ-pziA064P8OQD-": {
"title": "redis.conf",
"description": "",
"links": []
},
"wXRDsNGFckXV_CSiit5sN": {
"title": "Backup and Recovery",
"description": "",
"links": []
},
"Cb-KazR4PuR86VX5oT0zi": {
"title": "Upgrading Redis",
"description": "",
"links": []
},
"nUIfTkgm3PlSiqgun1BS7": {
"title": "Disaster Recovery",
"description": "",
"links": []
},
"8lyXDuZJ-KHl4v2_8Ew1h": {
"title": "Redis Enterprise",
"description": "",
"links": []
},
"cybF72wlJyJbHLUjitLvn": {
"title": "Active-Active geo Distribution",
"description": "",
"links": []
},
"sWOFnbh2EyaHRzquz1UeF": {
"title": "Redis on Flash",
"description": "",
"links": []
},
"ujs77bV8g8-FOm5hBtZFd": {
"title": "Security and Compliance",
"description": "",
"links": []
},
"JlLwy69eQ1bPHAOOJNqjo": {
"title": "When to consider enterprise?",
"description": "",
"links": []
}
}

View File

@@ -148,7 +148,7 @@
"links": [
{
"title": "Visit Dedicated Java Roadmap",
"url": "https://roadmap.sh/java",
"url": "/java",
"type": "article"
},
{
@@ -184,7 +184,7 @@
"links": [
{
"title": "Visit Dedicated Python Roadmap",
"url": "https://roadmap.sh/python",
"url": "/python",
"type": "article"
},
{
@@ -246,7 +246,7 @@
"links": [
{
"title": "Visit Dedicated Go Roadmap",
"url": "https://roadmap.sh/golang",
"url": "/golang",
"type": "article"
},
{
@@ -282,7 +282,7 @@
"links": [
{
"title": "Visit Dedicated JavaScript Roadmap",
"url": "https://roadmap.sh/javascript",
"url": "/javascript",
"type": "article"
},
{
@@ -1177,7 +1177,7 @@
"links": [
{
"title": "Visit Dedicated React Roadmap",
"url": "https://roadmap.sh/react",
"url": "/react",
"type": "article"
},
{

View File

@@ -1258,7 +1258,7 @@
"links": [
{
"title": "SQL Server Indexes",
"url": "https://www.sqlservercentral.com/articles/introduction-to-indexes",
"url": "https://www.sqlservercentral.com/articles/sql-server-indexes",
"type": "article"
},
{

View File

@@ -64,7 +64,7 @@
},
"kcG4IpneJzA6di0uqTiwb": {
"title": "CREATE Action Funnel",
"description": "Stephen Wendel's CREATE Action Funnel is a behavioral design framework aimed at helping individuals or organizations encourage specific behaviors in others, especially in the context of product design. It breaks down the process of motivating action into six key stages. Each stage helps identify where users might drop off or face barriers, allowing designers or strategists to address these pain points effectively. These stages are:\n\n* **CUE:** The user must notice a cue or prompt that tells them to act. This could be a notification, a visual element, or any kind of reminder.\n \n* **REACTION:** The user must react positively to the cue. This stage involves emotional and cognitive processing, where the individual decides if the action is relevant or attractive.\n \n* **EVALUATION:** The user evaluates whether the action is worth their time, energy, or resources. They assess the benefits versus the effort required.\n \n* **ABILITY:** The user must feel capable of taking the action. This involves ensuring that the action is easy enough to do and aligns with their skills and resources.\n \n* **TIMING:** The action needs to happen at the right time. Users need to have the opportunity and be in the right context to act.\n \n* **EXECUTION:** Finally, the action must be carried out successfully. This is the stage where the behavior is completed.\n \n\nThe CREATE Action Funnel is helpful for product designers, marketers, or behavior change professionals, as it provides a structured way to understand user actions and design interventions to improve completion rates. It identifies and solves the gaps that occur between intention and action.",
"description": "Stephen Wendell's Create Action Funnel is a UX design framework focused on converting website visitors into active customers through a systematic and engaging process. The approach emphasizes on understanding user behavior, catering to their needs, and directing them towards specific actions. The Action Funnel consists of four major steps:\n\n* **Establish the Objectives:** Before diving into the design, clearly define the goals you want to achieve through the website or app. Determine what actions you want the users to take (e.g., sign up, make a purchase, share content) and what constitutes a successful conversion.\n \n* **Understand User Mindsets:** Identify your target audience and recognize their needs, preferences, emotions, and pain points. Accomplishing this requires user research, creating personas, storyboarding, and empathy mapping, among other methods.\n \n* **Design the Optimal User Flow:** Craft a seamless and intuitive user journey by designing a clear path from the landing page to the desired action. Prioritize simplicity, usability, and efficiency. Make sure to include meaningful touchpoints and interactions to engage the users and make it easy for them to complete the intended action.\n \n* **Refine and Test the Experience:** Use wireframes and prototypes to test and iteratively refine the user experience. Employ user testing, A/B testing, and analytics to gather insight on user behavior, preferences, and engagement. Continuously use feedback to make improvements, ensuring that the design effectively leads visitors down the action funnel.\n \n\nBy implementing Stephen Wendell's `Create Action Funnel`, you can effectively guide users through an engaging journey that motivates them to become active customers, ultimately increasing conversion rates and overall satisfaction.",
"links": [
{
"title": "Behavioral Science Crash Course: Steve Wendel's CREATE Action Funnel",
@@ -75,7 +75,7 @@
},
"0Df110GZcDw5wbAe1eKoA": {
"title": "Spectrum of Thinking Interventions",
"description": "The _Spectrum of Thinking Interventions_ provides a structure for understanding the different types of decision-making processes by illustrating how our minds would respond in a _default, lowest energy way_, if we didn't consciously do something different. This spectrum ranges from situations requiring minimal thought to those demanding intensive thinking, and includes the mechanisms (\"interventions\") that our minds will likely use.\n\n* **Habits:** Triggering a learned routine based on familiar cues\n* **Other intuitive responses:** Used in familiar or semi-familiar situations, with responses based on past experiences\n* **Active mindset or self-concept:** Used in ambiguous scenarios with multiple possible interpretations\n* **Heuristics:** Used in situations requiring conscious attention, but where decisions can be made more easily\n* **Focused, conscious calculation:** Used in unfamiliar scenarios or crucial decisions where deliberate focus is needed\n\nWith this spectrum in mind, it is essential as a UX designer to leverage on the mind's decision-making process, analyze which mechanisms are most applicable to your target users and design the most accessible and effective solutions.",
"description": "The _Spectrum of Thinking Interventions_ provides a structure to guide your UX design process, helping you identify the types and range of thinking interventions that the user may require. This spectrum encompasses four primary categories: guidance, explanation, exploration, and creation.\n\nGuidance\n--------\n\nGuidance-based interventions are designed to help users navigate through a digital product or service with minimal effort. They may be aimed at full-fledged beginners, casual users, or experts in their respective domains. Such interventions may include signposts, tooltips, and clearly articulated labels.\n\n_Examples:_\n\n* Visual cues (e.g., icons, colors)\n* Signposting (e.g., breadcrumbs)\n* In-context information (e.g., tool tips, hints)\n\nExplanation\n-----------\n\nExplanation-based interventions provide users with detailed narratives, overviews, or background information that helps them make informed decisions. This may include tutorials, articles, videos, or any other mediums that help explain complex concepts or instructions.\n\n_Examples:_\n\n* Multimedia tutorials\n* Articles or blog posts\n* Infographics or diagrams\n\nExploration\n-----------\n\nExploration-based interventions encourage users to understand and interact with the product by investigating, asking questions, or searching for solutions on their own. This can be done by providing interactive elements, multiple pathways, and opportunities for discovery.\n\n_Examples:_\n\n* Interactive simulations or models\n* Advanced search capabilities\n* Multiple UI paths for task completion\n\nCreation\n--------\n\nCreation-based interventions engage users by offering them the tools and resources to co-create or customize their experience. This type of intervention often involves a more extensive level of input and involvement from the user as they become active participants in the design process.\n\n_Examples:_\n\n* Customizable user interfaces\n* Allowing users to create their content\n* Enabling users to manage their preferences, settings, and configurations\n\nWith this spectrum in mind, it is essential as a UX designer to analyze which types of thinking interventions are most relevant to your target users and design the most accessible and effective solutions. Always consider how these interventions will influence users' decision-making processes and their overall satisfaction with your digital product or service.",
"links": []
},
"kWA8CvocP1pkom2N7O4gb": {

View File

@@ -242,25 +242,13 @@
},
"NCIzs3jbQTv1xXhAaGfZN": {
"title": "v-text",
"description": "The `v-text` directive is used to set the textContent property of an element. It's important to note that when using this directive it will overwrite the HTML content inside the element. The expected input is a string, so it's important to wrap any text in single quotes.\n\nExample:\n\n <template>\n <p v-text=\"'I am some text'\"></p>\n </template>\n \n\nVisit the following resources to learn more:",
"links": [
{
"title": "v-text documentation",
"url": "https://vuejs.org/api/built-in-directives.html#v-text",
"type": "article"
}
]
"description": "",
"links": []
},
"bZxtIBeIfeUcR32LZWrPW": {
"title": "v-html",
"description": "The `v-thml` directive is similar to the `v-text` directive, but the difference is that `v-html` renders its content as HTML. This means that if you pass an HTML element it will be rendered as an element and not plain text. Since the content is render as HTMl, it can pose a security risk if the content contains malicius JavaScript code. For this reason you should never use this directive in combination with user input, unless the input is first properly sanitized.\n\nExample:\n\n <template>\n <p v-html=\"'<h1>Text</h1>'\"></p>\n </template>\n \n\nVisit the following resources to learn more:",
"links": [
{
"title": "v-html documentation",
"url": "https://vuejs.org/api/built-in-directives.html#v-html",
"type": "article"
}
]
"description": "",
"links": []
},
"_TlbGTKFCMO0wdLbC6xHX": {
"title": "v-show",
@@ -297,25 +285,13 @@
},
"a9caVhderJaVo0v14w8WB": {
"title": "v-else-if",
"description": "This directive is used to add additional conditions to a v-if and v-else block.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "v-else-if Documentation",
"url": "https://vuejs.org/api/built-in-directives.html#v-else-if",
"type": "article"
}
]
"description": "",
"links": []
},
"3ftwRjQ9e1-qDT9BV53zr": {
"title": "v-for",
"description": "The `v-for` directive is used to render an HTML element, a block of elements, or even a component based on an array, an object, or a set number of times. When using this directive it is important to assign a unique key to each item to avoid issues and improve perfomance. This directive follows the `item in items` syntax.\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const foods = ref([\n {id: 1, name: \"apple\"},\n {id: 2, name: \"pear\"},\n {id: 3, name: \"pizza\"}\n ]);\n </script>\n \n <template>\n <p v-for=\"food in foods\" :key=\"food.id\">{{ food.name }}</p>\n </template>\n \n\nVisit the following resources to learn more:",
"links": [
{
"title": "v-for documentation",
"url": "https://vuejs.org/guide/essentials/list#v-for",
"type": "article"
}
]
"description": "",
"links": []
},
"hVuRmhXVP65IPtuHTORjJ": {
"title": "v-on",
@@ -324,18 +300,12 @@
},
"cuM9q9vYy8JpZPGeBffd1": {
"title": "v-bind",
"description": "The `v-bind` directive dynamically binds an HTML attribute to data.\n\nThe shorthand for this directive is `:`\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const image_url = ref(\"path/to/image.png\")\n </script>\n \n <template>\n <img :src=\"image_url\" />\n </template>\n \n\nVisit the following resources for more information:",
"links": [
{
"title": "v-bind documentation",
"url": "https://vuejs.org/api/built-in-directives.html#v-bind",
"type": "article"
}
]
"description": "",
"links": []
},
"cxu2Wbt306SxM4JKQQqnL": {
"title": "v-model",
"description": "The v-model directive in Vue.js is used for creating two-way data bindings on form input elements, such as `<input>`, `<textarea>`, and `<select>`. This means that the data can be updated in the component when the user inputs something, and the UI will update if the data in the component changes.",
"description": "The v-model directive in Vue.js is used for creating two-way data bindings on form input elements, such as , , and . This means that the data can be updated in the component when the user inputs something, and the UI will update if the data in the component changes.",
"links": [
{
"title": "Form Input Bindings",
@@ -346,58 +316,28 @@
},
"m9pQ3daR3KiwRATcQysHA": {
"title": "v-slot",
"description": "The v-slot directive to define slots in components, allowing you to pass and render content dynamically inside a component.\n\nFor named slots, you use v-slot with a specific slot name. This lets you pass different content to different parts of a component:\n\n <template>\n <custom-component>\n <template v-slot:header>\n <h1>Header Content</h1>\n </template>\n <template v-slot:footer>\n <p>Footer Content</p>\n </template>\n </custom-component>\n </template>\n \n\nThe shorthand for `v-slot` is `#`, for example `v-slot:header` becomes `#header`.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "v-slot documentation",
"url": "https://vuejs.org/api/built-in-directives.html#v-slot",
"type": "article"
}
]
"description": "",
"links": []
},
"5k9CrbzhNy9iiS6ez2UE6": {
"title": "v-once",
"description": "The `v-once` directive makes an HTML element render only once, skipping every future update.\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const input = ref(\"Some Text\");\n </script>\n \n <template>\n <input v-model=\"input\">\n <p v-once>{{ input }}</p>\n </template>\n \n\nIn this example the **p** element will not change its text even if the input variable is changed through the **input** element.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "v-once documentation",
"url": "https://vuejs.org/api/built-in-directives.html#v-once",
"type": "article"
}
]
"description": "",
"links": []
},
"mlsrhioiEkqnRIL6O3hNa": {
"title": "v-pre",
"description": "The `v-pre` directive makes an element render its content as-is, skipping its compilation. The most common use case is when displaying raw mustache syntax.\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const text = ref(\"Some Text\")\n </script>\n \n <template>\n <p v-pre >{{ text }}</p>\n </template>\n \n\nThe **p** element will display: `{{ text }}` and not `Some Text` because the compilation is skipped.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "v-pre Documentation",
"url": "https://vuejs.org/api/built-in-directives.html#v-pre",
"type": "article"
}
]
"description": "",
"links": []
},
"RrSekP8Ub01coegMwLP6a": {
"title": "v-cloak",
"description": "The v-cloak directive is used to prevent the uncompiled Vue template from being visible while the Vue instance is still loading. It temporarily hides the content until Vue has finished compiling the template\n\nThe v-cloak directive remains until the component instance is mounted.\n\n <div v-cloak>\n {{ message }}\n </div>\n \n\nCombined with CSS, you can hide elements with v-cloak until they are ready.\n\n [v-cloak] {\n display: none;\n }\n \n\nThe `<div>` will not be visible until the compilation is done.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "v-cloak documentation",
"url": "https://vuejs.org/api/built-in-directives.html#v-cloak",
"type": "article"
}
]
"description": "",
"links": []
},
"RRPhAxIqvAcjZIcLe_N8-": {
"title": "Optimizing Renders",
"description": "Optimizing rendering is crucial for ensuring a smooth and efficient user experience across all your frontend projects. Sluggish webpages can lead to frustration for users, and potentially cause them to entirely abandon your web application. This issue comes up most often in single-page applications (SPAs), where the entirety of your application is loaded within a single webpage, and updates to it are handled dynamically without needing a full reload of the webpage.\n\nLearn more from the following resources:",
"links": [
{
"title": "Optimizing rendering in Vue",
"url": "https://blog.logrocket.com/optimizing-rendering-vue/",
"type": "article"
}
]
"description": "",
"links": []
},
"dxwKfBxd5KYVkfEPMdHp-": {
"title": "Debugging",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 KiB

View File

@@ -71,7 +71,6 @@ Here is the list of available roadmaps with more being actively worked upon.
- [Design System Roadmap](https://roadmap.sh/design-system)
- [PostgreSQL Roadmap](https://roadmap.sh/postgresql-dba)
- [SQL Roadmap](https://roadmap.sh/sql)
- [Redis Roadmap](https://roadmap.sh/redis)
- [Blockchain Roadmap](https://roadmap.sh/blockchain)
- [ASP.NET Core Roadmap](https://roadmap.sh/aspnet-core)
- [System Design Roadmap](https://roadmap.sh/system-design)

View File

@@ -12,8 +12,8 @@ const ALL_BEST_PRACTICE_DIR = path.join(
process.cwd(),
'/src/data/best-practices',
);
const ALL_GUIDE_DIR = path.join(process.cwd(), '/src/content/guides');
const ALl_AUTHOR_DIR = path.join(process.cwd(), '/src/content/authors');
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(),

View File

@@ -1,39 +0,0 @@
---
import type { ChangelogFileType } from '../../lib/changelog';
import { DateTime } from 'luxon';
interface Props {
changelog: ChangelogFileType;
}
const { changelog } = Astro.props;
const { data: frontmatter } = changelog;
const { Content } = await changelog.render();
const formattedDate = DateTime.fromJSDate(frontmatter.date).toFormat(
'dd LLL, yyyy',
);
---
<div class='relative'>
<span
class='absolute -left-6 top-2 h-2 w-2 flex-shrink-0 rounded-full bg-gray-300'
></span>
<div class='mb-3 flex items-center gap-2'>
<span class='flex-shrink-0 text-xs tracking-wide text-gray-400'>
{formattedDate}
</span>
<span class='truncate text-base font-medium'>
{frontmatter.title}
</span>
</div>
<div class='rounded-xl border bg-white p-6'>
<div
class='prose prose-sm prose-h2:mt-3 prose-h2:text-lg prose-h2:font-medium prose-p:mb-0 prose-blockquote:font-normal prose-blockquote:text-gray-500 prose-ul:my-0 prose-ul:rounded-lg prose-ul:bg-gray-100 prose-ul:px-4 prose-ul:py-4 prose-ul:pl-7 prose-img:mt-0 prose-img:rounded-lg [&>blockquote>p]:mt-0 [&>ul>li]:my-0 [&>ul>li]:mb-1 [&>ul]:mt-3'
>
<Content />
</div>
</div>
</div>

View File

@@ -9,31 +9,28 @@ export function ContentConfirmationModal(props: ContentConfirmationModalProps) {
const { onClose, onClick } = props;
return (
<Modal onClose={onClose} wrapperClassName="max-w-lg">
<Modal onClose={onClose}>
<div className="p-4">
<h2 className="text-lg font-semibold">
Copy Node Details and Resources?
</h2>
<h2 className="text-lg font-semibold">Roadmap Content</h2>
<p className="balanc text-gray-600">
This will just copy the roadmap in your team. Would you like to copy
the resource links and node details as well?
Do you want to copy the content of this roadmap?
</p>
<div className="mt-4 grid grid-cols-2 gap-2">
<button
className="rounded-lg border p-2.5 font-normal"
className="rounded-lg border p-2.5 font-medium"
onClick={() => {
onClick(false);
}}
>
No, copy roadmap only
No
</button>
<button
className="rounded-lg border bg-black p-2.5 font-normal text-white hover:opacity-80"
className="rounded-lg border bg-black p-2.5 font-medium text-white hover:opacity-80"
onClick={() => {
onClick(true);
}}
>
Yes, also copy resources
Yes
</button>
</div>
</div>

View File

@@ -10,7 +10,6 @@ import { showLoginPopup } from '../../lib/popup.ts';
import { isLoggedIn } from '../../lib/jwt.ts';
import { useState } from 'react';
import { CreateRoadmapModal } from './CreateRoadmap/CreateRoadmapModal.tsx';
import { RoadmapAlert } from '../RoadmapAlert.tsx';
export function CustomRoadmapAlert() {
const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false);
@@ -24,18 +23,33 @@ export function CustomRoadmapAlert() {
}}
/>
)}
<div className="relative mb-5 mt-0 rounded-md border border-yellow-500 bg-yellow-100 p-2 sm:-mt-6 sm:mb-7 sm:p-2.5">
<p className="mb-2.5 mt-2 text-sm text-yellow-800 sm:mb-1.5 sm:mt-1 sm:text-base">
This is a custom roadmap made by a community member and is not
verified by <span className="font-semibold">roadmap.sh</span>
</p>
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
<a
href="/roadmaps"
className="inline-flex items-center gap-1.5 text-sm font-semibold text-yellow-700 underline-offset-2 hover:underline"
>
<BadgeCheck className="h-4 w-4 stroke-[2.5]" />
Visit Official Roadmaps
</a>
<span className="hidden font-black text-yellow-700 sm:block">
&middot;
</span>
<a
href="/community"
className="inline-flex items-center gap-1.5 text-sm font-semibold text-yellow-700 underline-offset-2 hover:underline"
>
<HeartHandshake className="h-4 w-4 stroke-[2.5]" />
More Community Roadmaps
</a>
</div>
<RoadmapAlert
title="Community Roadmaps"
description={
<>
This is a custom roadmap made by a community member and is not
verified by <span className="font-semibold">roadmap.sh</span>
</>
}
floatingIcon={MessageCircleHeart}
className="mb-5 mt-0 sm:-mt-6 sm:mb-7"
/>
<MessageCircleHeart className="absolute bottom-2 right-2 hidden h-12 w-12 text-yellow-500 opacity-50 sm:block" />
</div>
</>
);
}

View File

@@ -159,8 +159,7 @@ export function FlowRoadmapRenderer(props: FlowRoadmapRendererProps) {
{hideRenderer && (
<EmptyRoadmap
roadmapId={roadmapId}
// @ts-ignore
canManage={roadmap?.canManage}
canManage={roadmap.canManage}
className="grow"
/>
)}

View File

@@ -4,13 +4,7 @@ import { DashboardCardLink } from './DashboardCardLink';
import { useState } from 'react';
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
import { Simulate } from 'react-dom/test-utils';
import {
ArrowUpRight,
Bot,
BrainCircuit,
Map,
PencilRuler,
} from 'lucide-react';
import { Bot, BrainCircuit, Map, PencilRuler } from 'lucide-react';
type DashboardAiRoadmapsProps = {
roadmaps: {
@@ -26,18 +20,9 @@ export function DashboardAiRoadmaps(props: DashboardAiRoadmapsProps) {
return (
<>
<div className="mb-2 mt-6 flex items-center justify-between gap-2">
<h2 className="text-xs uppercase text-gray-400">
My AI Roadmaps
</h2>
<a
href="/ai/explore"
className="rounded-full bg-gray-200 px-2.5 py-0.5 text-xs font-medium text-gray-700 hover:bg-gray-300 hover:text-black"
>
AI Generated Roadmaps
</a>
</div>
<h2 className="mb-2 mt-6 text-xs uppercase text-gray-400">
AI Generated Roadmaps
</h2>
{!isLoading && roadmaps.length === 0 && (
<DashboardCardLink
@@ -63,7 +48,7 @@ export function DashboardAiRoadmaps(props: DashboardAiRoadmapsProps) {
{roadmaps.map((roadmap) => (
<a
href={`/ai/${roadmap.slug}`}
className="relative truncate rounded-md border bg-white p-2.5 text-left text-sm shadow-sm hover:border-gray-400 hover:bg-gray-50"
className="relative rounded-md border bg-white p-2.5 text-left text-sm shadow-sm truncate hover:border-gray-400 hover:bg-gray-50"
>
{roadmap.title}
</a>
@@ -84,7 +69,9 @@ export function DashboardAiRoadmaps(props: DashboardAiRoadmapsProps) {
type CustomProgressCardSkeletonProps = {};
function RoadmapCardSkeleton(props: CustomProgressCardSkeletonProps) {
function RoadmapCardSkeleton(
props: CustomProgressCardSkeletonProps,
) {
return (
<div className="h-[42px] w-full animate-pulse rounded-md bg-gray-200" />
);

View File

@@ -19,6 +19,7 @@ export function DashboardCardLink(props: DashboardCardLinkProps) {
className,
)}
href={href}
target="_blank"
>
<Icon className="mb-4 size-10 text-gray-300" strokeWidth={1.25} />
<h4 className="text-xl font-semibold tracking-wide">{title}</h4>

View File

@@ -54,14 +54,13 @@ export function DashboardPage(props: DashboardPageProps) {
return (
<div className="min-h-screen bg-gray-50 pb-20 pt-8">
<div className="container">
<div className="mb-6 flex flex-wrap items-center gap-1.5 sm:mb-8">
<div className="mb-6 sm:mb-8 flex flex-wrap items-center gap-1.5">
<DashboardTab
label="Personal"
isActive={!selectedTeamId}
onClick={() => setSelectedTeamId(undefined)}
avatar={userAvatar}
/>
{isLoading && (
<>
<DashboardTabSkeleton />

View File

@@ -1,26 +1,17 @@
import { cn } from '../../lib/classname';
type EmptyStackMessageProps = {
number: number | string;
number: number;
title: string;
description: string;
buttonText: string;
buttonLink: string;
bodyClassName?: string;
};
export function EmptyStackMessage(props: EmptyStackMessageProps) {
const { number, title, description, buttonText, buttonLink, bodyClassName } =
props;
const { number, title, description, buttonText, buttonLink } = props;
return (
<div className="absolute inset-0 flex items-center justify-center rounded-md bg-black/50">
<div
className={cn(
'flex max-w-[200px] flex-col items-center justify-center rounded-md bg-white p-4 shadow-sm',
bodyClassName,
)}
>
<div className="flex max-w-[200px] flex-col items-center justify-center rounded-md bg-white p-4 shadow-sm">
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-300 text-white">
{number}
</span>

View File

@@ -4,13 +4,7 @@ import { DashboardCardLink } from './DashboardCardLink';
import { useState } from 'react';
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
import { Simulate } from 'react-dom/test-utils';
import {
ArrowUpRight,
Bot,
BrainCircuit,
Map,
PencilRuler,
} from 'lucide-react';
import {Bot, BrainCircuit, Map, PencilRuler} from 'lucide-react';
type ListDashboardCustomProgressProps = {
progresses: UserProgress[];
@@ -46,18 +40,9 @@ export function ListDashboardCustomProgress(
<>
{customRoadmapModal}
<div className="mb-2 mt-6 flex items-center justify-between gap-2">
<h2 className="text-xs uppercase text-gray-400">
{isAIGeneratedRoadmaps ? 'My AI Roadmaps' : 'My Custom Roadmaps'}
</h2>
<a
href="/community"
className="rounded-full bg-gray-200 px-2.5 py-0.5 text-xs font-medium text-gray-700 hover:bg-gray-300 hover:text-black"
>
Community Roadmaps
</a>
</div>
<h2 className="mb-2 mt-6 text-xs uppercase text-gray-400">
{isAIGeneratedRoadmaps ? 'AI Generated Roadmaps' : 'Custom Roadmaps'}
</h2>
{!isLoading && progresses.length === 0 && isAIGeneratedRoadmaps && (
<DashboardCardLink

View File

@@ -14,9 +14,6 @@ import { CheckEmoji } from '../ReactIcons/CheckEmoji.tsx';
import { ConstructionEmoji } from '../ReactIcons/ConstructionEmoji.tsx';
import { BookEmoji } from '../ReactIcons/BookEmoji.tsx';
import { DashboardAiRoadmaps } from './DashboardAiRoadmaps.tsx';
import type { AllowedProfileVisibility } from '../../api/user.ts';
import { PencilIcon, type LucideIcon } from 'lucide-react';
import { cn } from '../../lib/classname.ts';
type UserDashboardResponse = {
name: string;
@@ -24,7 +21,6 @@ type UserDashboardResponse = {
avatar: string;
headline: string;
username: string;
profileVisibility: AllowedProfileVisibility;
progresses: UserProgress[];
projects: ProjectStatusDocument[];
aiRoadmaps: {
@@ -226,22 +222,20 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
return 0;
});
const { username } = personalDashboardDetails || {};
return (
<section>
{isLoading ? (
<div className="h-7 w-1/4 animate-pulse rounded-lg bg-gray-200"></div>
) : (
<div className="flex flex-col items-start justify-between gap-1 sm:flex-row sm:items-center">
<div className="flex items-start sm:items-center justify-between flex-col sm:flex-row gap-1">
<h2 className="text-lg font-medium">
Hi {name}, good {getCurrentPeriod()}!
</h2>
<a
href="/home"
className="rounded-full bg-gray-200 px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-300 hover:text-black"
className="text-xs text-purple-600 underline underline-offset-2 hover:text-purple-700"
>
Visit Homepage
Looking for old homepage? Click here
</a>
</div>
)}
@@ -259,20 +253,8 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
<DashboardCard
imgUrl={avatarLink}
title={name!}
description={
username ? 'View your profile' : 'Setup your profile'
}
href={username ? `/u/${username}` : '/account/update-profile'}
{...(username && {
externalLinkIcon: PencilIcon,
externalLinkHref: '/account/update-profile',
externalLinkText: 'Edit',
})}
className={
!username
? 'border-dashed border-gray-500 bg-gray-100 hover:border-gray-500 hover:bg-gray-200'
: ''
}
description="Setup your profile"
href="/account/update-profile"
/>
<DashboardCard
@@ -291,7 +273,7 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
<DashboardCard
icon={CheckEmoji}
title="Best Practices"
description="Do things the right way"
description="Do things right way"
href="/best-practices"
/>
</>
@@ -330,61 +312,34 @@ type DashboardCardProps = {
title: string;
description: string;
href: string;
externalLinkIcon?: LucideIcon;
externalLinkText?: string;
externalLinkHref?: string;
className?: string;
};
function DashboardCard(props: DashboardCardProps) {
const {
icon: Icon,
imgUrl,
title,
description,
href,
externalLinkHref,
externalLinkIcon: ExternalLinkIcon,
externalLinkText,
className,
} = props;
const { icon: Icon, imgUrl, title, description, href } = props;
return (
<div
className={cn(
'relative overflow-hidden',
className,
)}
<a
href={href}
target="_blank"
className="flex flex-col overflow-hidden rounded-lg border border-gray-300 bg-white hover:border-gray-400 hover:bg-gray-50"
>
<a href={href} className="flex flex-col rounded-lg border border-gray-300 bg-white hover:border-gray-400 hover:bg-gray-50">
{Icon && (
<div className="px-4 pb-3 pt-4">
<Icon className="size-6" />
</div>
)}
{imgUrl && (
<div className="px-4 pb-1.5 pt-3.5">
<img src={imgUrl} alt={title} className="size-8 rounded-full" />
</div>
)}
<div className="flex grow flex-col justify-center gap-0.5 p-4">
<h3 className="truncate font-medium text-black">{title}</h3>
<p className="text-xs text-black">{description}</p>
{Icon && (
<div className="px-4 pb-3 pt-4">
<Icon className="size-6" />
</div>
</a>
{externalLinkHref && (
<a
href={externalLinkHref}
className="absolute right-1 top-1 flex items-center gap-1.5 rounded-md bg-gray-200 p-1 px-2 text-xs text-gray-600 hover:bg-gray-300 hover:text-black"
>
{ExternalLinkIcon && <ExternalLinkIcon className="size-3" />}
{externalLinkText}
</a>
)}
</div>
{imgUrl && (
<div className="px-4 pb-1.5 pt-3.5">
<img src={imgUrl} alt={title} className="size-8 rounded-full" />
</div>
)}
<div className="flex grow flex-col justify-center gap-0.5 p-4">
<h3 className="truncate font-medium text-black">{title}</h3>
<p className="text-xs text-black">{description}</p>
</div>
</a>
);
}

View File

@@ -26,7 +26,8 @@ type ProgressStackProps = {
topicDoneToday: number;
};
const MAX_PROGRESS_TO_SHOW = 11;
const MAX_PROGRESS_TO_SHOW = 5;
const MAX_BOOKMARKS_TO_SHOW = 5;
const MAX_PROJECTS_TO_SHOW = 8;
type ProgressLaneProps = {
@@ -35,7 +36,6 @@ type ProgressLaneProps = {
linkHref?: string;
isLoading?: boolean;
isEmpty?: boolean;
loadingWrapperClassName?: string;
loadingSkeletonCount?: number;
loadingSkeletonClassName?: string;
children: React.ReactNode;
@@ -43,7 +43,6 @@ type ProgressLaneProps = {
emptyIcon?: LucideIcon;
emptyLinkText?: string;
emptyLinkHref?: string;
className?: string;
};
function ProgressLane(props: ProgressLaneProps) {
@@ -52,7 +51,6 @@ function ProgressLane(props: ProgressLaneProps) {
linkText,
linkHref,
isLoading = false,
loadingWrapperClassName = '',
loadingSkeletonCount = 4,
loadingSkeletonClassName = '',
children,
@@ -61,16 +59,10 @@ function ProgressLane(props: ProgressLaneProps) {
emptyMessage = `No ${title.toLowerCase()} to show`,
emptyLinkHref = '/roadmaps',
emptyLinkText = 'Explore',
className,
} = props;
return (
<div
className={cn(
'flex h-full flex-col rounded-md border bg-white px-4 py-3 shadow-sm',
className,
)}
>
<div className="flex h-full flex-col rounded-md border bg-white px-4 py-3 shadow-sm">
{isLoading && (
<div className={'flex flex-row justify-between'}>
<div className="h-[16px] w-[75px] animate-pulse rounded-md bg-gray-100"></div>
@@ -94,13 +86,11 @@ function ProgressLane(props: ProgressLaneProps) {
<div className="mt-4 flex flex-grow flex-col gap-1.5">
{isLoading && (
<div
className={cn('grid grid-cols-2 gap-2', loadingWrapperClassName)}
>
<>
{Array.from({ length: loadingSkeletonCount }).map((_, index) => (
<CardSkeleton key={index} className={loadingSkeletonClassName} />
))}
</div>
</>
)}
{!isLoading && children}
@@ -129,27 +119,29 @@ export function ProgressStack(props: ProgressStackProps) {
const { progresses, projects, isLoading, accountStreak, topicDoneToday } =
props;
const bookmarkedProgresses = progresses.filter(
(progress) => progress?.isFavorite,
);
const userProgresses = progresses.filter(
(progress) => !progress?.isFavorite || progress?.done > 0,
);
const [showAllProgresses, setShowAllProgresses] = useState(false);
const sortedProgresses = progresses.sort((a, b) => {
if (a.isFavorite && !b.isFavorite) {
return 1;
}
if (!a.isFavorite && b.isFavorite) {
return -1;
}
return 0;
});
const userProgressesToShow = showAllProgresses
? sortedProgresses
: sortedProgresses.slice(0, MAX_PROGRESS_TO_SHOW);
? userProgresses
: userProgresses.slice(0, MAX_PROGRESS_TO_SHOW);
const [showAllProjects, setShowAllProjects] = useState(false);
const projectsToShow = showAllProjects
? projects
: projects.slice(0, MAX_PROJECTS_TO_SHOW);
const [showAllBookmarks, setShowAllBookmarks] = useState(false);
const bookmarksToShow = showAllBookmarks
? bookmarkedProgresses
: bookmarkedProgresses.slice(0, MAX_BOOKMARKS_TO_SHOW);
const totalProjectFinished = projects.filter(
(project) => project.repositoryUrl,
).length;
@@ -175,70 +167,92 @@ export function ProgressStack(props: ProgressStackProps) {
</div>
<div className="mt-2 grid min-h-[330px] grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
<div className="relative col-span-2">
{!isLoading && userProgressesToShow.length === 0 && (
<div className="relative">
{!isLoading && bookmarksToShow.length === 0 && (
<EmptyStackMessage
number={1}
title={'Bookmark some Roadmaps'}
description={
'Bookmark some roadmaps to access them quickly and start updating your progress'
}
title={'Bookmark Roadmaps'}
description={'Bookmark some roadmaps to access them quickly'}
buttonText={'Explore Roadmaps'}
buttonLink={'/roadmaps'}
bodyClassName="max-w-[280px]"
/>
)}
<ProgressLane
title="Progress & Bookmarks"
title={'Bookmarks'}
isLoading={isLoading}
loadingSkeletonCount={MAX_PROGRESS_TO_SHOW}
linkHref="/roadmaps"
linkText="Roadmaps"
isEmpty={userProgressesToShow.length === 0}
loadingSkeletonCount={5}
linkHref={'/roadmaps'}
linkText={'Roadmaps'}
isEmpty={bookmarksToShow.length === 0}
emptyIcon={Bookmark}
emptyMessage={'No bookmarks to show'}
emptyLinkHref={'/roadmaps'}
emptyLinkText={'Explore Roadmaps'}
>
<div className="grid grid-cols-2 gap-2">
{userProgressesToShow.length > 0 && (
<>
{userProgressesToShow.map((progress) => {
const isFavorite =
progress.isFavorite &&
!progress.done &&
!progress.skipped;
if (isFavorite) {
return (
<DashboardBookmarkCard
key={progress.resourceId}
bookmark={progress}
/>
);
}
return (
<DashboardProgressCard
key={progress.resourceId}
progress={progress}
/>
);
})}
</>
)}
{sortedProgresses.length > MAX_PROGRESS_TO_SHOW && (
<ShowAllButton
showAll={showAllProgresses}
setShowAll={setShowAllProgresses}
count={sortedProgresses.length}
maxCount={MAX_PROGRESS_TO_SHOW}
className="min-h-[38px] rounded-md border border-dashed leading-none"
{bookmarksToShow.map((progress) => {
return (
<DashboardBookmarkCard
key={progress.resourceId}
bookmark={progress}
/>
)}
</div>
);
})}
{bookmarkedProgresses.length > MAX_BOOKMARKS_TO_SHOW && (
<ShowAllButton
showAll={showAllBookmarks}
setShowAll={setShowAllBookmarks}
count={bookmarkedProgresses.length}
maxCount={MAX_BOOKMARKS_TO_SHOW}
className="mb-0.5 mt-3"
/>
)}
</ProgressLane>
</div>
<div className="relative">
{!isLoading && userProgressesToShow.length === 0 && (
<EmptyStackMessage
number={2}
title={'Track Progress'}
description={'Pick your first roadmap and start learning'}
buttonText={'Explore roadmaps'}
buttonLink={'/roadmaps'}
/>
)}
<ProgressLane
title={'Progress'}
linkHref={'/roadmaps'}
linkText={'Roadmaps'}
isLoading={isLoading}
loadingSkeletonCount={5}
isEmpty={userProgressesToShow.length === 0}
emptyMessage={'Update your Progress'}
emptyIcon={Map}
emptyLinkText={'Explore Roadmaps'}
>
{userProgressesToShow.length > 0 && (
<>
{userProgressesToShow.map((progress) => {
return (
<DashboardProgressCard
key={progress.resourceId}
progress={progress}
/>
);
})}
</>
)}
{userProgresses.length > MAX_PROGRESS_TO_SHOW && (
<ShowAllButton
showAll={showAllProgresses}
setShowAll={setShowAllProgresses}
count={userProgresses.length}
maxCount={MAX_PROGRESS_TO_SHOW}
className="mb-0.5 mt-3"
/>
)}
</ProgressLane>
</div>
@@ -248,7 +262,6 @@ export function ProgressStack(props: ProgressStackProps) {
linkHref={'/projects'}
linkText={'Projects'}
isLoading={isLoading}
loadingWrapperClassName="grid-cols-1"
loadingSkeletonClassName={'h-5'}
loadingSkeletonCount={8}
isEmpty={projectsToShow.length === 0}
@@ -259,7 +272,7 @@ export function ProgressStack(props: ProgressStackProps) {
>
{!isLoading && projectsToShow.length === 0 && (
<EmptyStackMessage
number={2}
number={3}
title={'Build your first project'}
description={'Pick a project to practice and start building'}
buttonText={'Explore Projects'}
@@ -304,15 +317,17 @@ function ShowAllButton(props: ShowAllButtonProps) {
const { showAll, setShowAll, count, maxCount, className } = props;
return (
<button
className={cn(
'flex w-full items-center justify-center text-sm text-gray-500 hover:text-gray-700',
className,
)}
onClick={() => setShowAll(!showAll)}
>
{!showAll ? <>+ show {count - maxCount} more</> : <>- show less</>}
</button>
<span className="flex flex-grow items-end">
<button
className={cn(
'flex w-full items-center justify-center text-sm text-gray-500 hover:text-gray-700',
className,
)}
onClick={() => setShowAll(!showAll)}
>
{!showAll ? <>+ show {count - maxCount} more</> : <>- show less</>}
</button>
</span>
);
}
@@ -326,7 +341,7 @@ function CardSkeleton(props: CardSkeletonProps) {
return (
<div
className={cn(
'h-[38px] w-full animate-pulse rounded-md bg-gray-100',
'h-10 w-full animate-pulse rounded-md bg-gray-100',
className,
)}
/>

View File

@@ -1,7 +1,7 @@
---
import type { GuideFileType } from '../lib/guide';
import GuideListItem from './GuideListItem.astro';
import type { QuestionGroupType } from '../lib/question-group';
import { QuestionGroupType } from '../lib/question-group';
export interface Props {
heading: string;
@@ -15,8 +15,8 @@ const sortedGuides: (QuestionGroupType | GuideFileType)[] = [
...guides,
...questions,
].sort((a, b) => {
const aDate = new Date(a.data.date!);
const bDate = new Date(b.data.date!);
const aDate = new Date(a.frontmatter.date);
const bDate = new Date(b.frontmatter.date);
return bDate.getTime() - aDate.getTime();
});

View File

@@ -1,5 +1,4 @@
import { BadgeCheck, Bot, Telescope, Wand } from 'lucide-react';
import { RoadmapAlert } from '../RoadmapAlert';
import { BadgeCheck, Telescope, Wand } from 'lucide-react';
type AIRoadmapAlertProps = {
isListing?: boolean;
@@ -9,20 +8,46 @@ export function AIRoadmapAlert(props: AIRoadmapAlertProps) {
const { isListing = false } = props;
return (
<RoadmapAlert
title={`AI Generated Roadmap${isListing ? 's' : ''}`}
badgeText="Beta"
description={
<>
{isListing
? 'These are AI generated roadmaps and are not verified by'
: 'This is an AI generated roadmap and is not verified by'}{' '}
<span className={'font-semibold'}>roadmap.sh</span>. We are currently
in beta and working hard to improve the quality of the generated
roadmaps.
</>
}
floatingIcon={Bot}
/>
<div className="mb-3 w-full rounded-xl bg-yellow-100 px-4 py-3 text-yellow-800">
<h2 className="flex items-center text-base font-semibold text-yellow-800 sm:text-lg">
AI Generated Roadmap{isListing ? 's' : ''}{' '}
<span className="ml-1.5 rounded-md border border-yellow-500 bg-yellow-200 px-1.5 text-xs uppercase tracking-wide text-yellow-800">
Beta
</span>
</h2>
<p className="mb-2 mt-1">
{isListing
? 'These are AI generated roadmaps and are not verified by'
: 'This is an AI generated roadmap and is not verified by'}{' '}
<span className={'font-semibold'}>roadmap.sh</span>. We are currently in
beta and working hard to improve the quality of the generated roadmaps.
</p>
<p className="mb-1.5 mt-2 flex flex-col gap-2 text-sm sm:flex-row">
{isListing ? (
<a
href="/ai"
className="flex items-center gap-1.5 rounded-md border border-yellow-600 px-2 py-1 text-yellow-700 transition-colors hover:bg-yellow-300 hover:text-yellow-800"
>
<Wand size={15} />
Create your own Roadmap with AI
</a>
) : (
<a
href="/ai/explore"
className="flex items-center gap-1.5 rounded-md border border-yellow-600 px-2 py-1 text-yellow-700 transition-colors hover:bg-yellow-300 hover:text-yellow-800"
>
<Telescope size={15} />
Explore other AI Roadmaps
</a>
)}
<a
href="/roadmaps"
className="flex items-center gap-1.5 rounded-md border border-yellow-600 bg-yellow-200 px-2 py-1 text-yellow-800 transition-colors hover:bg-yellow-300"
>
<BadgeCheck size={15} />
Visit Official Roadmaps
</a>
</p>
</div>
);
}

View File

@@ -10,11 +10,11 @@ interface Props {
const { guide } = Astro.props;
const { headings: allHeadings, Content } = await guide.render();
const allHeadings = guide.getHeadings();
const tableOfContent = getGuideTableOfContent(allHeadings);
const showTableOfContent = tableOfContent.length > 0;
const { data: guideFrontmatter, author } = guide;
const { frontmatter: guideFrontmatter, author } = guide;
---
<article class='lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]'>
@@ -40,26 +40,26 @@ const { data: guideFrontmatter, author } = guide;
</h1>
<p class='my-0 flex items-center justify-start text-sm text-gray-400'>
<a
href={`/authors/${author.slug}`}
href={`/authors/${author.id}`}
class='inline-flex items-center font-medium underline-offset-2 hover:text-gray-600 hover:underline'
>
<img
alt={author.data.name}
src={author.data.imageUrl}
alt={author.frontmatter.name}
src={author.frontmatter.imageUrl}
class='mb-0 mr-2 inline h-5 w-5 rounded-full'
/>
{author.data.name}
{author.frontmatter.name}
</a>
<span class='mx-2 hidden sm:inline'>&middot;</span>
<a
class='hidden underline-offset-2 hover:text-gray-600 sm:inline'
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.slug}.md`}
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.id}.md`}
target='_blank'
>
Improve this Guide
</a>
</p>
<Content />
<guide.Content />
</MarkdownFile>
</div>
</article>

View File

@@ -7,7 +7,7 @@ export interface Props {
}
const { guide } = Astro.props;
const { data: frontmatter, author } = guide;
const { frontmatter, author } = guide;
return undefined;
---
@@ -18,18 +18,18 @@ return undefined;
class='hidden items-center justify-start text-gray-400 sm:flex sm:justify-center'
>
{
author?.data && (
author?.frontmatter && (
<>
<a
href={`/authors/${author.slug}`}
href={`/authors/${author.id}`}
class='inline-flex items-center font-medium hover:text-gray-600 hover:underline'
>
<img
alt={author.data.name}
src={author.data.imageUrl}
alt={author.frontmatter.name}
src={author.frontmatter.imageUrl}
class='mr-2 inline h-5 w-5 rounded-full'
/>
{author.data.name}
{author.frontmatter.name}
</a>
<span class='mx-1.5'>&middot;</span>
</>
@@ -39,7 +39,7 @@ return undefined;
<span class='mx-1.5'>&middot;</span>
<a
class='text-blue-400 hover:text-blue-500 hover:underline'
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.slug}.md`}
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.id}.md`}
target='_blank'>Improve this Guide</a
>
</p>

View File

@@ -1,7 +1,7 @@
---
import type { GuideFileType } from '../lib/guide';
import type { GuideFileType, GuideFrontmatter } from '../lib/guide';
import { replaceVariables } from '../lib/markdown';
import type { QuestionGroupType } from '../lib/question-group';
import { QuestionGroupType } from '../lib/question-group';
export interface Props {
guide: GuideFileType | QuestionGroupType;
@@ -14,7 +14,7 @@ function isQuestionGroupType(
}
const { guide } = Astro.props;
const { data: frontmatter, slug: id } = guide;
const { frontmatter, id } = guide;
let pageUrl = '';
let guideType = '';
@@ -23,9 +23,9 @@ if (isQuestionGroupType(guide)) {
pageUrl = `/questions/${id}`;
guideType = 'Questions';
} else {
const excludedBySlug = (frontmatter as GuideFileType['data']).excludedBySlug;
const excludedBySlug = (frontmatter as GuideFrontmatter).excludedBySlug;
pageUrl = excludedBySlug ? excludedBySlug : `/guides/${id}`;
guideType = (frontmatter as GuideFileType['data']).type;
guideType = (frontmatter as GuideFrontmatter).type;
}
---
@@ -46,7 +46,7 @@ if (isQuestionGroupType(guide)) {
New
<span class='hidden sm:inline'>
&middot;
{new Date(frontmatter.date!).toLocaleString('default', {
{new Date(frontmatter.date).toLocaleString('default', {
month: 'long',
})}
</span>

View File

@@ -1,16 +1,5 @@
---
interface Props {
class?: string;
}
const { class: className } = Astro.props;
---
<div
class:list={[
'container prose prose-xl prose-h2:mb-3 prose-h2:mt-10 prose-h2:scroll-mt-5 prose-h2:text-balance prose-h2:text-3xl prose-h3:mt-2 prose-h3:scroll-mt-5 prose-h3:text-balance prose-h4:text-balance prose-h5:text-balance prose-h5:font-medium prose-blockquote:font-normal prose-code:bg-transparent prose-img:mt-1 prose-h2:sm:scroll-mt-10 prose-h3:sm:scroll-mt-10',
className,
]}
class='container prose-h2:text-balance prose-h3:text-balance prose-h4:text-balance prose-h5:text-balance prose prose-xl prose-h2:mb-3 prose-h2:mt-10 prose-h2:scroll-mt-5 prose-h2:text-3xl prose-h3:mt-2 prose-h3:scroll-mt-5 prose-h5:font-medium prose-blockquote:font-normal prose-code:bg-transparent prose-img:mt-1 prose-h2:sm:scroll-mt-10 prose-h3:sm:scroll-mt-10'
>
<slot />
</div>

View File

@@ -14,7 +14,8 @@ import { showLoginPopup } from '../../lib/popup';
import { VoteButton } from './VoteButton.tsx';
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
import { SelectLanguages } from './SelectLanguages.tsx';
import type { ProjectFileType } from '../../lib/project.ts';
import type { ProjectFrontmatter } from '../../lib/project.ts';
import { ProjectSolutionModal } from './ProjectSolutionModal.tsx';
export interface ProjectStatusDocument {
_id?: string;
@@ -64,7 +65,7 @@ type PageState = {
};
type ListProjectSolutionsProps = {
project: ProjectFileType['data'];
project: ProjectFrontmatter;
projectId: string;
};

View File

@@ -1,8 +1,10 @@
import { Badge } from '../Badge.tsx';
import type { ProjectFileType } from '../../lib/project.ts';
import type {
ProjectDifficultyType,
ProjectFileType,
} from '../../lib/project.ts';
import { Users } from 'lucide-react';
import { formatCommaNumber } from '../../lib/number.ts';
import type { ProjectDifficultyType } from '../../content/project.ts';
type ProjectCardProps = {
project: ProjectFileType;
@@ -18,7 +20,7 @@ const badgeVariants: Record<ProjectDifficultyType, string> = {
export function ProjectCard(props: ProjectCardProps) {
const { project, userCount = 0 } = props;
const { data: frontmatter, slug: id } = project;
const { frontmatter, id } = project;
return (
<a

View File

@@ -2,16 +2,16 @@ import { ProjectCard } from './ProjectCard.tsx';
import { HeartHandshake, Trash2 } from 'lucide-react';
import { cn } from '../../lib/classname.ts';
import { useMemo, useState } from 'react';
import { type ProjectFileType } from '../../lib/project.ts';
import {
projectDifficulties,
type ProjectDifficultyType,
type ProjectFileType,
} from '../../lib/project.ts';
import {
deleteUrlParam,
getUrlParams,
setUrlParams,
} from '../../lib/browser.ts';
import {
projectDifficulties,
type ProjectDifficultyType,
} from '../../content/project.ts';
type DifficultyButtonProps = {
difficulty: ProjectDifficultyType;
@@ -56,7 +56,7 @@ export function ProjectsList(props: ProjectsListProps) {
const result = new Map<ProjectDifficultyType, ProjectFileType[]>();
for (const project of projects) {
const difficulty = project.data.difficulty;
const difficulty = project.frontmatter.difficulty;
if (!result.has(difficulty)) {
result.set(difficulty, []);
@@ -78,7 +78,6 @@ export function ProjectsList(props: ProjectsListProps) {
<div className="flex flex-wrap gap-1">
{projectDifficulties.map((projectDifficulty) => (
<DifficultyButton
key={projectDifficulty}
onClick={() => {
setDifficulty(projectDifficulty);
setUrlParams({ difficulty: projectDifficulty });
@@ -120,24 +119,18 @@ export function ProjectsList(props: ProjectsListProps) {
{matchingProjects
.sort((project) => {
return project.data.difficulty === 'beginner'
return project.frontmatter.difficulty === 'beginner'
? -1
: project.data.difficulty === 'intermediate'
: project.frontmatter.difficulty === 'intermediate'
? 0
: 1;
})
.sort((a, b) => {
return a.data.sort - b.data.sort;
return a.frontmatter.sort - b.frontmatter.sort;
})
.map((matchingProject) => {
const count = userCounts[matchingProject?.slug] || 0;
return (
<ProjectCard
key={matchingProject.slug}
project={matchingProject}
userCount={count}
/>
);
const count = userCounts[matchingProject?.id] || 0;
return <ProjectCard project={matchingProject} userCount={count} />;
})}
</div>
</div>

View File

@@ -7,9 +7,11 @@ import {
setUrlParams,
} from '../../lib/browser.ts';
import { CategoryFilterButton } from '../Roadmaps/CategoryFilterButton.tsx';
import { type ProjectFileType } from '../../lib/project.ts';
import {
projectDifficulties,
type ProjectFileType,
} from '../../lib/project.ts';
import { ProjectCard } from './ProjectCard.tsx';
import { projectDifficulties } from '../../content/project.ts';
type ProjectGroup = {
id: string;
@@ -26,7 +28,7 @@ export function ProjectsPage(props: ProjectsPageProps) {
const { roadmapsProjects, userCounts } = props;
const allUniqueProjectIds = new Set<string>(
roadmapsProjects.flatMap((group) =>
group.projects.map((project) => project.slug),
group.projects.map((project) => project.id),
),
);
const allUniqueProjects = useMemo(
@@ -35,7 +37,7 @@ export function ProjectsPage(props: ProjectsPageProps) {
.map((id) =>
roadmapsProjects
.flatMap((group) => group.projects)
.find((project) => project.slug === id),
.find((project) => project.id === id),
)
.filter(Boolean) as ProjectFileType[],
[allUniqueProjectIds],
@@ -65,8 +67,8 @@ export function ProjectsPage(props: ProjectsPageProps) {
const sortedVisibleProjects = useMemo(
() =>
visibleProjects.sort((a, b) => {
const projectADifficulty = a?.data.difficulty || 'beginner';
const projectBDifficulty = b?.data.difficulty || 'beginner';
const projectADifficulty = a?.frontmatter.difficulty || 'beginner';
const projectBDifficulty = b?.frontmatter.difficulty || 'beginner';
return (
projectDifficulties.indexOf(projectADifficulty) -
projectDifficulties.indexOf(projectBDifficulty)
@@ -187,7 +189,7 @@ export function ProjectsPage(props: ProjectsPageProps) {
<ProjectCard
key={project.id}
project={project}
userCount={userCounts[project.slug] || 0}
userCount={userCounts[project.id] || 0}
/>
))}
</div>

View File

@@ -1,9 +1,13 @@
---
import { getGuideTableOfContent, type HeadingGroupType } from '../../lib/guide';
import {
getGuideTableOfContent,
type GuideFileType,
HeadingGroupType,
} from '../../lib/guide';
import MarkdownFile from '../MarkdownFile.astro';
import { TableOfContent } from '../TableOfContent/TableOfContent';
import { markdownToHtml, replaceVariables } from '../../lib/markdown';
import type { QuestionGroupType } from '../../lib/question-group';
import { QuestionGroupType } from '../../lib/question-group';
import { QuestionsList } from './QuestionsList';
interface Props {
@@ -12,17 +16,19 @@ interface Props {
const { questionGroup } = Astro.props;
const { headings: allHeadings, Content } = await questionGroup.render();
const allHeadings = questionGroup.getHeadings();
const tableOfContent: HeadingGroupType[] = [
...getGuideTableOfContent(allHeadings),
{
depth: 2,
text: 'Test yourself with Flashcards',
title: 'Test with Flashcards',
children: [],
slug: 'test-with-flashcards',
text: 'Test yourself with Flashcards',
},
{
depth: 2,
title: 'Questions List',
children: [
{
depth: 2,
@@ -52,7 +58,7 @@ const tableOfContent: HeadingGroupType[] = [
];
const showTableOfContent = tableOfContent.length > 0;
const { data: guideFrontmatter, author } = questionGroup;
const { frontmatter: guideFrontmatter, author } = questionGroup;
---
<article class='lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]'>
@@ -80,20 +86,20 @@ const { data: guideFrontmatter, author } = questionGroup;
author && (
<p class='my-0 flex items-center justify-start text-sm text-gray-400'>
<a
href={`/authors/${author?.slug}`}
href={`/authors/${author?.id}`}
class='inline-flex items-center font-medium underline-offset-2 hover:text-gray-600 hover:underline'
>
<img
alt={author.data.name}
src={author.data.imageUrl}
alt={author.frontmatter.name}
src={author.frontmatter.imageUrl}
class='mb-0 mr-2 inline h-5 w-5 rounded-full'
/>
{author.data.name}
{author.frontmatter.name}
</a>
<span class='mx-2 hidden sm:inline'>&middot;</span>
<a
class='hidden underline-offset-2 hover:text-gray-600 sm:inline'
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/question-groups/${questionGroup.slug}`}
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/question-groups/${questionGroup.id}`}
target='_blank'
>
Improve this Guide
@@ -101,7 +107,7 @@ const { data: guideFrontmatter, author } = questionGroup;
</p>
)
}
<Content />
<questionGroup.Content />
<h2 id='test-with-flashcards'>Test yourself with Flashcards</h2>
<p>
@@ -110,7 +116,7 @@ const { data: guideFrontmatter, author } = questionGroup;
</p>
<div class='mx-0 sm:-mb-32'>
<QuestionsList
groupId={questionGroup.slug}
groupId={questionGroup.id}
questions={questionGroup.questions}
client:load
/>
@@ -130,8 +136,8 @@ const { data: guideFrontmatter, author } = questionGroup;
</h3>
{questionGroup.questions
.filter((q) => {
return q?.topics
?.map((t) => t.toLowerCase())
return q.topics
.map((t) => t.toLowerCase())
.includes(questionLevel);
})
.map((q) => (

View File

@@ -1,70 +0,0 @@
import {
BadgeCheck,
HeartHandshake,
Telescope,
type LucideIcon,
} from 'lucide-react';
import type { ReactNode } from 'react';
import { cn } from '../lib/classname';
type RoadmapAlertProps = {
title: string;
badgeText?: string;
description: string | ReactNode;
floatingIcon: LucideIcon;
className?: string;
};
export function RoadmapAlert(props: RoadmapAlertProps) {
const {
title,
badgeText,
description,
floatingIcon: FloatingIcon,
className,
} = props;
return (
<div
className={cn(
'relative mb-3 w-full rounded-xl bg-yellow-100 px-4 py-3 text-yellow-800',
className,
)}
>
<h2 className="flex items-center text-base font-semibold text-yellow-800 sm:text-lg">
{title}{' '}
{badgeText && (
<span className="ml-1.5 rounded-md border border-yellow-500 bg-yellow-200 px-1.5 text-xs uppercase tracking-wide text-yellow-800">
{badgeText}
</span>
)}
</h2>
<p className="mb-2 mt-1 text-balance">{description}</p>
<p className="mb-1.5 mt-2 flex flex-col gap-2 text-sm md:flex-row">
<a
href="/roadmaps"
className="flex items-center gap-1.5 rounded-md border border-yellow-600 bg-yellow-200 px-2 py-1 text-yellow-800 transition-colors hover:bg-yellow-300"
>
<BadgeCheck size={15} />
Visit Official Roadmaps
</a>
<a
href="/community"
className="flex items-center gap-1.5 rounded-md border border-yellow-600 px-2 py-1 text-yellow-700 transition-colors hover:bg-yellow-300 hover:text-yellow-800"
>
<HeartHandshake size={15} />
Community Roadmaps
</a>
<a
href="/ai/explore"
className="flex items-center gap-1.5 rounded-md border border-yellow-600 px-2 py-1 text-yellow-700 transition-colors hover:bg-yellow-300 hover:text-yellow-800"
>
<Telescope size={15} />
AI Generated Roadmaps
</a>
</p>
<FloatingIcon className="pointer-events-none absolute right-2 top-2 hidden h-12 w-12 text-yellow-500 opacity-50 sm:block md:bottom-2 md:top-auto" />
</div>
);
}

View File

@@ -1,31 +0,0 @@
import { useIsMounted } from '../../hooks/use-is-mounted';
import { MarkFavorite } from '../FeaturedItems/MarkFavorite';
import type { GroupType } from './RoadmapsPage';
type RoadmapCardProps = {
roadmap: GroupType['roadmaps'][number];
};
export function RoadmapCard(props: RoadmapCardProps) {
const { roadmap } = props;
const isMounted = useIsMounted();
return (
<a
key={roadmap.link}
className="relative rounded-md border bg-white px-3 py-2 text-left text-sm shadow-sm transition-all hover:border-gray-300 hover:bg-gray-50"
href={roadmap.link}
>
{roadmap.title}
{isMounted && (
<MarkFavorite
resourceId={roadmap.link.split('/').pop()!}
resourceType="roadmap"
className="data-[is-favorite=true]:opacity-35"
/>
)}
</a>
);
}

View File

@@ -8,10 +8,6 @@ import {
getUrlParams,
setUrlParams,
} from '../../lib/browser.ts';
import { RoadmapCard } from './RoadmapCard.tsx';
import { httpGet } from '../../lib/http.ts';
import type { UserProgressResponse } from '../HeroSection/FavoriteRoadmaps.tsx';
import { isLoggedIn } from '../../lib/jwt.ts';
const groupNames = [
'Absolute Beginners',
@@ -31,7 +27,7 @@ const groupNames = [
type AllowGroupNames = (typeof groupNames)[number];
export type GroupType = {
type GroupType = {
group: AllowGroupNames;
roadmaps: {
title: string;
@@ -285,12 +281,6 @@ const groups: GroupType[] = [
type: 'skill',
otherGroups: ['Web Development'],
},
{
title: 'Redis',
link: '/redis',
type: 'skill',
otherGroups: ['Web Development'],
},
],
},
{
@@ -483,37 +473,6 @@ export function RoadmapsPage() {
]);
}, [activeGroup]);
async function loadProgress() {
const { response: progressList, error } =
await httpGet<UserProgressResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-hero-roadmaps`,
);
if (error || !progressList) {
return;
}
progressList?.forEach((progress) => {
window.dispatchEvent(
new CustomEvent('mark-favorite', {
detail: {
resourceId: progress.resourceId,
resourceType: progress.resourceType,
isFavorite: progress.isFavorite,
},
}),
);
});
}
useEffect(() => {
if (!isLoggedIn()) {
return;
}
loadProgress().finally(() => {});
}, []);
useEffect(() => {
const { g } = getUrlParams() as { g: AllowGroupNames };
if (!g) {
@@ -588,7 +547,13 @@ export function RoadmapsPage() {
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2 md:grid-cols-3">
{group.roadmaps.map((roadmap) => (
<RoadmapCard roadmap={roadmap} key={roadmap.link} />
<a
key={roadmap.link}
className="rounded-md border bg-white px-3 py-2 text-left text-sm shadow-sm transition-all hover:border-gray-300 hover:bg-gray-50"
href={roadmap.link}
>
{roadmap.title}
</a>
))}
</div>
</div>

View File

@@ -87,13 +87,15 @@ export function ProfileUsername(props: ProfileUsernameProps) {
{currentUsername !== username && username && isUnique && (
<span className="text-xs text-green-600">
URL after update{' '}
<span
<a
href={`${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/u/${username}`}
target="_blank"
className={
'ml-0.5 rounded-md border border-purple-500 px-1.5 py-0.5 text-xs font-medium text-purple-700 transition-colors'
'ml-0.5 rounded-md border border-purple-500 px-1.5 py-0.5 text-xs font-medium text-purple-700 transition-colors hover:bg-purple-500 hover:text-white'
}
>
roadmap.sh/u/{username}
</span>
</a>
</span>
)}
</span>

View File

@@ -71,7 +71,6 @@ export function UpdatePublicProfileForm() {
const [profileRoadmaps, setProfileRoadmaps] = useState<RoadmapType[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isProfileUpdated, setIsProfileUpdated] = useState(false);
const { isCopied, copyText } = useCopyText();
@@ -110,7 +109,6 @@ export function UpdatePublicProfileForm() {
await loadProfileSettings();
toast.success('Profile updated successfully');
setIsProfileUpdated(true);
};
const loadProfileSettings = async () => {
@@ -595,42 +593,6 @@ export function UpdatePublicProfileForm() {
>
{isLoading ? 'Please wait..' : 'Save Profile'}
</button>
{isProfileUpdated && publicProfileUrl && (
<div className="flex items-center gap-2">
<button
type="button"
className={cn(
'flex shrink-0 flex-row items-center gap-1 rounded-lg border border-black py-1.5 pl-2.5 pr-3.5 text-xs uppercase text-black transition-colors hover:bg-black hover:text-white',
isCopied
? 'border-green-600 bg-green-600 text-white hover:bg-green-600 hover:text-white'
: '',
)}
onClick={() => {
copyText(`${window.location.origin}${publicProfileUrl}`);
}}
>
{isCopied ? (
<>
<CheckCircle className="size-4" />
Copied Profile URL
</>
) : (
<>
<Copy className="size-4" />
Copy Profile URL
</>
)}
</button>
<a
className='flex shrink-0 flex-row items-center gap-1 rounded-lg border border-black py-1.5 pl-2.5 pr-3.5 text-xs uppercase text-black transition-colors hover:bg-black hover:text-white'
href={publicProfileUrl}
target="_blank"
>
<ArrowUpRight className="size-4" />
View Profile
</a>
</div>
)}
</form>
</div>
);

View File

@@ -3,7 +3,6 @@ import {
Globe,
LinkedinIcon,
Mail,
Pencil,
Twitter,
} from 'lucide-react';
import type { GetPublicProfileResponse } from '../../api/user';
@@ -16,12 +15,11 @@ type UserPublicProfileHeaderProps = {
export function UserPublicProfileHeader(props: UserPublicProfileHeaderProps) {
const { userDetails } = props;
const { name, links, publicConfig, avatar, email, isOwnProfile } =
userDetails;
const { name, links, publicConfig, avatar, email } = userDetails;
const { headline, isAvailableForHire, isEmailVisible } = publicConfig!;
return (
<div className="container relative flex items-center gap-6 rounded-xl border bg-white p-8">
<div className="container flex items-center gap-6 rounded-xl border bg-white p-8">
<img
src={
avatar
@@ -29,7 +27,7 @@ export function UserPublicProfileHeader(props: UserPublicProfileHeaderProps) {
: '/images/default-avatar.png'
}
alt={name}
className="h-32 w-32 rounded-full object-cover"
className="h-32 w-32 object-cover rounded-full"
/>
<div>
@@ -53,16 +51,6 @@ export function UserPublicProfileHeader(props: UserPublicProfileHeaderProps) {
{isEmailVisible && <UserLink href={`mailto:${email}`} icon={Mail} />}
</div>
</div>
{isOwnProfile && (
<a
href="/account/update-profile"
className="absolute right-4 top-4 flex items-center gap-1.5 text-sm text-gray-500 hover:text-black"
>
<Pencil className="h-3 w-3 stroke-2" />
Edit Profile
</a>
)}
</div>
);
}

View File

@@ -38,10 +38,6 @@ export function UserPublicProjects(props: UserPublicProjectsProps) {
return 0;
}) || [];
if (!enrichedProjects.length) {
return null;
}
return (
<div className="mt-5">
<h2 className="mb-2 text-xs uppercase tracking-wide text-gray-400">

View File

@@ -1,12 +1,13 @@
---
import type { VideoFileType } from '../lib/video';
import YouTubeAlert from './YouTubeAlert.astro';
export interface Props {
video: VideoFileType;
}
const { video } = Astro.props;
const { data: frontmatter, author } = video;
const { frontmatter, author } = video;
---
<div class='border-b bg-white py-5 sm:py-12'>
@@ -15,15 +16,15 @@ const { data: frontmatter, author } = video;
class='hidden items-center justify-start text-gray-400 sm:flex sm:justify-center'
>
<a
href={`/authors/${author.slug}`}
href={`/authors/${author.id}`}
class='inline-flex items-center font-medium hover:text-gray-600 hover:underline'
>
<img
alt={author.data.name}
src={author.data.imageUrl}
alt={author.frontmatter.name}
src={author.frontmatter.imageUrl}
class='mr-2 inline h-5 w-5 rounded-full'
/>
{author.data.name}
{author.frontmatter.name}
</a>
<span class='mx-1.5'>&middot;</span>
<span class='capitalize'>Illustrated Video</span>

View File

@@ -6,21 +6,21 @@ export interface Props {
}
const { video } = Astro.props;
const { data: frontmatter, slug: id } = video;
const { frontmatter, id } = video;
---
<a
class:list={[
'text-md group block flex items-center justify-between border-b py-2 text-gray-600 no-underline hover:text-blue-600',
'block no-underline py-2 group text-md items-center text-gray-600 hover:text-blue-600 flex justify-between border-b',
]}
href={`/videos/${id}`}
>
<span class='transition-transform group-hover:translate-x-2'>
<span class='group-hover:translate-x-2 transition-transform'>
{frontmatter.title}
{
frontmatter.isNew && (
<span class='ml-1.5 rounded-sm bg-green-300 px-1.5 py-0.5 text-xs font-medium uppercase text-green-900'>
<span class='bg-green-300 text-green-900 text-xs font-medium px-1.5 py-0.5 rounded-sm uppercase ml-1.5'>
New
<span class='hidden sm:inline'>
&middot;
@@ -32,9 +32,9 @@ const { data: frontmatter, slug: id } = video;
)
}
</span>
<span class='hidden text-xs capitalize text-gray-500 sm:block'>
<span class='capitalize text-gray-500 text-xs hidden sm:block'>
{frontmatter.duration}
</span>
<span class='block text-xs text-gray-400 sm:hidden'> &raquo;</span>
<span class='text-gray-400 text-xs block sm:hidden'> &raquo;</span>
</a>

View File

@@ -1,23 +0,0 @@
import { defineCollection, z } from 'astro:content';
export const authorCollection = defineCollection({
type: 'content',
schema: z.object({
name: z.string(),
imageUrl: z.string(),
employment: z
.object({
title: z.string(),
company: z.string(),
})
.optional(),
social: z
.object({
linkedin: z.string().optional(),
twitter: z.string().optional(),
github: z.string().optional(),
website: z.string().optional(),
})
.optional(),
}),
});

View File

@@ -1,16 +0,0 @@
import { defineCollection, z } from 'astro:content';
export const changelogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
seo: z
.object({
title: z.string(),
description: z.string().optional(),
})
.optional(),
date: z.date(),
}),
});

View File

@@ -1,25 +0,0 @@
---
title: 'New Dashboard, Leaderboards and Projects'
description: 'New leaderboard page showing the most active users'
seo:
title: 'Leaderboard Page - roadmap.sh'
description: ''
date: 2024-09-13
---
TL;DR: new dashboard, leaderboard page and projects page.
- New dashboard for logged-in users
- New leaderboard page
- Projects page listing all projects
- Ability to stop a started project
- Frontend and backend content improvements
- Bug fixes
![Leaderboard Page](https://assets.roadmap.sh/guest/personal-dashboard.png)
We just launched a dedicated dashboard for logged-in users to showing progress, projects, bookmarks and more. You can still access the old homepage by visiting [this page](https://roadmap.sh/home).
We also launched a new [leaderboard page](/leaderboard) showing the most active users, users who completed most projects and more.
There is also a [new projects page](/projects) where you can see all the projects you have been working on. You can also now stop a started project.

View File

@@ -1,12 +0,0 @@
---
title: 'New Dashboard Page'
description: 'We have added a new dashboard page to help you track your progress'
seo:
title: 'New Dashboard Page - roadmap.sh'
description: 'We have added a new dashboard page to help you track your progress'
date: 2024-09-12
---
We have revamped the dashboard page for logged-in users. The new dashboard page will help you track your progress and see your overall progress in a single view. We have also added a new progress bar to help you visualize your progress.
If you want to access the guest homepage, you check check it out [here](/home).

View File

@@ -1,15 +0,0 @@
import { authorCollection } from './author';
import { changelogCollection } from './changelog';
import { guideCollection } from './guide';
import { projectCollection } from './project';
import { questionGroupCollection } from './question-group';
import { videoCollection } from './video';
export const collections = {
authors: authorCollection,
guides: guideCollection,
'question-groups': questionGroupCollection,
projects: projectCollection,
videos: videoCollection,
changelogs: changelogCollection,
};

View File

@@ -1,25 +0,0 @@
import { defineCollection, z } from 'astro:content';
export const guideCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
authorId: z.string(),
canonicalUrl: z.string().optional(),
excludedBySlug: z.string().optional(),
seo: z.object({
title: z.string(),
description: z.string(),
ogImageUrl: z.string().optional(),
}),
isNew: z.boolean(),
type: z.enum(['visual', 'textual']),
date: z.date(),
sitemap: z.object({
priority: z.number(),
changefreq: z.enum(['daily', 'weekly', 'monthly', 'yearly']),
}),
tags: z.array(z.string()).optional(),
}),
});

View File

@@ -1,28 +0,0 @@
import { defineCollection, z } from 'astro:content';
export const projectDifficulties = [
'beginner',
'intermediate',
'advanced',
] as const;
export type ProjectDifficultyType = (typeof projectDifficulties)[number];
export const projectCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
isNew: z.boolean(),
sort: z.number(),
difficulty: z.enum(projectDifficulties),
nature: z.string(),
skills: z.array(z.string()),
seo: z.object({
title: z.string(),
description: z.string(),
keywords: z.array(z.string()),
ogImageUrl: z.string().optional(),
}),
roadmapIds: z.array(z.string()),
}),
});

View File

@@ -1,32 +0,0 @@
import { defineCollection, z } from 'astro:content';
export const questionGroupCollection = defineCollection({
type: 'content',
schema: z.object({
order: z.number(),
briefTitle: z.string(),
briefDescription: z.string(),
title: z.string(),
description: z.string(),
isNew: z.boolean(),
authorId: z.string().optional(),
date: z.date().optional(),
seo: z.object({
title: z.string(),
description: z.string(),
ogImageUrl: z.string().optional(),
keywords: z.array(z.string()),
}),
sitemap: z.object({
priority: z.number(),
changefreq: z.string(),
}),
questions: z.array(
z.object({
question: z.string(),
answer: z.string(),
topics: z.array(z.string()),
}),
),
}),
});

View File

@@ -1,24 +0,0 @@
import { defineCollection, z } from 'astro:content';
export const videoCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
authorId: z.string(),
seo: z
.object({
title: z.string().optional(),
description: z.string().optional(),
})
.optional(),
isNew: z.boolean(),
duration: z.string(),
date: z.date(),
sitemap: z.object({
priority: z.number(),
changefreq: z.enum(['daily', 'weekly', 'monthly', 'yearly']),
}),
tags: z.array(z.string()),
}),
});

View File

@@ -1,5 +1,5 @@
---
title: 'What is a DevOps Engineer? Responsibilities & Roles in @currentYear@'
title: 'What is a DevOps Engineer? Responsbilities & Roles in @currentYear@'
description: 'Explore the responsibilities and roles of a DevOps Engineer in @currentYear@. Gain insights into the evolving field of DevOps and what it takes to succeed.'
authorId: ekene
excludedBySlug: '/devops/devops-engineer'
@@ -21,13 +21,13 @@ tags:
![What is a DevOps engineer and what are their responsibilities?](https://assets.roadmap.sh/guest/what-is-devops-engineer-jort4.jpg)
Are you a developer monitoring recent changes in the ecosystem, looking to change careers or pick up new skills in 2024? If your choice is [DevOps](https://roadmap.sh/devops), you might be wondering what it entails, what it will take to become one in 2024, and how it is affected by the recent changes in the tech ecosystem.
Are you a developer monitoring recent changes in the ecosystem, looking to change careers or pick up new skills in 2024? If your choice is DevOps, you might be wondering what it entails, what it will take to become one in 2024, and how it is affected by the recent changes in the tech ecosystem.
In recent years, the technology ecosystem has experienced a constant shift in the way hiring managers reach out, companies hire, and the roles and responsibilities described in job postings. Particularly, 2023 proved to be a challenging year as layoffs in the technology sector grew significantly, with more than [262,000 employees laid off across 1,180 firms](https://www.statista.com/statistics/199999/worldwide-tech-layoffs-covid-19/).
Despite this change, DevOps, a field within the same ecosystem, has experienced continuous growth. In fact, the DevOps market size is expected to grow to [25.5 billion USD by 2028](https://www.marketsandmarkets.com/Market-Reports/devops-market-824.html#:~:text=The%20global%20DevOps%20market%20size,USD%2010.4%20billion%20in%202023.). This indicates that the roles and responsibilities of a DevOps engineer in the modern technology environment will evolve alongside this increasing demand.
In this guide, we'll discuss the roles and responsibilities of a DevOps engineer, the importance of DevOps in teams, common roles within a DevOps team, and best practices for DevOps teams. Finally, the guide will offer roadmaps for your DevOps journey.
In this guide, we'll discuss the roles and responsibilities of a [DevOps engineer](https://roadmap.sh/devops), the importance of DevOps in teams, common roles within a DevOps team, and best practices for DevOps teams. Finally, the guide will offer roadmaps for your DevOps journey.
A DevOps engineer's roles and responsibilities include:

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