mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-13 02:01:57 +08:00
Compare commits
369 Commits
content/gr
...
1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c5d28b68b | ||
|
|
2f8c0c5748 | ||
|
|
5e08af99b2 | ||
|
|
2882815313 | ||
|
|
e093f98a42 | ||
|
|
d3f8e0517b | ||
|
|
efc874163b | ||
|
|
3e8abbed13 | ||
|
|
244d336d8e | ||
|
|
9d24b98f67 | ||
|
|
007bd7feb0 | ||
|
|
7619945028 | ||
|
|
3265f9729d | ||
|
|
4591ad2336 | ||
|
|
163e03f578 | ||
|
|
2215174c20 | ||
|
|
aa52e08ac4 | ||
|
|
96acb6c93e | ||
|
|
69ebd50a90 | ||
|
|
e2eaf7d19c | ||
|
|
da7ba5bf4c | ||
|
|
0d17cf145c | ||
|
|
8a7f7a4a83 | ||
|
|
7d3255576b | ||
|
|
97529cbf54 | ||
|
|
52af178a19 | ||
|
|
f0425fd964 | ||
|
|
570d6a04b1 | ||
|
|
1e677183aa | ||
|
|
14a29b4634 | ||
|
|
bc66a805e3 | ||
|
|
f6c10d7344 | ||
|
|
4e96943374 | ||
|
|
1235459d7a | ||
|
|
0d35fe0364 | ||
|
|
76d6fab581 | ||
|
|
da39147539 | ||
|
|
9e230a01a2 | ||
|
|
4c3452926a | ||
|
|
b36c5b3c26 | ||
|
|
95fe79a0f1 | ||
|
|
850a9ffc9d | ||
|
|
2b8d18d880 | ||
|
|
9b551a69a7 | ||
|
|
cdb9201b2f | ||
|
|
ab15d91614 | ||
|
|
9d2fdfa7cf | ||
|
|
bcad685e27 | ||
|
|
74ae339fe1 | ||
|
|
5811fd8832 | ||
|
|
6c710a92c1 | ||
|
|
51f068085d | ||
|
|
1795bc1495 | ||
|
|
d4ef930187 | ||
|
|
54fae335c2 | ||
|
|
0f886e9def | ||
|
|
3f299cdd8b | ||
|
|
7fccd6b399 | ||
|
|
f75512e96a | ||
|
|
32ff9a700b | ||
|
|
6976202171 | ||
|
|
cf1cca7cb3 | ||
|
|
2236c3f93c | ||
|
|
4f067504a2 | ||
|
|
42747f4f97 | ||
|
|
1d952f75f8 | ||
|
|
9e61ef5dd1 | ||
|
|
c7770cc64c | ||
|
|
af7e25dc92 | ||
|
|
cf3365e778 | ||
|
|
bd69872059 | ||
|
|
746ee3d548 | ||
|
|
73c55a0eaa | ||
|
|
299d0f3ada | ||
|
|
98097f939a | ||
|
|
f4904da3f8 | ||
|
|
465c00b4d5 | ||
|
|
69c54e5dfe | ||
|
|
6f4898c216 | ||
|
|
b8cc07c29e | ||
|
|
eae0ad3ecb | ||
|
|
56bf52e641 | ||
|
|
689f24e0f1 | ||
|
|
63d66b3f4e | ||
|
|
4930c00f78 | ||
|
|
5745fc56bf | ||
|
|
55d5ced587 | ||
|
|
018be76895 | ||
|
|
b268106684 | ||
|
|
56e2108be2 | ||
|
|
9dfbceda7c | ||
|
|
c698265f42 | ||
|
|
752d4614b8 | ||
|
|
d73e08f8f6 | ||
|
|
cf648924cf | ||
|
|
2d15290566 | ||
|
|
06dd1934f3 | ||
|
|
316ada1259 | ||
|
|
30d2f15433 | ||
|
|
4ac1319d8d | ||
|
|
4e924981c1 | ||
|
|
fdf3fd050b | ||
|
|
79afd0a6a8 | ||
|
|
03e35ee928 | ||
|
|
eaaedb8034 | ||
|
|
84e87a501e | ||
|
|
8fca669787 | ||
|
|
3c1d41119f | ||
|
|
495fd37eae | ||
|
|
4cfeb1c372 | ||
|
|
91a47faec0 | ||
|
|
8c03aedea1 | ||
|
|
9a515f85c1 | ||
|
|
0a2468aad2 | ||
|
|
fc2eb36d58 | ||
|
|
3c5ea2131d | ||
|
|
75e1f67ee8 | ||
|
|
b40894cfdc | ||
|
|
4fb2e1f46d | ||
|
|
8eccfd22e3 | ||
|
|
d84800fcaf | ||
|
|
bb3260f4b7 | ||
|
|
9a2e1fd673 | ||
|
|
3f599fab35 | ||
|
|
cdc710123f | ||
|
|
bb43c8eba6 | ||
|
|
c01d595546 | ||
|
|
77a66fd25d | ||
|
|
a93ac86766 | ||
|
|
4044dbea91 | ||
|
|
3fc9ffe8b4 | ||
|
|
880475f6de | ||
|
|
a26945288b | ||
|
|
b97ae52a1b | ||
|
|
76ddeeedb2 | ||
|
|
00b7fe6e7f | ||
|
|
c43442f127 | ||
|
|
68c62d218d | ||
|
|
47b10a1a1a | ||
|
|
1fd135d1c1 | ||
|
|
61bdc80f5a | ||
|
|
4fbefd5ae9 | ||
|
|
835476ed31 | ||
|
|
83745ae1b4 | ||
|
|
9465cfb5c2 | ||
|
|
4edd398770 | ||
|
|
21b3b7cbdf | ||
|
|
ae6763bf83 | ||
|
|
be5a61b697 | ||
|
|
8e25dca636 | ||
|
|
b91d404f17 | ||
|
|
80f2cb8cbc | ||
|
|
2dc3d4fd24 | ||
|
|
2432ff9fd4 | ||
|
|
8f1f8846c9 | ||
|
|
7dac8665a0 | ||
|
|
f0181ff08f | ||
|
|
0ad95c2dd0 | ||
|
|
d184e93519 | ||
|
|
4ef31700a5 | ||
|
|
087f4e5c25 | ||
|
|
c5ae26458a | ||
|
|
0c6de5d89b | ||
|
|
124d113162 | ||
|
|
c88b0f3b1a | ||
|
|
06d72599d9 | ||
|
|
eb9cd6cdcc | ||
|
|
c7589b8325 | ||
|
|
4c07ac509b | ||
|
|
1240b6b1bc | ||
|
|
ad05c49570 | ||
|
|
c01a854a5a | ||
|
|
7b1dde1d62 | ||
|
|
56b0275b06 | ||
|
|
7a0d784d81 | ||
|
|
2c9eb1f9ee | ||
|
|
e4ca1c9598 | ||
|
|
2b8e06d651 | ||
|
|
56088a838c | ||
|
|
542d82c2dc | ||
|
|
980322bae0 | ||
|
|
56fbe9a685 | ||
|
|
6939240d59 | ||
|
|
4caaee3da5 | ||
|
|
e829af3e62 | ||
|
|
7ba0fa9004 | ||
|
|
74433cd0d3 | ||
|
|
dec3e992b3 | ||
|
|
7a4c27460f | ||
|
|
5553b411eb | ||
|
|
98cc968ed1 | ||
|
|
3de37468a6 | ||
|
|
3364eae0a6 | ||
|
|
a06eaec5d4 | ||
|
|
10e433f538 | ||
|
|
129deed6a9 | ||
|
|
ce35a8112f | ||
|
|
35f6070133 | ||
|
|
629f1058f2 | ||
|
|
199310df93 | ||
|
|
0d45fcbf79 | ||
|
|
47cbcde5dc | ||
|
|
5b12eb9e02 | ||
|
|
6632b46d98 | ||
|
|
25e009a63f | ||
|
|
9ae7eed1e3 | ||
|
|
8db62cb19f | ||
|
|
d1a991b18c | ||
|
|
8107e008ff | ||
|
|
944858bbb1 | ||
|
|
b864c60ea3 | ||
|
|
618b55f601 | ||
|
|
b5c65b408b | ||
|
|
21f2ef80ba | ||
|
|
ebd351e133 | ||
|
|
77dab81b92 | ||
|
|
0350da2929 | ||
|
|
59c07c9000 | ||
|
|
79ab31dec7 | ||
|
|
16983cb950 | ||
|
|
e29fe52cb1 | ||
|
|
7921acb666 | ||
|
|
b53f8c982c | ||
|
|
0b72a07147 | ||
|
|
5155a0c358 | ||
|
|
bd5663ab26 | ||
|
|
af3ccd5bb5 | ||
|
|
035eaa47e8 | ||
|
|
3541d4e717 | ||
|
|
e8dcfe97f2 | ||
|
|
8f3307e53e | ||
|
|
dcc825416d | ||
|
|
f8fcb8d600 | ||
|
|
40919dec14 | ||
|
|
f3592155bf | ||
|
|
927ee73be7 | ||
|
|
4f81d5374e | ||
|
|
e95fd69886 | ||
|
|
11d9da5afb | ||
|
|
cea8abc5ef | ||
|
|
7169d3bb8f | ||
|
|
ae9c1c4992 | ||
|
|
58e560af7d | ||
|
|
09fa166f56 | ||
|
|
6ed7d9c25f | ||
|
|
e59fc5e4e9 | ||
|
|
f5da05c3ec | ||
|
|
07b200b878 | ||
|
|
ccca782f25 | ||
|
|
77d9846d9b | ||
|
|
8da175e9d8 | ||
|
|
467634889b | ||
|
|
b46b425b41 | ||
|
|
9e23439f0c | ||
|
|
c6db625e35 | ||
|
|
672245e4e4 | ||
|
|
e4ce3475c6 | ||
|
|
d15b97db73 | ||
|
|
8f040e5e8a | ||
|
|
888800d2a0 | ||
|
|
51b2c70586 | ||
|
|
5b4cc86f61 | ||
|
|
9952ee5805 | ||
|
|
dacbf09f55 | ||
|
|
a16787ab58 | ||
|
|
7d45c8e462 | ||
|
|
796bde76c9 | ||
|
|
22d5622e1e | ||
|
|
2312fdd608 | ||
|
|
bc2ecea03b | ||
|
|
84a551f906 | ||
|
|
9fab5c7134 | ||
|
|
c61f4a845d | ||
|
|
025753b279 | ||
|
|
6b9901db28 | ||
|
|
34f0e483ec | ||
|
|
0ae9bc0e3e | ||
|
|
3f17f60daf | ||
|
|
7f2acba352 | ||
|
|
907fb9915f | ||
|
|
fd2e64ec50 | ||
|
|
3fd5b9e744 | ||
|
|
edff9156ff | ||
|
|
e1c89585e9 | ||
|
|
abaa839b26 | ||
|
|
1bc7384929 | ||
|
|
6a148295f7 | ||
|
|
ea25f2d99b | ||
|
|
08303c0623 | ||
|
|
f18f9fb5b3 | ||
|
|
dfc07e0753 | ||
|
|
64a19fdc3c | ||
|
|
1b3e8712ff | ||
|
|
f242c6e358 | ||
|
|
7e2121bed9 | ||
|
|
bb80ceb7ba | ||
|
|
25dfb28368 | ||
|
|
a1c75bb9f8 | ||
|
|
efdb628120 | ||
|
|
ac23dddeb9 | ||
|
|
b208eaa1bd | ||
|
|
8ebf97277c | ||
|
|
928d79e3fb | ||
|
|
9b95218eb8 | ||
|
|
8bcdd84f0f | ||
|
|
67a72aab11 | ||
|
|
771f3a9cb7 | ||
|
|
38b6b34437 | ||
|
|
548dfd85e7 | ||
|
|
971d23c43a | ||
|
|
3aac8de849 | ||
|
|
8d605735b2 | ||
|
|
7debdb90c1 | ||
|
|
f6f5c821b3 | ||
|
|
227e08b7c4 | ||
|
|
16651606fb | ||
|
|
0ea67f695d | ||
|
|
6c4386ed7d | ||
|
|
e65ba9365b | ||
|
|
e5843568dd | ||
|
|
7968151c44 | ||
|
|
9f0753f098 | ||
|
|
98d0aa5103 | ||
|
|
c1706e2c18 | ||
|
|
84e74096b7 | ||
|
|
3d96fdf1df | ||
|
|
a157605b2b | ||
|
|
ec83830577 | ||
|
|
6babeb3f21 | ||
|
|
0b9754c9ae | ||
|
|
c2f7754b0d | ||
|
|
4df519845f | ||
|
|
910bd371dd | ||
|
|
0aa6db6007 | ||
|
|
01be603780 | ||
|
|
55a3ce4def | ||
|
|
a21264eb5e | ||
|
|
8c216782e5 | ||
|
|
ed9823245b | ||
|
|
378e53eba4 | ||
|
|
66b68bc26f | ||
|
|
0785d28bb4 | ||
|
|
f43dda522d | ||
|
|
ba98142d5b | ||
|
|
43160d3058 | ||
|
|
d40a858c6a | ||
|
|
4024005c4a | ||
|
|
91d1fc7245 | ||
|
|
328efa6ff6 | ||
|
|
cb352aba68 | ||
|
|
625ca5dcf4 | ||
|
|
25d686ae5c | ||
|
|
0ab94faa95 | ||
|
|
5299a04acd | ||
|
|
f326a58bee | ||
|
|
d8d52a6e86 | ||
|
|
ba09cc4b86 | ||
|
|
5804deb8ac | ||
|
|
63b3f0199b | ||
|
|
0b0addaee4 | ||
|
|
aab6d380aa | ||
|
|
79b5c09a06 | ||
|
|
3bd4ad5874 | ||
|
|
79887dc7d5 | ||
|
|
dc8cb8e777 | ||
|
|
a8059e73c0 | ||
|
|
ee2b3e5de0 | ||
|
|
807e5ea2c1 | ||
|
|
f7b42203a4 |
13
.babelrc
Normal file
13
.babelrc
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"presets": [
|
||||
"next/babel"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"styled-components",
|
||||
{
|
||||
"ssr": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
23
.editorconfig
Normal file
23
.editorconfig
Normal file
@@ -0,0 +1,23 @@
|
||||
# EditorConfig for the node-soap library - head over to editorconfig.org to see if you editor supports this file.
|
||||
|
||||
# this is the topmost .editorconfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.xml]
|
||||
insert_final_newline = false
|
||||
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{package.json, .travis.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,2 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: kamranahmedse
|
||||
|
||||
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<!--
|
||||
Please do not remove anything written below.
|
||||
|
||||
Fill the details and open the issue. Any issue that
|
||||
doesn't have all of these filled in will be closed,
|
||||
if yours is closed reopen with everything filled in.
|
||||
-->
|
||||
|
||||
#### What roadmap is this issue about?
|
||||
|
||||
- [ ] Frontend Roadmap
|
||||
- [ ] Backend Roadmap
|
||||
- [ ] DevOps Roadmap
|
||||
- [ ] All Roadmaps
|
||||
|
||||
#### What is this issue about?
|
||||
|
||||
- [ ] Functionality of the website
|
||||
- [ ] Discussion for a pull request I would want to open.
|
||||
- [ ] Addition of a new item
|
||||
- [ ] Removal of some existing item
|
||||
- [ ] Changing in arrangement
|
||||
- [ ] General suggestion
|
||||
- [ ] Sharing an Idea
|
||||
- [ ] Something else
|
||||
|
||||
#### Please acknowledge the below listed
|
||||
|
||||
- [ ] This is not a duplicate issue. I have searched and there is no existing issue for this.
|
||||
- [ ] I understand that these roadmaps are highly opinionated. The purpose is to not to include everything out there in these roadmaps but to have everything that is most relevant today comparing to the other options listed.
|
||||
- [ ] I have read the [contribution docs](../contributing) before opening this issue.
|
||||
|
||||
|
||||
#### Enter the details about the issue here
|
||||
|
||||
<!-- Please enter the issue details here -->
|
||||
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#### What roadmap does this PR target?
|
||||
|
||||
- [ ] Code Change
|
||||
- [ ] Frontend Roadmap
|
||||
- [ ] Backend Roadmap
|
||||
- [ ] DevOps Roadmap
|
||||
- [ ] All Roadmaps
|
||||
- [ ] Guides
|
||||
|
||||
#### Please acknowledge the items listed below
|
||||
|
||||
- [ ] I have discussed this contribution and got a go-ahead in an issue before opening this pull request.
|
||||
- [ ] This is not a duplicate issue. I have searched and there is no existing issue for this.
|
||||
- [ ] I understand that these roadmaps are highly opinionated. The purpose is to not to include everything out there in these roadmaps but to have everything that is most relevant today comparing to the other options listed.
|
||||
- [ ] I have read the [contribution docs](../contributing) before opening this PR.
|
||||
|
||||
#### Enter the details about the contribution
|
||||
|
||||
<!-- Enter the details here -->
|
||||
25
.github/workflows/deploy.yml
vendored
25
.github/workflows/deploy.yml
vendored
@@ -1,34 +1,29 @@
|
||||
name: Deployment to GH Pages
|
||||
name: Deployment to GitHub Pages
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
env:
|
||||
ROADMAP_GA_SECRET: ${{ secrets.GA_SECRET }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PAT: ${{ secrets.PAT }}
|
||||
CI: true
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 18
|
||||
- run: git config --global url."https://${{ secrets.PAT }}@github.com/".insteadOf ssh://git@github.com/
|
||||
- uses: pnpm/action-setup@v2.2.2
|
||||
with:
|
||||
version: 7.13.4
|
||||
node-version: 12
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
pnpm install
|
||||
- name: Generate meta and build
|
||||
npm install -g yarn
|
||||
yarn install
|
||||
- name: Generate meta and builld
|
||||
run: |
|
||||
npm run build
|
||||
touch ./dist/.nojekyll
|
||||
echo 'roadmap.sh' > ./dist/CNAME
|
||||
- name: Deploy to GH Pages
|
||||
yarn meta
|
||||
yarn build
|
||||
- name: Deploy to GitHub Pages
|
||||
run: |
|
||||
git config user.email "kamranahmed.se@gmail.com"
|
||||
git config user.name "Kamran Ahmed"
|
||||
|
||||
38
.github/workflows/update-deps.yml
vendored
38
.github/workflows/update-deps.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Update dependencies
|
||||
|
||||
on:
|
||||
workflow_dispatch: # allow manual run
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # every sunday at midnight
|
||||
|
||||
jobs:
|
||||
upgrade-deps:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: pnpm/action-setup@v2.2.2
|
||||
with:
|
||||
version: 7.13.4
|
||||
- name: Upgrade dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
npm run upgrade
|
||||
pnpm install --lockfile-only
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
delete-branch: false
|
||||
branch: "update-deps"
|
||||
base: "master"
|
||||
labels: |
|
||||
dependencies
|
||||
automated pr
|
||||
reviewers: kamranahmedse
|
||||
commit-message: "chore: update dependencies to latest"
|
||||
title: "Upgrade dependencies to latest"
|
||||
body: |
|
||||
Updates all dependencies to latest versions.
|
||||
Please review the changes and merge if everything looks good.
|
||||
38
.gitignore
vendored
38
.gitignore
vendored
@@ -1,26 +1,14 @@
|
||||
# build output
|
||||
dist/
|
||||
.output/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
bin/developer-roadmap
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
_*
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
tests-examples
|
||||
.idea
|
||||
.next
|
||||
out
|
||||
.env
|
||||
build
|
||||
node_modules
|
||||
yarn-error.log
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
config/*.json
|
||||
!config/dev.json
|
||||
|
||||
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -73,4 +73,4 @@ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.ht
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -1,3 +1,6 @@
|
||||
The codebase in this repository is covered under BSD 4-Clause license and
|
||||
the content with CC BY-NC-ND 3.0 with additional clauses
|
||||
|
||||
Everything including text and images in this project are protected by the copyright laws.
|
||||
You are allowed to use this material for personal use but are not allowed to use it for
|
||||
any other purpose including publishing the images, the project files or the content in the
|
||||
@@ -5,11 +8,9 @@ images in any form either digital, non-digital, textual, graphical or written fo
|
||||
You are allowed to share the links to the repository or the website roadmap.sh but not
|
||||
the content for any sort of usage that involves the content of this repository taken out
|
||||
of the repository and be shared from any other medium including but not limited to blog
|
||||
posts, articles, newsletters, you must get prior consent from the understated. These
|
||||
conditions do not apply to the readonly GitHub forks created using the Fork button on
|
||||
GitHub with the whole purpose of contributing to the project.
|
||||
posts, articles, newsletters, you must get prior consent from the understated.
|
||||
|
||||
Copyright © 2023 Kamran Ahmed <kamranahmed.se@gmail.com>
|
||||
Copyright © 2021 Kamran Ahmed <kamranahmed.se@gmail.com>
|
||||
|
||||
Please note that I am really flexible with allowing the usage of the content in this
|
||||
repository. If you reach out to me with a brief detail of why and how you would like
|
||||
@@ -1,39 +0,0 @@
|
||||
// https://astro.build/config
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
import compress from 'astro-compress';
|
||||
import { defineConfig } from 'astro/config';
|
||||
import rehypeExternalLinks from 'rehype-external-links';
|
||||
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://roadmap.sh',
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
theme: 'dracula'
|
||||
},
|
||||
rehypePlugins: [
|
||||
[
|
||||
rehypeExternalLinks,
|
||||
{
|
||||
target: '_blank',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
integrations: [
|
||||
tailwind({
|
||||
config: {
|
||||
applyBaseStyles: false,
|
||||
},
|
||||
}),
|
||||
sitemap({
|
||||
filter: shouldIndexPage,
|
||||
serialize: serializeSitemap,
|
||||
}),
|
||||
compress({
|
||||
css: false,
|
||||
js: false,
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const jsonsDir = path.join(process.cwd(), 'public/jsons');
|
||||
const childJsonDirs = fs.readdirSync(jsonsDir);
|
||||
|
||||
childJsonDirs.forEach((childJsonDir) => {
|
||||
const fullChildJsonDirPath = path.join(jsonsDir, childJsonDir);
|
||||
const jsonFiles = fs.readdirSync(fullChildJsonDirPath);
|
||||
|
||||
jsonFiles.forEach((jsonFileName) => {
|
||||
console.log(`Compressing ${jsonFileName}...`);
|
||||
|
||||
const jsonFilePath = path.join(fullChildJsonDirPath, jsonFileName);
|
||||
const json = require(jsonFilePath);
|
||||
|
||||
fs.writeFileSync(jsonFilePath, JSON.stringify(json));
|
||||
});
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const CONTENT_DIR = path.join(__dirname, '../content');
|
||||
// Directory containing the roadmaps
|
||||
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/roadmaps');
|
||||
const roadmapId = process.argv[2];
|
||||
|
||||
const allowedRoadmapIds = fs.readdirSync(ROADMAP_CONTENT_DIR);
|
||||
if (!roadmapId) {
|
||||
console.error('roadmapId is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!allowedRoadmapIds.includes(roadmapId)) {
|
||||
console.error(`Invalid roadmap key ${roadmapId}`);
|
||||
console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Directory holding the roadmap content files
|
||||
const roadmapDirName = fs
|
||||
.readdirSync(ROADMAP_CONTENT_DIR)
|
||||
.find((dirName) => dirName.replace(/\d+-/, '') === roadmapId);
|
||||
|
||||
if (!roadmapDirName) {
|
||||
console.error('Roadmap directory not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const roadmapDirPath = path.join(ROADMAP_CONTENT_DIR, roadmapDirName);
|
||||
const roadmapContentDirPath = path.join(
|
||||
ROADMAP_CONTENT_DIR,
|
||||
roadmapDirName,
|
||||
'content'
|
||||
);
|
||||
|
||||
// If roadmap content already exists do not proceed as it would override the files
|
||||
if (fs.existsSync(roadmapContentDirPath)) {
|
||||
console.error(`Roadmap content already exists @ ${roadmapContentDirPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function prepareDirTree(control, dirTree, dirSortOrders) {
|
||||
// Directories are only created for groups
|
||||
if (control.typeID !== '__group__') {
|
||||
return;
|
||||
}
|
||||
|
||||
// e.g. 104-testing-your-apps:other-options
|
||||
const controlName = control?.properties?.controlName || '';
|
||||
// e.g. 104
|
||||
const sortOrder = controlName.match(/^\d+/)?.[0];
|
||||
|
||||
// No directory for a group without control name
|
||||
if (!controlName || !sortOrder) {
|
||||
return;
|
||||
}
|
||||
|
||||
// e.g. testing-your-apps:other-options
|
||||
const controlNameWithoutSortOrder = controlName.replace(/^\d+-/, '');
|
||||
// e.g. ['testing-your-apps', 'other-options']
|
||||
const dirParts = controlNameWithoutSortOrder.split(':');
|
||||
|
||||
// Nest the dir path in the dirTree
|
||||
let currDirTree = dirTree;
|
||||
dirParts.forEach((dirPart) => {
|
||||
currDirTree[dirPart] = currDirTree[dirPart] || {};
|
||||
currDirTree = currDirTree[dirPart];
|
||||
});
|
||||
|
||||
dirSortOrders[controlNameWithoutSortOrder] = Number(sortOrder);
|
||||
|
||||
const childrenControls = control.children.controls.control;
|
||||
// No more children
|
||||
if (childrenControls.length) {
|
||||
childrenControls.forEach((childControl) => {
|
||||
prepareDirTree(childControl, dirTree, dirSortOrders);
|
||||
});
|
||||
}
|
||||
|
||||
return { dirTree, dirSortOrders };
|
||||
}
|
||||
|
||||
const roadmap = require(path.join(__dirname, `../public/jsons/roadmaps/${roadmapId}`));
|
||||
const controls = roadmap.mockup.controls.control;
|
||||
|
||||
// Prepare the dir tree that we will be creating and also calculate the sort orders
|
||||
const dirTree = {};
|
||||
const dirSortOrders = {};
|
||||
|
||||
controls.forEach((control) => {
|
||||
prepareDirTree(control, dirTree, dirSortOrders);
|
||||
});
|
||||
|
||||
/**
|
||||
* @param parentDir Parent directory in which directory is to be created
|
||||
* @param dirTree Nested dir tree to be created
|
||||
* @param sortOrders Mapping from groupName to sort order
|
||||
* @param filePaths The mapping from groupName to file path
|
||||
*/
|
||||
function createDirTree(parentDir, dirTree, sortOrders, filePaths = {}) {
|
||||
const childrenDirNames = Object.keys(dirTree);
|
||||
const hasChildren = childrenDirNames.length !== 0;
|
||||
|
||||
// @todo write test for this, yolo for now
|
||||
const groupName = parentDir
|
||||
.replace(roadmapContentDirPath, '') // Remove base dir path
|
||||
.replace(/(^\/)|(\/$)/g, '') // Remove trailing slashes
|
||||
.replace(/(^\d+?-)/g, '') // Remove sorting information
|
||||
.replaceAll('/', ':') // Replace slashes with `:`
|
||||
.replace(/:\d+-/, ':');
|
||||
|
||||
const humanizedGroupName = groupName
|
||||
.split(':')
|
||||
.pop()
|
||||
?.replaceAll('-', ' ')
|
||||
.replace(/^\w/, ($0) => $0.toUpperCase());
|
||||
|
||||
const sortOrder = sortOrders[groupName] || '';
|
||||
|
||||
// Attach sorting information to dirname
|
||||
// e.g. /roadmaps/100-frontend/content/internet
|
||||
// ———> /roadmaps/100-frontend/content/103-internet
|
||||
if (sortOrder) {
|
||||
parentDir = parentDir.replace(/(.+?)([^\/]+)?$/, `$1${sortOrder}-$2`);
|
||||
}
|
||||
|
||||
// If no children, create a file for this under the parent directory
|
||||
if (!hasChildren) {
|
||||
let fileName = `${parentDir}.md`;
|
||||
fs.writeFileSync(fileName, `# ${humanizedGroupName}`);
|
||||
|
||||
filePaths[groupName || 'home'] = fileName.replace(CONTENT_DIR, '');
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
// There *are* children, so create the parent as a directory
|
||||
// and create `index.md` as the content file for this
|
||||
fs.mkdirSync(parentDir);
|
||||
|
||||
let readmeFilePath = path.join(parentDir, 'index.md');
|
||||
fs.writeFileSync(readmeFilePath, `# ${humanizedGroupName}`);
|
||||
|
||||
filePaths[groupName || 'home'] = readmeFilePath.replace(CONTENT_DIR, '');
|
||||
|
||||
// For each of the directory names, create a
|
||||
// directory inside the given directory
|
||||
childrenDirNames.forEach((dirName) => {
|
||||
createDirTree(
|
||||
path.join(parentDir, dirName),
|
||||
dirTree[dirName],
|
||||
dirSortOrders,
|
||||
filePaths
|
||||
);
|
||||
});
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
// Create directories and get back the paths for created directories
|
||||
createDirTree(roadmapContentDirPath, dirTree, dirSortOrders);
|
||||
console.log('Created roadmap content directory structure');
|
||||
@@ -1,44 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const roadmapId = process.argv[2];
|
||||
if (!roadmapId) {
|
||||
console.error('Error: roadmapId is required');
|
||||
}
|
||||
|
||||
const fullPath = path.join(__dirname, `../src/roadmaps/${roadmapId}`);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
console.error(`Error: path not found: ${fullPath}!`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function readFiles(folderPath) {
|
||||
const stats = fs.lstatSync(folderPath);
|
||||
|
||||
if (stats.isFile()) {
|
||||
return [folderPath];
|
||||
}
|
||||
|
||||
const folderContent = fs.readdirSync(folderPath);
|
||||
let files = [];
|
||||
|
||||
for (const file of folderContent) {
|
||||
const filePath = path.join(folderPath, file);
|
||||
|
||||
files = [...files, ...readFiles(filePath)];
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
const files = readFiles(fullPath);
|
||||
let allLinks = [];
|
||||
|
||||
files.forEach((file) => {
|
||||
const fileContent = fs.readFileSync(file, 'utf-8');
|
||||
const matches = [...fileContent.matchAll(/\[[^\]]+]\((https?:\/\/[^)]+)\)/g)];
|
||||
|
||||
allLinks = [...allLinks, ...matches.map((match) => match[1])];
|
||||
});
|
||||
|
||||
allLinks.map((link) => console.log(link));
|
||||
22
components/about-header/index.js
Normal file
22
components/about-header/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { AboutHeaderWrap } from './style';
|
||||
import siteConfig from "content/site";
|
||||
|
||||
const AboutHeader = () => (
|
||||
<AboutHeaderWrap>
|
||||
<div className="container container-small">
|
||||
<div className="author-info">
|
||||
<img className='author-img d-none d-sm-none d-md-block d-lg-block d-xl-block' src="/kamran.jpeg" />
|
||||
<div className="author-msg">
|
||||
<h1>Hello, I'm Kamran Ahmed.</h1>
|
||||
<p>I created <span className='flow-black'>roadmap.sh</span> to help developers find their path if they are confused and help them grow in their career.</p>
|
||||
<div className="author-links">
|
||||
<a href={`https://twitter.com/${siteConfig.twitter}`} target="_blank">@kamranahmedse</a>
|
||||
<a href="mailto:kamran@roadmap.sh">kamran@roadmap.sh</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AboutHeaderWrap>
|
||||
);
|
||||
|
||||
export default AboutHeader;
|
||||
49
components/about-header/style.js
Normal file
49
components/about-header/style.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const AboutHeaderWrap = styled.div`
|
||||
text-align: left;
|
||||
padding: 70px 0;
|
||||
margin: 0 auto;
|
||||
|
||||
.author-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
margin-bottom: 0;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.author-img {
|
||||
height: 160px;
|
||||
margin-right: 25px;
|
||||
margin-top: 8px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.author-links a {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
background: #2d2d2d;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
font-weight: 500;
|
||||
margin-top: 12px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
54
components/badges/index.js
Normal file
54
components/badges/index.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BadgesList = styled.p`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
export const PrimaryBadge = styled.span`
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
background: #2929ff;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
text-transform: uppercase;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
|
||||
svg {
|
||||
height: 11px !important;
|
||||
margin-right: 5px;
|
||||
|
||||
&.fa-clock {
|
||||
height: 10px !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SecondaryBadge = styled(PrimaryBadge)`
|
||||
background: #696969;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
export const InfoBadge = styled(PrimaryBadge)`
|
||||
background: #039640;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
export const DarkBadge = styled(PrimaryBadge)`
|
||||
background: #101010;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
export const BadgeLink = styled.a`
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration:none;
|
||||
}
|
||||
`;
|
||||
133
components/detailed-roadmap/index.js
Normal file
133
components/detailed-roadmap/index.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faTwitterSquare, faRedditSquare, faGithubSquare } from '@fortawesome/free-brands-svg-icons'
|
||||
import {
|
||||
PageHeader,
|
||||
RoadmapMeta,
|
||||
ShareRoadmap,
|
||||
Sidebar,
|
||||
Summary,
|
||||
SummaryContainer,
|
||||
MobileNavHeader,
|
||||
SidebarButton,
|
||||
MobileSidebar,
|
||||
MobileSidebarWrap,
|
||||
DesktopSidebarWrap,
|
||||
PageTitle,
|
||||
PageDetail
|
||||
} from './style';
|
||||
import { faBars } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getFacebookShareUrl } from 'lib/url';
|
||||
import { ShareIcon } from 'components/share-icon';
|
||||
import { getRedditShareUrl, getTwitterShareUrl } from 'lib/url';
|
||||
import siteConfig from "content/site";
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const DetailedRoadmap = ({ roadmap }) => {
|
||||
const [menuActive, setMenuState] = useState(false);
|
||||
const {
|
||||
sidebar = {},
|
||||
page: currentPage = {},
|
||||
author = {}
|
||||
} = roadmap;
|
||||
|
||||
const roadmapPages = Object.keys(sidebar || {}).map((groupTitle, groupCounter) => {
|
||||
if (groupTitle.startsWith('_')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @todo remove it after completing the frontend roadmap
|
||||
const isInProgress = groupCounter !== 0;
|
||||
|
||||
return (
|
||||
<div className={`links-group ${isInProgress ? 'in-progress' : ''}`} key={groupTitle}>
|
||||
<h3>
|
||||
{ groupTitle }
|
||||
{ isInProgress && <span className='badge badge-warning progress-badge'>In Progress</span> }
|
||||
</h3>
|
||||
<ul>
|
||||
{ sidebar[groupTitle].map(page => {
|
||||
const isActivePage = page.url === currentPage.url;
|
||||
// e.g. /frontend should mark `/frontend/landscape` as active
|
||||
const isSummaryPage = page.url === `${currentPage.url}/summary`;
|
||||
|
||||
return (
|
||||
<li className={classNames({ active: isActivePage || isSummaryPage })} key={page.url}>
|
||||
<a href={ page.url }>
|
||||
<span className="bullet"></span>
|
||||
{ page.title }
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}) }
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const filePath = currentPage.path.replace(/^\//, '');
|
||||
const RoadmapContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<PageHeader className="border-top border-bottom text-center text-md-left">
|
||||
<div className="container d-flex align-items-center flex-column flex-md-row">
|
||||
<RoadmapMeta>
|
||||
<h3>{ roadmap.title }</h3>
|
||||
<p>
|
||||
Roadmap contributed by <a href={ author.url } target="_blank">{ author.name }</a>
|
||||
{ roadmap.contributorsCount > 1 && ` and <a href="${roadmap.contributorsUrl}">${roadmap.contributorsCount} others</a>`}
|
||||
</p>
|
||||
</RoadmapMeta>
|
||||
<ShareRoadmap className="mt-2 mt-md-0">
|
||||
<ShareIcon href={ siteConfig.url.repo } target="_blank">
|
||||
<FontAwesomeIcon icon={ faGithubSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getFacebookShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faFacebookSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getTwitterShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faTwitterSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getRedditShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faRedditSquare } />
|
||||
</ShareIcon>
|
||||
</ShareRoadmap>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<MobileNavHeader className="border-bottom d-block d-md-none">
|
||||
<div className="container">
|
||||
<SidebarButton onClick={() => setMenuState((prevMenuActive) => !prevMenuActive)}>
|
||||
<FontAwesomeIcon icon={ faBars } />
|
||||
{ currentPage.title }
|
||||
</SidebarButton>
|
||||
</div>
|
||||
<MobileSidebarWrap className={classNames({ visible: menuActive })}>
|
||||
<div className="container">
|
||||
<MobileSidebar>
|
||||
{ roadmapPages }
|
||||
</MobileSidebar>
|
||||
</div>
|
||||
</MobileSidebarWrap>
|
||||
</MobileNavHeader>
|
||||
|
||||
<Summary className="container">
|
||||
<DesktopSidebarWrap className="d-none d-md-block">
|
||||
<Sidebar>
|
||||
{ roadmapPages }
|
||||
</Sidebar>
|
||||
</DesktopSidebarWrap>
|
||||
<PageDetail>
|
||||
<PageTitle>{ currentPage.title }</PageTitle>
|
||||
<MdRenderer>
|
||||
<RoadmapContent />
|
||||
</MdRenderer>
|
||||
</PageDetail>
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default DetailedRoadmap;
|
||||
199
components/detailed-roadmap/style.js
Normal file
199
components/detailed-roadmap/style.js
Normal file
@@ -0,0 +1,199 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const Summary = styled.div`
|
||||
text-align: left;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PageHeader = styled.div`
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
color: #696969;
|
||||
|
||||
a {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const RoadmapMeta = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const ShareRoadmap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
margin-bottom: 0;
|
||||
& + a {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileNavHeader = styled.div`
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const SidebarButton = styled.button`
|
||||
background: transparent;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
-webkit-appearance: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
margin-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PageDetail = styled.div`
|
||||
padding: 25px 0 100px;
|
||||
`;
|
||||
|
||||
export const PageTitle = styled.h1`
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
`;
|
||||
|
||||
export const Sidebar = styled.div`
|
||||
padding-bottom: 150px;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bullet {
|
||||
display: inline-block;
|
||||
margin-right: 7px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
min-width: 7px;
|
||||
border-radius: 100%;
|
||||
background: #efefef;
|
||||
transform: translateX(-4px);
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
|
||||
.links-group {
|
||||
padding: 30px 0 10px;
|
||||
width: 260px;
|
||||
|
||||
h3 {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.links-group.in-progress {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.links-group li {
|
||||
list-style: none;
|
||||
margin: 7px 0;
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
.bullet {
|
||||
display: inline-block;
|
||||
margin-right: 12px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
min-width: 7px;
|
||||
border-radius: 100%;
|
||||
background: #efefef;
|
||||
transform: translateX(-4px);
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
&.active a {
|
||||
color: #101010;
|
||||
}
|
||||
|
||||
&.active .bullet {
|
||||
background: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DesktopSidebarWrap = styled.div`
|
||||
border-left: 1px solid #efefef;
|
||||
|
||||
${Sidebar} {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileSidebarWrap = styled.div`
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
background: white;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
bottom: 100%;
|
||||
overflow-y: scroll;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0 10px 20px;
|
||||
top: calc(100% + 1px);
|
||||
transition: bottom 0.5s ease 0s;
|
||||
|
||||
&.visible {
|
||||
bottom: -50vh;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileSidebar = styled(Sidebar)`
|
||||
border-left: 1px solid #efefef;
|
||||
margin-left: 12px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.links-group {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.progress-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
`;
|
||||
16
components/faq-list/index.js
Normal file
16
components/faq-list/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { FaqContainer } from './style';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const AboutPage = require(`../../content/pages/about.md`).default;
|
||||
|
||||
const FaqList = () => (
|
||||
<FaqContainer className='border-top bg-light'>
|
||||
<div className="container container-small">
|
||||
<MdRenderer>
|
||||
<AboutPage />
|
||||
</MdRenderer>
|
||||
</div>
|
||||
</FaqContainer>
|
||||
);
|
||||
|
||||
export default FaqList;
|
||||
25
components/faq-list/style.js
Normal file
25
components/faq-list/style.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FaqContainer = styled.div`
|
||||
padding: 40px 0;
|
||||
|
||||
h4 {
|
||||
margin-top: 30px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
}
|
||||
`;
|
||||
25
components/featured-content/guides.js
Normal file
25
components/featured-content/guides.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import Link from 'next/link';
|
||||
import { FeaturedContentWrap } from './style';
|
||||
import FeaturedGuide from 'components/featured-guide';
|
||||
import { getFeaturedGuides } from 'lib/guide';
|
||||
|
||||
const FeaturedGuides = () => (
|
||||
<FeaturedContentWrap className="featured-content-wrap">
|
||||
<div className="container">
|
||||
<p className='border-through featured-separator'>
|
||||
<span>
|
||||
Guides mostly visited by the community
|
||||
<a href="/guides" className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Guides →</a>
|
||||
</span>
|
||||
</p>
|
||||
<div className="swim-lane row">
|
||||
{ getFeaturedGuides()
|
||||
.map(guide => (
|
||||
<FeaturedGuide guide={ guide } key={ guide.url } />
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
</FeaturedContentWrap>
|
||||
);
|
||||
|
||||
export default FeaturedGuides;
|
||||
16
components/featured-content/index.js
Normal file
16
components/featured-content/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { FeaturedWrap } from './style';
|
||||
import FeaturedGuides from './guides';
|
||||
import FeaturedRoadmaps from './roadmaps';
|
||||
|
||||
const FeaturedContent = (props) => (
|
||||
<FeaturedWrap className="border-top bg-light">
|
||||
<FeaturedRoadmaps />
|
||||
<FeaturedGuides />
|
||||
</FeaturedWrap>
|
||||
);
|
||||
|
||||
FeaturedContent.defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
export default FeaturedContent;
|
||||
27
components/featured-content/roadmaps.js
Normal file
27
components/featured-content/roadmaps.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { FeaturedContentWrap } from './style';
|
||||
import roadmaps from 'content/roadmaps';
|
||||
import FeaturedRoadmap from 'components/featured-roadmap';
|
||||
|
||||
const FeaturedRoadmaps = () => (
|
||||
<FeaturedContentWrap className="featured-content-wrap">
|
||||
<div className="container">
|
||||
<div className="featured-head">
|
||||
<p className="border-through featured-separator">
|
||||
<span>
|
||||
Roadmaps mostly visited by the community
|
||||
<a href='/roadmaps' className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Roadmaps →</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="swim-lane row">
|
||||
{ roadmaps
|
||||
.filter(({ featured }) => featured)
|
||||
.map(roadmap => (
|
||||
<FeaturedRoadmap roadmap={ roadmap } key={ roadmap.url } />
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
</FeaturedContentWrap>
|
||||
);
|
||||
|
||||
export default FeaturedRoadmaps;
|
||||
110
components/featured-content/style.js
Normal file
110
components/featured-content/style.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FeaturedWrap = styled.div`
|
||||
padding: 30px 0 50px;
|
||||
`;
|
||||
|
||||
export const FeaturedContentWrap = styled.div`
|
||||
.featured-head {
|
||||
text-align: center;
|
||||
display: block;
|
||||
|
||||
h3 {
|
||||
font-weight: 700;
|
||||
font-size: 35px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.featured-separator {
|
||||
font-size: 16px;
|
||||
margin-bottom: 40px;
|
||||
margin-top: 25px;
|
||||
color: #999;
|
||||
|
||||
span {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
a {
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 3px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.swim-lane {
|
||||
.featured-block {
|
||||
}
|
||||
}
|
||||
|
||||
.guide-item {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
padding: 15px 10px;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
font-size: 15px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.journey-block {
|
||||
a {
|
||||
border: 1px solid #f7f7f7;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
cursor: pointer;
|
||||
margin-bottom: 32px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
|
||||
img {
|
||||
filter: grayscale(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.journey-meta {
|
||||
padding: 18px 25px 20px;
|
||||
|
||||
h4 {
|
||||
line-height: 27px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0;
|
||||
font-size: 18px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
color: #999999;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
filter: grayscale(1);
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
25
components/featured-guide/index.js
Normal file
25
components/featured-guide/index.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import formatDate from 'date-fns/format';
|
||||
|
||||
import { Author, AuthorImage, AuthorName, BlockLink, BlockMeta, BlockSubtitle, BlockTitle, PublishDate } from './style';
|
||||
import { findByUsername } from 'lib/author';
|
||||
|
||||
const FeaturedGuide = ({ guide }) => {
|
||||
const author = findByUsername(guide.author) || {};
|
||||
return (
|
||||
<div className='col-xl-4 col-lg-6 col-md-6 col-sm-12 col-12 grid-item-container'>
|
||||
<BlockLink href={guide.url}>
|
||||
<BlockTitle>{guide.title}</BlockTitle>
|
||||
<BlockSubtitle>{guide.featuredDescription || guide.description}</BlockSubtitle>
|
||||
<BlockMeta>
|
||||
<Author>
|
||||
<AuthorImage src={author.picture} />
|
||||
<AuthorName>{author.name}</AuthorName>
|
||||
</Author>
|
||||
<PublishDate>{formatDate(new Date(guide.createdAt), 'MMMM d, yyyy')}</PublishDate>
|
||||
</BlockMeta>
|
||||
</BlockLink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeaturedGuide;
|
||||
65
components/featured-guide/style.js
Normal file
65
components/featured-guide/style.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BlockLink = styled.a`
|
||||
border: 1px solid #f7f7f7;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
padding: 25px 25px 22px;
|
||||
border-radius: 10px;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
cursor: pointer;
|
||||
margin-bottom: 32px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const BlockTitle = styled.h4`
|
||||
line-height: 27px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const BlockSubtitle = styled.p`
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
color: #999999;
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
export const BlockMeta = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 19px;
|
||||
`;
|
||||
|
||||
export const PublishDate = styled.time`
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
`;
|
||||
|
||||
export const Author = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
color: #999;
|
||||
`;
|
||||
|
||||
export const AuthorImage = styled.img`
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
border-radius: 100%;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
export const AuthorName = styled.div``;
|
||||
12
components/featured-roadmap/index.js
Normal file
12
components/featured-roadmap/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { BlockLink, BlockSubtitle, BlockTitle } from './style';
|
||||
|
||||
const FeaturedRoadmap = ({ roadmap }) => (
|
||||
<div className='col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container'>
|
||||
<BlockLink href={roadmap.url}>
|
||||
<BlockTitle>{roadmap.title}</BlockTitle>
|
||||
<BlockSubtitle>{roadmap.featuredDescription || roadmap.description}</BlockSubtitle>
|
||||
</BlockLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default FeaturedRoadmap;
|
||||
38
components/featured-roadmap/style.js
Normal file
38
components/featured-roadmap/style.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BlockLink = styled.a`
|
||||
border: 1px solid #f7f7f7;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
padding: 26px 25px 25px;
|
||||
border-radius: 10px;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
cursor: pointer;
|
||||
margin-bottom: 32px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const BlockTitle = styled.h4`
|
||||
line-height: 27px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const BlockSubtitle = styled.p`
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
color: #999999;
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
23
components/guide-body/index.js
Normal file
23
components/guide-body/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import MdRenderer from 'components/md-renderer'
|
||||
import SharePage from 'components/share-page';
|
||||
import { GuideBodyWrap } from './style';
|
||||
|
||||
const GuideBody = ({ guide }) => {
|
||||
const GuideContent = require(`../../content/guides/${guide.fileName}.md`).default;
|
||||
return (
|
||||
<GuideBodyWrap>
|
||||
<MdRenderer>
|
||||
<GuideContent />
|
||||
{
|
||||
guide.author && <SharePage
|
||||
title={ guide.title }
|
||||
url={ guide.url }
|
||||
twitterUsername={ guide.author.twitter }
|
||||
/>
|
||||
}
|
||||
</MdRenderer>
|
||||
</GuideBodyWrap>
|
||||
);
|
||||
};
|
||||
|
||||
export default GuideBody;
|
||||
13
components/guide-body/style.js
Normal file
13
components/guide-body/style.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const GuideBodyWrap = styled.div`
|
||||
position: relative;
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
min-height: 300px;
|
||||
|
||||
p:first-child, h1, h2, h3, h4, h5, h6, img, blockquote {
|
||||
margin-top: 0;
|
||||
}
|
||||
`;
|
||||
83
components/guide-footer/index.js
Normal file
83
components/guide-footer/index.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faGithub, faHackerNewsSquare, faRedditSquare, faTwitter, faTwitterSquare } from '@fortawesome/free-brands-svg-icons'
|
||||
import { getContributionUrl } from "lib/guide";
|
||||
import {
|
||||
getTwitterUrl,
|
||||
getTwitterShareUrl,
|
||||
getFacebookShareUrl,
|
||||
getRedditShareUrl,
|
||||
getHnShareUrl
|
||||
} from "lib/url";
|
||||
import {
|
||||
AuthorBio,
|
||||
AuthorImg,
|
||||
AuthorInfoWrap,
|
||||
AuthorMeta,
|
||||
ContributeIcon,
|
||||
FooterBg,
|
||||
FooterContainer,
|
||||
FooterWrap,
|
||||
ShareIcons,
|
||||
ShareWrap
|
||||
} from './style';
|
||||
|
||||
|
||||
const GuideFooter = ({
|
||||
guide,
|
||||
guide: {
|
||||
author = {}
|
||||
} = {}
|
||||
}) => (
|
||||
<FooterWrap>
|
||||
<FooterBg className="border-top">
|
||||
<FooterContainer>
|
||||
<ShareWrap>
|
||||
<ContributeIcon>
|
||||
<a href={ getContributionUrl(guide) } target="_blank">
|
||||
<span className="d-none d-sm-none d-md-inline d-lg-inline d-xl-inline">Improve this Guide </span>
|
||||
<span className="d-inline d-sm-inline d-md-none d-lg-none d-xl-none">Contribute </span>
|
||||
<FontAwesomeIcon icon={faGithub}/>
|
||||
</a>
|
||||
</ContributeIcon>
|
||||
<ContributeIcon hasMargins>
|
||||
<a href={ getTwitterUrl(author.twitter) } target="_blank">
|
||||
<span className="d-none d-sm-none d-md-inline d-lg-inline d-xl-inline">Follow the author </span>
|
||||
<span className="d-inline d-sm-inline d-md-none d-lg-none d-xl-none">Author </span>
|
||||
<FontAwesomeIcon icon={faTwitter}/>
|
||||
</a>
|
||||
</ContributeIcon>
|
||||
<ShareIcons>
|
||||
<span className="d-none d-sm-none d-md-none d-lg-inline d-xl-inline">Help spread the word</span>
|
||||
<span className="d-inline d-sm-inline d-md-inline d-lg-none d-xl-none">Share</span>
|
||||
<a href={ getTwitterShareUrl({ text: `${guide.title} by @${author.twitter}`, url: guide.url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faTwitterSquare}/>
|
||||
</a>
|
||||
<a href={ getFacebookShareUrl({ text: guide.title, url: guide.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={faFacebookSquare}/>
|
||||
</a>
|
||||
<a href={ getRedditShareUrl({ text: guide.title, url: guide.url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faRedditSquare}/>
|
||||
</a>
|
||||
<a href={ getHnShareUrl({ text: guide.title, url: guide.url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faHackerNewsSquare}/>
|
||||
</a>
|
||||
</ShareIcons>
|
||||
</ShareWrap>
|
||||
</FooterContainer>
|
||||
</FooterBg>
|
||||
|
||||
<FooterBg className="border-top">
|
||||
<FooterContainer>
|
||||
<AuthorInfoWrap>
|
||||
<AuthorImg src={ author.picture } alt={ author.name }/>
|
||||
<AuthorMeta>
|
||||
<h4><a href={ getTwitterUrl(author.twitter) } target="_blank">{ author.name }</a></h4>
|
||||
<AuthorBio>{ author.bio }</AuthorBio>
|
||||
</AuthorMeta>
|
||||
</AuthorInfoWrap>
|
||||
</FooterContainer>
|
||||
</FooterBg>
|
||||
</FooterWrap>
|
||||
);
|
||||
|
||||
export default GuideFooter;
|
||||
123
components/guide-footer/style.js
Normal file
123
components/guide-footer/style.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FooterWrap = styled.div`
|
||||
display: block;
|
||||
margin-top: 50px;
|
||||
`;
|
||||
|
||||
export const FooterContainer = styled.div`
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
`;
|
||||
|
||||
export const FooterBg = styled.div`
|
||||
`;
|
||||
|
||||
export const ShareWrap = styled.div`
|
||||
padding: 17px 0px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #101010;
|
||||
|
||||
svg {
|
||||
height: 18px !important;
|
||||
width: 18px !important;
|
||||
color: #757575;
|
||||
margin-left: 7px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
export const ContributeIcon = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: ${props => props.hasMargins ? '0 30px' : '0'};
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #757575;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const ShareIcons = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #757575;
|
||||
font-size: 14px;
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorInfoWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 40px 0;
|
||||
|
||||
h4 {
|
||||
position: relative;
|
||||
font-size: 22px;
|
||||
margin: 10px 0;
|
||||
line-height: 17px;
|
||||
|
||||
a {
|
||||
font-weight: 700;
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
|
||||
.author-description {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorBio = styled.p`
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
color: #757575;
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorImg = styled.img`
|
||||
border-radius: 100%;
|
||||
height: 100px !important;
|
||||
width: 100px !important;
|
||||
margin-right: 22px;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
export const AuthorMeta = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
`;
|
||||
57
components/guide-header/index.js
Normal file
57
components/guide-header/index.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import formatDate from 'date-fns/format'
|
||||
import {
|
||||
ActionItems,
|
||||
AuthorImage,
|
||||
EditGuide,
|
||||
GuideAuthor,
|
||||
GuideDate,
|
||||
GuideMeta,
|
||||
GuideSubtitle,
|
||||
GuideTitle,
|
||||
HeaderWrap,
|
||||
} from './style';
|
||||
import { getContributionUrl } from "lib/guide";
|
||||
import { getTwitterUrl } from "lib/url";
|
||||
import { faClock, faEnvelope, faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
||||
import { BadgeLink, BadgesList, PrimaryBadge, SecondaryBadge, DarkBadge } from 'components/badges';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
const GuideHeader = ({
|
||||
guide,
|
||||
guide: {
|
||||
author = {}
|
||||
} = {}
|
||||
}) => (
|
||||
<HeaderWrap className="border-bottom">
|
||||
<GuideMeta>
|
||||
<GuideAuthor href={ getTwitterUrl(author.twitter) } target="_blank">
|
||||
<AuthorImage src={ author.picture } />
|
||||
{ author.name }
|
||||
</GuideAuthor>
|
||||
·
|
||||
<GuideDate>{ formatDate(new Date(guide.createdAt), 'EEEE, MMMM d yyyy') }</GuideDate>
|
||||
·
|
||||
<EditGuide href={ getContributionUrl(guide) } target="_blank">Improve this Guide</EditGuide>
|
||||
</GuideMeta>
|
||||
<GuideTitle>{ guide.title }</GuideTitle>
|
||||
<GuideSubtitle>{ guide.description }</GuideSubtitle>
|
||||
<ActionItems>
|
||||
<BadgesList className="mt-4">
|
||||
<BadgeLink href="/guides">
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon icon={faArrowLeft}/>
|
||||
Other Guides
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
<BadgeLink href="/signup">
|
||||
<PrimaryBadge>
|
||||
<FontAwesomeIcon icon={faEnvelope}/>
|
||||
Send me Updates
|
||||
</PrimaryBadge>
|
||||
</BadgeLink>
|
||||
</BadgesList>
|
||||
</ActionItems>
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default GuideHeader;
|
||||
54
components/guide-header/style.js
Normal file
54
components/guide-header/style.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
padding: 80px 15px 45px;
|
||||
text-align: center;
|
||||
margin-bottom: 35px;
|
||||
`;
|
||||
|
||||
export const GuideTitle = styled.h1`
|
||||
font-weight: 700;
|
||||
font-size: 46px;
|
||||
margin: 12px 0;
|
||||
`;
|
||||
|
||||
export const GuideSubtitle = styled.p`
|
||||
margin-bottom: 0;
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
`;
|
||||
|
||||
export const GuideMeta = styled.p`
|
||||
margin-bottom: 0;
|
||||
color: #757575;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
`;
|
||||
|
||||
export const GuideDate = styled.span`
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
`;
|
||||
|
||||
export const GuideAuthor = styled.a`
|
||||
margin-right: 7px;
|
||||
font-weight: 500;
|
||||
color: #101010;
|
||||
|
||||
&:hover {
|
||||
color: #101010;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorImage = styled.img`
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 100%;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
export const EditGuide = styled.a`
|
||||
margin-left: 7px;
|
||||
`;
|
||||
|
||||
export const ActionItems = styled.div``;
|
||||
74
components/helmet/index.js
Normal file
74
components/helmet/index.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import NextHead from 'next/head';
|
||||
import siteConfig from 'content/site';
|
||||
|
||||
const prepareTitle = (givenTitle) => {
|
||||
return givenTitle || siteConfig.title;
|
||||
};
|
||||
|
||||
const prepareDescription = (givenDescription) => {
|
||||
return givenDescription || siteConfig.description;
|
||||
};
|
||||
|
||||
// noinspection JSUnresolvedLibraryURL
|
||||
const Helmet = (props) => (
|
||||
<NextHead>
|
||||
<meta charSet='UTF-8' />
|
||||
|
||||
<title>{ prepareTitle(props.title) }</title>
|
||||
<meta name='description' content={ prepareDescription(props.description) } />
|
||||
|
||||
<meta name="author" content={ siteConfig.author } />
|
||||
<meta name="keywords" content={ props.keywords ? props.keywords.join(',') : siteConfig.keywords.join(',') } />
|
||||
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0" />
|
||||
{ props.canonical && <link rel="canonical" href={ props.canonical } /> }
|
||||
<meta httpEquiv="Content-Language" content="en" />
|
||||
|
||||
<meta property="og:title" content={ prepareTitle(props.title) } />
|
||||
<meta property="og:description" content={ prepareDescription(props.description) } />
|
||||
<meta property="og:image" content={ `${siteConfig.url.web}${siteConfig.logoSquare}` } />
|
||||
<meta property="og:url" content={ siteConfig.url.web } />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="article:publisher" content={ `https://facebook.com/${siteConfig.facebook}` } />
|
||||
<meta property="og:site_name" content={ siteConfig.name } />
|
||||
<meta property="article:author" content={ siteConfig.author } />
|
||||
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:site" content={ `@${siteConfig.twitter}` } />
|
||||
<meta name="twitter:title" content={ prepareTitle(props.title) } />
|
||||
<meta name="twitter:description" content={ prepareDescription(props.description) } />
|
||||
<meta name="twitter:image" content={ `${siteConfig.url.web}${siteConfig.logoSquare}` } />
|
||||
<meta name="twitter:image:alt" content="roadmap.sh" />
|
||||
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/manifest/apple-touch-icon.png" />
|
||||
<meta name="msapplication-TileColor" content="#101010" />
|
||||
<meta name="theme-color" content="#848a9a" />
|
||||
|
||||
<link rel="manifest" href="/manifest/manifest.json" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/manifest/icon32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/manifest/icon16.png" />
|
||||
<link rel="shortcut icon" href="/manifest/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="/manifest/favicon.ico" type="image/x-icon" />
|
||||
|
||||
{ /* Global Site Tag (gtag.js) - Google Analytics */ }
|
||||
{ process.env.GA_SECRET && (
|
||||
<>
|
||||
<script async src={ `https://www.googletagmanager.com/gtag/js?id=${process.env.GA_SECRET}` } />
|
||||
<script dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${process.env.GA_SECRET}');
|
||||
`,
|
||||
}} />
|
||||
</>
|
||||
)}
|
||||
|
||||
</NextHead>
|
||||
);
|
||||
|
||||
export default Helmet;
|
||||
13
components/hero-section/index.js
Normal file
13
components/hero-section/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Link from 'next/link';
|
||||
import { HeroSectionWrap } from './style';
|
||||
|
||||
const HeroSection = () => (
|
||||
<HeroSectionWrap>
|
||||
<div className="container">
|
||||
<h1>Developer Roadmaps</h1>
|
||||
<p>Community driven roadmaps, articles and resources for developers. <Link href="/signup"><a>Sign up</a></Link> for occasional updates on new roadmaps, updates and guides</p>
|
||||
</div>
|
||||
</HeroSectionWrap>
|
||||
);
|
||||
|
||||
export default HeroSection;
|
||||
30
components/hero-section/style.js
Normal file
30
components/hero-section/style.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeroSectionWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 70px 20px;
|
||||
margin: 0 auto;
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 70px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
margin-bottom: 0;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
`;
|
||||
15
components/icon-row-block/index.js
Normal file
15
components/icon-row-block/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import RowBlock from 'components/row-block';
|
||||
import { SubmitText, SubmitWrap } from './style';
|
||||
|
||||
const IconRowBlock = ({ url, icon, text, openExternal=false }) => (
|
||||
<RowBlock url={ url } openExternal={openExternal}>
|
||||
<SubmitWrap>
|
||||
<FontAwesomeIcon icon={ icon } />
|
||||
<SubmitText>{ text }</SubmitText>
|
||||
</SubmitWrap>
|
||||
</RowBlock>
|
||||
);
|
||||
|
||||
export default IconRowBlock;
|
||||
20
components/icon-row-block/style.js
Normal file
20
components/icon-row-block/style.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SubmitWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
height: 25px;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SubmitText = styled.p`
|
||||
margin-bottom: 0;
|
||||
color: dimgrey;
|
||||
margin-top: 8px;
|
||||
`;
|
||||
3
components/icons/facebook.svg
Normal file
3
components/icons/facebook.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="29" height="29">
|
||||
<path d="M23.2 5H5.8a.8.8 0 0 0-.8.8V23.2c0 .44.35.8.8.8h9.3v-7.13h-2.38V13.9h2.38v-2.38c0-2.45 1.55-3.66 3.74-3.66 1.05 0 1.95.08 2.2.11v2.57h-1.5c-1.2 0-1.48.57-1.48 1.4v1.96h2.97l-.6 2.97h-2.37l.05 7.12h5.1a.8.8 0 0 0 .79-.8V5.8a.8.8 0 0 0-.8-.79"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 298 B |
4
components/icons/github.svg
Normal file
4
components/icons/github.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-label="GitHub" role="img" viewBox="0 0 512 512" width="22px" height="20px">
|
||||
<rect width="512" height="512" rx="15%"/>
|
||||
<path fill="#fff" d="M335 499c14 0 12 17 12 17H165s-2-17 12-17c13 0 16-6 16-12l-1-50c-71 16-86-28-86-28-12-30-28-37-28-37-24-16 1-16 1-16 26 2 40 26 40 26 22 39 59 28 74 22 2-17 9-28 16-35-57-6-116-28-116-126 0-28 10-51 26-69-3-6-11-32 3-67 0 0 21-7 70 26 42-12 86-12 128 0 49-33 70-26 70-26 14 35 6 61 3 67 16 18 26 41 26 69 0 98-60 120-117 126 10 8 18 24 18 48l-1 70c0 6 3 12 16 12z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 565 B |
4
components/icons/link.svg
Normal file
4
components/icons/link.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 474 B |
3
components/icons/twitter.svg
Normal file
3
components/icons/twitter.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="29" height="29">
|
||||
<path d="M22.05 7.54a4.47 4.47 0 0 0-3.3-1.46 4.53 4.53 0 0 0-4.53 4.53c0 .35.04.7.08 1.05A12.9 12.9 0 0 1 5 6.89a5.1 5.1 0 0 0-.65 2.26c.03 1.6.83 2.99 2.02 3.79a4.3 4.3 0 0 1-2.02-.57v.08a4.55 4.55 0 0 0 3.63 4.44c-.4.08-.8.13-1.21.16l-.81-.08a4.54 4.54 0 0 0 4.2 3.15 9.56 9.56 0 0 1-5.66 1.94l-1.05-.08c2 1.27 4.38 2.02 6.94 2.02 8.3 0 12.86-6.9 12.84-12.85.02-.24 0-.43 0-.65a8.68 8.68 0 0 0 2.26-2.34c-.82.38-1.7.62-2.6.72a4.37 4.37 0 0 0 1.95-2.51c-.84.53-1.81.9-2.83 1.13z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 529 B |
9
components/link/badge-link.js
Normal file
9
components/link/badge-link.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export function BadgeLink({ target='_blank', variant ='success', badgeText, href, children }) {
|
||||
return (
|
||||
<p className='mb-0'>
|
||||
<a href={href} target={ target }>
|
||||
<span style={{ position: 'relative', top: '-2px'}} className={`badge badge-${variant}`}>{badgeText}</span> { children }
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
13
components/link/index.js
Normal file
13
components/link/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const StrongLink = styled.a`
|
||||
border-bottom: 2px solid currentColor;
|
||||
position: relative;
|
||||
transition: background-color 120ms;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
11
components/mark/index.js
Normal file
11
components/mark/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Mark = styled.span`
|
||||
background: #1b1e21;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 4px;
|
||||
padding: 0 6px 0 7px;
|
||||
`;
|
||||
10
components/md-renderer/index.js
Normal file
10
components/md-renderer/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import MdxComponents from './mdx-components';
|
||||
|
||||
const MdRenderer = (props) => (
|
||||
<MDXProvider components={ MdxComponents }>
|
||||
{ props.children }
|
||||
</MDXProvider>
|
||||
);
|
||||
|
||||
export default MdRenderer;
|
||||
18
components/md-renderer/mdx-components/a.js
Normal file
18
components/md-renderer/mdx-components/a.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Link = styled.a`
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const EnrichedLink = props => {
|
||||
// Is external URL or is a media URL
|
||||
const isExternalUrl = /(^http(s)?:\/\/)|(\.(png|svg|jpeg|jpg)$)/.test(props.href);
|
||||
|
||||
return (
|
||||
<Link href={ props.href } target={ isExternalUrl ? '_blank' : '_self' }>
|
||||
{ props.children }
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrichedLink;
|
||||
26
components/md-renderer/mdx-components/blockquote.js
Normal file
26
components/md-renderer/mdx-components/blockquote.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const BlockQuote = styled.blockquote`
|
||||
padding: 16px 20px;
|
||||
position: relative;
|
||||
background: #e8e8e8;
|
||||
border-radius: 5px;
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p + h4 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
|
||||
& + p {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default BlockQuote;
|
||||
77
components/md-renderer/mdx-components/heading.js
Normal file
77
components/md-renderer/mdx-components/heading.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import styled from 'styled-components';
|
||||
import Link from 'components/icons/link.svg';
|
||||
|
||||
const linkify = (Component) => {
|
||||
return (props) => {
|
||||
const text = props.children;
|
||||
const id = text.toLowerCase && text
|
||||
.toLowerCase()
|
||||
.replace(/[^\x00-\x7F]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[?!]/g, '');
|
||||
|
||||
return (
|
||||
<Component id={ id }>
|
||||
<HeaderLink href={ `#${id}` }>
|
||||
<Link />
|
||||
</HeaderLink>
|
||||
{ props.children }
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const HeaderLink = styled.a`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -25px;
|
||||
width: 25px;
|
||||
display: none;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
const H1 = styled.h1`
|
||||
position: relative;
|
||||
font-size: 42px;
|
||||
font-weight: 700;
|
||||
margin: 32px 0 10px !important;
|
||||
|
||||
&:hover ${HeaderLink} {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
const H2 = styled(H1).attrs({ as: 'h2' })`
|
||||
font-size: 32px;
|
||||
`;
|
||||
|
||||
const H3 = styled(H1).attrs({ as: 'h3' })`
|
||||
margin: 22px 0 8px;
|
||||
font-size: 30px;
|
||||
`;
|
||||
|
||||
const H4 = styled(H1).attrs({ as: 'h4' })`
|
||||
margin: 18px 0 8px;
|
||||
font-size: 24px;
|
||||
`;
|
||||
|
||||
const H5 = styled(H1).attrs({ as: 'h5' })`
|
||||
margin: 14px 0 8px;
|
||||
font-size: 18px;
|
||||
`;
|
||||
|
||||
const H6 = styled(H1).attrs({ as: 'h6' })`
|
||||
margin: 12px 0 8px;
|
||||
font-size: 18px;
|
||||
`;
|
||||
|
||||
export const Headings = {
|
||||
h1: linkify(H1),
|
||||
h2: linkify(H2),
|
||||
h3: linkify(H3),
|
||||
h4: linkify(H4),
|
||||
h5: linkify(H5),
|
||||
h6: linkify(H6),
|
||||
};
|
||||
8
components/md-renderer/mdx-components/iframe.js
Normal file
8
components/md-renderer/mdx-components/iframe.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const IFrame = styled.iframe`
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
margin: 30px auto;
|
||||
`;
|
||||
7
components/md-renderer/mdx-components/img.js
Normal file
7
components/md-renderer/mdx-components/img.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Img = styled.img`
|
||||
max-width: 100%;
|
||||
margin: 25px auto;
|
||||
display: block;
|
||||
`;
|
||||
23
components/md-renderer/mdx-components/index.js
Normal file
23
components/md-renderer/mdx-components/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import P from './p';
|
||||
import { Headings } from './heading';
|
||||
import { Pre } from './pre';
|
||||
import BlockQuote from './blockquote';
|
||||
import { Table } from './table';
|
||||
import { IFrame } from './iframe';
|
||||
import { Img } from './img';
|
||||
import EnrichedLink from './a';
|
||||
import { BadgeLink } from '../../link/badge-link';
|
||||
|
||||
const MdxComponents = {
|
||||
p: P,
|
||||
...Headings,
|
||||
pre: Pre,
|
||||
blockquote: BlockQuote,
|
||||
a: EnrichedLink,
|
||||
table: Table,
|
||||
iframe: IFrame,
|
||||
img: Img,
|
||||
BadgeLink: BadgeLink
|
||||
};
|
||||
|
||||
export default MdxComponents;
|
||||
20
components/md-renderer/mdx-components/p.js
Normal file
20
components/md-renderer/mdx-components/p.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const P = styled.p`
|
||||
color: inherit;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 26px;
|
||||
margin: 0 0 12px;
|
||||
|
||||
img + em {
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
margin: 5px 0 10px;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
export default P;
|
||||
12
components/md-renderer/mdx-components/pre.js
Normal file
12
components/md-renderer/mdx-components/pre.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Pre = styled.pre`
|
||||
margin: 25px -25px 25px -25px !important;
|
||||
padding: 20px 25px !important;
|
||||
border-radius: 10px;
|
||||
line-height: 1.5 !important;
|
||||
|
||||
code {
|
||||
background: transparent;
|
||||
}
|
||||
`;
|
||||
25
components/md-renderer/mdx-components/table.js
Normal file
25
components/md-renderer/mdx-components/table.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Table = styled.table`
|
||||
border-collapse: separate;
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
margin: 20px 0;
|
||||
|
||||
th {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
background: #FAFAFA;
|
||||
text-transform: uppercase;
|
||||
height: 40px;
|
||||
vertical-align: middle;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #EAEAEA;
|
||||
}
|
||||
`;
|
||||
62
components/page-footer/carbon.scss
Normal file
62
components/page-footer/carbon.scss
Normal file
@@ -0,0 +1,62 @@
|
||||
#carbonads {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu,
|
||||
Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
position: fixed;
|
||||
bottom: 15px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
#carbonads {
|
||||
display: flex;
|
||||
max-width: 330px;
|
||||
background-color: hsl(0, 0%, 98%);
|
||||
box-shadow: 0 1px 4px 1px hsla(0, 0%, 0%, .1);
|
||||
}
|
||||
|
||||
#carbonads a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#carbonads a:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#carbonads span {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#carbonads .carbon-wrap {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.carbon-img {
|
||||
display: block;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.carbon-img img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.carbon-text {
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.carbon-poweredby {
|
||||
display: block;
|
||||
padding: 8px 10px;
|
||||
background: repeating-linear-gradient(-45deg, transparent, transparent 5px, hsla(0, 0%, 0%, .025) 5px, hsla(0, 0%, 0%, .025) 10px) hsla(203, 11%, 95%, .4);
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .5px;
|
||||
font-weight: 600;
|
||||
font-size: 9px;
|
||||
line-height: 1;
|
||||
}
|
||||
41
components/page-footer/custom-ad.jsx
Normal file
41
components/page-footer/custom-ad.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
export const CustomAd = () => {
|
||||
return (
|
||||
<div id="carbonads">
|
||||
<span>
|
||||
<span className="carbon-wrap">
|
||||
<a
|
||||
href="https://freemote.com/strategy?sl=roadmap"
|
||||
className="carbon-img"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
<img
|
||||
src="/fm-img.png"
|
||||
alt="FM Logo"
|
||||
border="0"
|
||||
height="100"
|
||||
width="130"
|
||||
style={{ maxWidth: "130px" }}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://freemote.com/strategy?sl=roadmap"
|
||||
className="carbon-text"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
He Went from ZERO TO $74,000 as a Full Time Developer in 7 Weeks
|
||||
</a>
|
||||
</span>
|
||||
<a
|
||||
href="https://github.com/sponsors/kamranahmedse"
|
||||
className="carbon-poweredby"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
Sponsored by
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
71
components/page-footer/index.js
Normal file
71
components/page-footer/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import siteConfig from 'content/site';
|
||||
import { FooterWrap } from './style.js';
|
||||
import './carbon.scss';
|
||||
import { CustomAd } from "./custom-ad";
|
||||
|
||||
const PageFooter = () => (
|
||||
<FooterWrap className="border-top">
|
||||
<div className="container">
|
||||
<div className="foot-cols-wrap row">
|
||||
<div className="site-meta-wrap col-12 col-sm-12 col-lg col-xl col-md-12">
|
||||
<div className="site-meta">
|
||||
<div className="brand-detail">
|
||||
<a href="/" className='brand'><img src="/brand.png" alt="" /> roadmap.sh</a>
|
||||
<span className="preposition">by</span>
|
||||
<a href="https://twitter.com/kamranahmedse" target="_blank" className='follow-author'>@kamranahmedse</a>
|
||||
</div>
|
||||
<div className="brand-explanation">
|
||||
<p>Community created roadmaps, articles, resources and journeys to help you choose your path and grow in your career.</p>
|
||||
</div>
|
||||
<p className='meta-links'>
|
||||
© roadmap.sh ·
|
||||
<a href="/about">FAQ</a> ·
|
||||
<a href="/terms">Terms</a> ·
|
||||
<a href="/privacy">Privacy</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="site-contribute foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className='foot-header'>Contribute</li>
|
||||
<li><a href='https://gum.co/roadmap-sh' target="_blank">Sponsor us</a></li>
|
||||
<li><a href={ siteConfig.url.addGuide } target="_blank">Write a Guide</a></li>
|
||||
<li><a href={ siteConfig.url.addRoadmap } target="_blank">Submit a Roadmap</a></li>
|
||||
<li><a href='/about'>About this Site</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="site-learn foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className="foot-header">Learn</li>
|
||||
<li><a href="/guides">Read Guides</a></li>
|
||||
<li><a href="/watch">Watch Videos</a></li>
|
||||
<li><a href="/podcasts">Podcasts</a></li>
|
||||
<li><a href="https://www.youtube.com/channel/UCA0H2KIWgWTwpTFjSxp0now?sub_confirmation=1" target="_blank">YouTube</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="site-learn foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className="foot-header">Most Visited</li>
|
||||
<li><a href="/frontend">Frontend Roadmap</a></li>
|
||||
<li><a href="/backend">Backend Roadmap</a></li>
|
||||
<li><a href="/devops">DevOps Roadmap</a></li>
|
||||
<li><a href="/roadmaps">Upcoming</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomAd />
|
||||
|
||||
{/* Do not show on local */}
|
||||
{ process.env.GA_SECRET && false && (
|
||||
<>
|
||||
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CE7DLK3Y&placement=roadmapsh" id="_carbonads_js"></script>
|
||||
{/*<div id="codefund"></div>*/}
|
||||
{/*<script src="https://app.codefund.io/properties/681/funder.js" async></script>*/}
|
||||
</>
|
||||
) }
|
||||
</FooterWrap>
|
||||
);
|
||||
|
||||
export default PageFooter;
|
||||
94
components/page-footer/style.js
Normal file
94
components/page-footer/style.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FooterWrap = styled.div`
|
||||
padding: 65px 0;
|
||||
|
||||
.site-meta {
|
||||
margin-bottom: 30px;
|
||||
width: 350px;
|
||||
|
||||
.brand-detail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
|
||||
img {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.preposition {
|
||||
margin: 0 7px;
|
||||
}
|
||||
|
||||
.follow-author {
|
||||
background-color: #1e99e6;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
padding: 0 6px;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: #43aaea;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-explanation {
|
||||
color: #999;
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-links {
|
||||
color: #a3a3a3;
|
||||
font-size: 15px;
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.foot-col {
|
||||
margin-bottom: 20px;
|
||||
a {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.foot-header {
|
||||
font-weight: 500;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin-bottom: 2px;
|
||||
font-size: 15px;
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
16
components/page-header/index.js
Normal file
16
components/page-header/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { HeaderWrap, Subtitle, Title } from './style';
|
||||
|
||||
const PageHeader = ({
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
}) => (
|
||||
<HeaderWrap>
|
||||
<Title>{ title }</Title>
|
||||
<Subtitle dangerouslySetInnerHTML={{ __html: subtitle }} />
|
||||
|
||||
{ children }
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default PageHeader;
|
||||
22
components/page-header/style.js
Normal file
22
components/page-header/style.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
export const Subtitle = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
||||
13
components/page-logo-header/index.js
Normal file
13
components/page-logo-header/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { HeaderWrap, Subtitle, Title, Logo } from './style';
|
||||
|
||||
const PageLogoHeader = ({ title, subtitle, children, }) => (
|
||||
<HeaderWrap>
|
||||
<Logo src='/brand.png' alt='' />
|
||||
<Title>{ title }</Title>
|
||||
<Subtitle dangerouslySetInnerHTML={{ __html: subtitle }} />
|
||||
|
||||
{ children }
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default PageLogoHeader;
|
||||
30
components/page-logo-header/style.js
Normal file
30
components/page-logo-header/style.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
export const Subtitle = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
export const Logo = styled.img`
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
margin-bottom: 26px;
|
||||
`;
|
||||
62
components/roadmap-header/index.js
Normal file
62
components/roadmap-header/index.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faArrowLeft, faClock, faEnvelope, faHandshake } from '@fortawesome/free-solid-svg-icons';
|
||||
import { BadgeLink, BadgesList, DarkBadge, PrimaryBadge, SecondaryBadge } from 'components/badges';
|
||||
import siteConfig from "content/site";
|
||||
import { Description, Header, Title, MenuItemLink, MenuItems } from './style';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const RoadmapHeader = ({ roadmap, page = 'landscape' }) => (
|
||||
<Header>
|
||||
<Title>{ roadmap.title }</Title>
|
||||
<Description>{ roadmap.description }</Description>
|
||||
<BadgesList className="mt-4">
|
||||
<BadgeLink href="/roadmaps">
|
||||
<DarkBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faArrowLeft } />
|
||||
Other Roadmaps
|
||||
</DarkBadge>
|
||||
</BadgeLink>
|
||||
{ roadmap.upcoming && (
|
||||
<BadgeLink href="/signup">
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faClock } />
|
||||
Upcoming Roadmap
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
) }
|
||||
{ !roadmap.upcoming && (
|
||||
<BadgeLink href={ `${siteConfig.url.issue}?title=[${roadmap.title}] - Title Here` } target="_blank" className='d-none d-md-block' >
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon icon={ faHandshake } />
|
||||
Suggest Changes
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
) }
|
||||
|
||||
<BadgeLink href="/signup">
|
||||
<PrimaryBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faEnvelope } />
|
||||
Send me Updates
|
||||
</PrimaryBadge>
|
||||
</BadgeLink>
|
||||
</BadgesList>
|
||||
|
||||
<MenuItems className="border-bottom">
|
||||
<div className={ classNames({ 'd-none': roadmap.title.toLowerCase() !== 'frontend developer' })}>
|
||||
<Link href={ `${roadmap.url}` } passHref>
|
||||
<MenuItemLink className={ classNames({ active: page === 'landscape', }) }>Landscape</MenuItemLink>
|
||||
</Link>
|
||||
<Link href={ `${roadmap.url}/resources` } passHref>
|
||||
<MenuItemLink className={ classNames({ active: page === 'resources', }) }>Resources</MenuItemLink>
|
||||
</Link>
|
||||
{/*<Link href={ `${roadmap.url}/resources` } passHref>*/}
|
||||
{/* <MenuItemLink className={ classNames({ active: false, }) }>Project Ideas</MenuItemLink>*/}
|
||||
{/*</Link>*/}
|
||||
</div>
|
||||
</MenuItems>
|
||||
|
||||
</Header>
|
||||
);
|
||||
|
||||
export default RoadmapHeader;
|
||||
50
components/roadmap-header/style.js
Normal file
50
components/roadmap-header/style.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Header = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px 10px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
font-size: 48px;
|
||||
`;
|
||||
|
||||
export const Description = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444444;
|
||||
`;
|
||||
|
||||
export const MenuItems = styled.div`
|
||||
margin: 35px 0 15px;
|
||||
`;
|
||||
|
||||
export const MenuItemLink = styled.a`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 5px 10px 8px;
|
||||
text-decoration: none;
|
||||
color: rgb(102, 102, 102);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
&.active, &.active:hover {
|
||||
color: #2d2d2d;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 0;
|
||||
left: 9px;
|
||||
right: 9px;
|
||||
bottom: -1px;
|
||||
border-bottom: 2px solid currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #111111;
|
||||
}
|
||||
`;
|
||||
35
components/roadmap-resources/index.js
Normal file
35
components/roadmap-resources/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Summary, SummaryContainer, UpcomingContainer } from './style';
|
||||
import GuideBody from 'components/guide-body';
|
||||
import RoadmapHeader from 'components/roadmap-header';
|
||||
import SharePage from 'components/share-page';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const RoadmapResources = ({ roadmap }) => {
|
||||
if (roadmap.upcoming) {
|
||||
return (
|
||||
<>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<UpcomingContainer>
|
||||
<GuideBody guide={{ fileName: 'upcoming' }} />
|
||||
</UpcomingContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = roadmap.resources.replace(/^\//, '');
|
||||
const ResourcesContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<RoadmapHeader roadmap={ roadmap } page='resources' />
|
||||
<Summary className="container">
|
||||
<MdRenderer>
|
||||
<ResourcesContent />
|
||||
</MdRenderer>
|
||||
<SharePage title={ roadmap.description } url={ roadmap.url } />
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default RoadmapResources;
|
||||
21
components/roadmap-resources/style.js
Normal file
21
components/roadmap-resources/style.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const UpcomingContainer = styled.div`
|
||||
text-align: center;
|
||||
padding: 40px 0 50px;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Summary = styled.div`
|
||||
margin-top: 35px;
|
||||
min-height: 400px;
|
||||
max-width: 1000px;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
`;
|
||||
36
components/roadmap-summary/index.js
Normal file
36
components/roadmap-summary/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Summary, SummaryContainer, UpcomingContainer } from './style';
|
||||
import classNames from 'classnames';
|
||||
import GuideBody from 'components/guide-body';
|
||||
import RoadmapHeader from 'components/roadmap-header';
|
||||
import SharePage from 'components/share-page';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const RoadmapSummary = ({ roadmap }) => {
|
||||
if (roadmap.upcoming) {
|
||||
return (
|
||||
<>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<UpcomingContainer>
|
||||
<GuideBody guide={{ fileName: 'upcoming' }} />
|
||||
</UpcomingContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = roadmap.path.replace(/^\//, '');
|
||||
const RoadmapContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<Summary className={classNames("container", { "container-small": roadmap.isTextHeavy })}>
|
||||
<MdRenderer>
|
||||
<RoadmapContent />
|
||||
</MdRenderer>
|
||||
<SharePage title={ roadmap.description } url={ roadmap.url } />
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default RoadmapSummary;
|
||||
21
components/roadmap-summary/style.js
Normal file
21
components/roadmap-summary/style.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const UpcomingContainer = styled.div`
|
||||
text-align: center;
|
||||
padding: 40px 0 50px;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Summary = styled.div`
|
||||
margin-top: 35px;
|
||||
min-height: 400px;
|
||||
max-width: 1000px;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
`;
|
||||
28
components/row-block/index.js
Normal file
28
components/row-block/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Badge, BlockItem, ItemSubtitle, ItemTitle, ItemWrap } from './style';
|
||||
|
||||
const RowBlock = ({
|
||||
title,
|
||||
subtitle,
|
||||
url,
|
||||
badge,
|
||||
openExternal = false,
|
||||
disabled = false,
|
||||
children = null
|
||||
}) => (
|
||||
<ItemWrap className="col-md-6 col-lg-4 col-xl-4">
|
||||
<BlockItem href={ url } disabled={ disabled } target={openExternal ? '_blank' : '_self'}>
|
||||
{ !children && (
|
||||
<>
|
||||
<ItemTitle>
|
||||
{ title }
|
||||
{ badge && <Badge>{ badge }</Badge>}
|
||||
</ItemTitle>
|
||||
<ItemSubtitle>{ subtitle }</ItemSubtitle>
|
||||
</>
|
||||
) }
|
||||
{ children }
|
||||
</BlockItem>
|
||||
</ItemWrap>
|
||||
);
|
||||
|
||||
export default RowBlock;
|
||||
62
components/row-block/style.js
Normal file
62
components/row-block/style.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ItemWrap = styled.div`
|
||||
padding: 0 10px 20px;
|
||||
`;
|
||||
|
||||
export const BlockItem = styled.a`
|
||||
min-height: 114px;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 1px;
|
||||
max-width: 100%;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
justify-content: center;
|
||||
text-decoration:none;
|
||||
opacity: ${ props => props.disabled ? 0.5 : 1 };
|
||||
|
||||
&:hover {
|
||||
text-decoration:none;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ItemTitle = styled.h3`
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
color: #101010;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
export const Badge = styled.span`
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
background: #8a8a8a;
|
||||
color: white;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
height: 18px;
|
||||
`;
|
||||
|
||||
export const ItemSubtitle = styled.p`
|
||||
font-size: 14px;
|
||||
color: rgb(102, 102, 102);
|
||||
white-space: normal;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
`;
|
||||
21
components/share-icon/index.js
Normal file
21
components/share-icon/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ShareIcon = styled.a`
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
height: 22px !important;
|
||||
width: 22px !important;
|
||||
color: #757575;
|
||||
transition: all 0.2s;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
color: #000000
|
||||
}
|
||||
`;
|
||||
37
components/share-page/index.js
Normal file
37
components/share-page/index.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faHackerNewsSquare, faRedditSquare, faTwitterSquare } from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
import { getFacebookShareUrl, getHnShareUrl, getRedditShareUrl, getTwitterShareUrl } from 'lib/url';
|
||||
import { ShareIconsList, ShareWrap } from './style';
|
||||
import { ShareIcon } from 'components/share-icon';
|
||||
|
||||
const SharePage = ({
|
||||
url,
|
||||
title,
|
||||
twitterUsername,
|
||||
}) => (
|
||||
<ShareWrap>
|
||||
<ShareIconsList className="d-none d-sm-flex">
|
||||
<ShareIcon
|
||||
href={ getTwitterShareUrl({
|
||||
text: `${title} ${twitterUsername ? `by @${twitterUsername}` : ''}`,
|
||||
url: url
|
||||
})}
|
||||
target="_blank"
|
||||
>
|
||||
<FontAwesomeIcon icon={ faTwitterSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getFacebookShareUrl({ text: title, url: url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faFacebookSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getHnShareUrl({ text: title, url: url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faHackerNewsSquare}/>
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getRedditShareUrl({ text: title, url: url })} target="_blank">
|
||||
<FontAwesomeIcon icon={ faRedditSquare } />
|
||||
</ShareIcon>
|
||||
</ShareIconsList>
|
||||
</ShareWrap>
|
||||
);
|
||||
|
||||
export default SharePage;
|
||||
21
components/share-page/style.js
Normal file
21
components/share-page/style.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ShareIconsList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: sticky;
|
||||
top: 65px;
|
||||
`;
|
||||
|
||||
export const ShareWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
padding: 0 0;
|
||||
top: 0;
|
||||
left: -40px;
|
||||
min-height: 100%;
|
||||
`;
|
||||
17
components/signup-form/index.js
Normal file
17
components/signup-form/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { SignUpWrap, Title, Subtitle, Textbox, Button } from './style';
|
||||
|
||||
const SignUpForm = () => (
|
||||
<SignUpWrap>
|
||||
<Title>Subscribe</Title>
|
||||
<Subtitle>Enter your email below to get notified about the new roadmaps, guides and updates</Subtitle>
|
||||
<form action="https://kamranahmed.us9.list-manage.com/subscribe/post?u=6f57b741a6a939744f1f203a0&id=f9ca4d6aee" target="_blank" method="post">
|
||||
<Textbox type="email" name="EMAIL" required placeholder="Your email" />
|
||||
<div style={{position: 'absolute', left: '-5000px'}}>
|
||||
<input type="text" name="b_6f57b741a6a939744f1f203a0_f9ca4d6aee" tabIndex="-1" />
|
||||
</div>
|
||||
<Button type="submit">Subscribe</Button>
|
||||
</form>
|
||||
</SignUpWrap>
|
||||
);
|
||||
|
||||
export default SignUpForm;
|
||||
57
components/signup-form/style.js
Normal file
57
components/signup-form/style.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SignUpWrap = styled.div`
|
||||
max-width: 400px;
|
||||
margin: 200px auto;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 17px;
|
||||
`;
|
||||
|
||||
export const Subtitle = styled.p`
|
||||
font-size: 16px;
|
||||
`;
|
||||
|
||||
export const Textbox = styled.input`
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
height: auto;
|
||||
outline: none;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 5px;
|
||||
|
||||
&:focus {
|
||||
border: 1px solid #101010;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Button = styled.button`
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
margin-top: 10px;
|
||||
background: #333;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
background: #000000;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
26
components/site-nav/index.js
Normal file
26
components/site-nav/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { HeaderWrap } from './style';
|
||||
|
||||
const SiteNav = () => (
|
||||
<HeaderWrap>
|
||||
<div className='top-row container'>
|
||||
<div className='flex-grow-1 brand'>
|
||||
<a href='/'>
|
||||
<img src='/brand.png' alt='' />
|
||||
</a>
|
||||
</div>
|
||||
<div className='nav-links'>
|
||||
<a href='/guides'>Read</a>
|
||||
<a href='/watch'>
|
||||
Watch
|
||||
<span className='new-item' />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='ml-3 align-items-center d-none d-md-flex'>
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=kamranahmedse&repo=developer-roadmap&type=star&count=true&size=large" frameBorder="0" scrolling="0" width="190px" height="30px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default SiteNav;
|
||||
61
components/site-nav/style.js
Normal file
61
components/site-nav/style.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
padding: 15px 0;
|
||||
font-size: 18px;
|
||||
|
||||
.top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.github-button {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.brand img {
|
||||
padding: 5px 0;
|
||||
height: 50px;
|
||||
text-decoration: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
a {
|
||||
position: relative;
|
||||
padding: 0 10px;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.signup {
|
||||
background: #101010;
|
||||
border-radius: 5px;
|
||||
color: #ffffff;
|
||||
padding: 7px 10px;
|
||||
margin-left: 15px;
|
||||
|
||||
&:hover {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
}
|
||||
|
||||
.new-item {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
background: #e25712;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
26
components/sponsor-banner/index.js
Normal file
26
components/sponsor-banner/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { BannerWrap, CloseSponsor, SponsorLogo } from './style';
|
||||
|
||||
export function SponsorBanner({ onCloseBanner = () => null }) {
|
||||
return (
|
||||
<div className='row'>
|
||||
<div className='col p-0'>
|
||||
<BannerWrap
|
||||
href={`https://www.youtube.com/channel/UCA0H2KIWgWTwpTFjSxp0now?sub_confirmation=1`}
|
||||
target='_blank'
|
||||
className='alert alert-info'
|
||||
>
|
||||
<SponsorLogo src='/sponsors/youtube.svg' />
|
||||
We now have a youtube channel. <span className='d-none d-sm-inline-block'>Subscribe for the video content.</span>
|
||||
|
||||
<CloseSponsor
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onCloseBanner();
|
||||
}}
|
||||
className='close'>×</CloseSponsor>
|
||||
</BannerWrap>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
components/sponsor-banner/style.js
Normal file
49
components/sponsor-banner/style.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BannerWrap = styled.a`
|
||||
margin-bottom: 0;
|
||||
//background: #101010;
|
||||
//color: white;
|
||||
background: #ffe0b2;
|
||||
color: #d8362a;
|
||||
font-weight: 600;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 10px 15px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
transition: all 200ms;
|
||||
|
||||
&:hover {
|
||||
background: #ffd698;
|
||||
color: #bd2015;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SponsorLogo = styled.img`
|
||||
height: 20px;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
`;
|
||||
|
||||
export const EmojiWrap = styled.img`
|
||||
height: 18px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin: 0 6px 0 6px;
|
||||
`;
|
||||
|
||||
export const CloseSponsor = styled.span`
|
||||
text-shadow: none;
|
||||
margin-right: 15px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
color: #101010;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: #101010;
|
||||
}
|
||||
`;
|
||||
22
components/tos-page/index.js
Normal file
22
components/tos-page/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TosPage = styled.div`
|
||||
h2 {
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #333;
|
||||
}
|
||||
`;
|
||||
42
content/authors.json
Normal file
42
content/authors.json
Normal file
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"username": "kamranahmedse",
|
||||
"name": "Kamran Ahmed",
|
||||
"twitter": "kamranahmedse",
|
||||
"picture": "/authors/kamranahmedse.jpeg",
|
||||
"bio": "Lead engineer at Tajawal. Lover of all things web and opensource. Created roadmap.sh to help the confused ones."
|
||||
},
|
||||
{
|
||||
"username": "jesse",
|
||||
"name": "Jesse Li",
|
||||
"twitter": "__jesse_li",
|
||||
"picture": "/authors/jesse.png",
|
||||
"bio": "Software engineer."
|
||||
},
|
||||
{
|
||||
"username": "dmytrobol",
|
||||
"name": "Dmytro Bolkachov",
|
||||
"twitter": "dmytrobol",
|
||||
"picture": "/authors/dmytrobol.png",
|
||||
"bio": "JavaScript Lad, Movie buff and coder interested in everything web related"
|
||||
},
|
||||
{
|
||||
"username": "spekulatius",
|
||||
"name": "Peter Thaleikis",
|
||||
"twitter": "spekulatius1984",
|
||||
"picture": "/authors/spekulatius.jpg",
|
||||
"bio": "Developer building side-projects for fun, lover of the web and open source"
|
||||
},
|
||||
{
|
||||
"username": "ebrahimbharmal007",
|
||||
"name": "Ebrahim Bharmal",
|
||||
"twitter": "BharmalEbrahim",
|
||||
"picture": "/authors/ebrahimbharmal007.png",
|
||||
"bio": "Love building projects using tools completely new to me. Python forever. Senior at University of Texas at Arlington (2021)"
|
||||
},
|
||||
{
|
||||
"username": "lesovsky",
|
||||
"name": "Alexey Lesovsky",
|
||||
"bio": "Linux system administrator and PostgreSQL DBA at DataEgret."
|
||||
}
|
||||
]
|
||||
264
content/guides.json
Normal file
264
content/guides.json
Normal file
@@ -0,0 +1,264 @@
|
||||
[
|
||||
{
|
||||
"title": "WebStorm — Project History",
|
||||
"description": "Learn how to peek through the history of any git repository to learn how it grew.",
|
||||
"url": "/guides/project-history",
|
||||
"fileName": "project-history",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-07-16T19:59:14.191Z",
|
||||
"createdAt": "2020-07-16T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "CI and CD",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/guides/ci-cd",
|
||||
"fileName": "ci-cd",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "SSO — Single Sign On",
|
||||
"description": "Learn the basics of SAML and understand how does Single Sign On work.",
|
||||
"url": "/guides/sso",
|
||||
"fileName": "sso",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-07-01T19:59:14.191Z",
|
||||
"createdAt": "2020-07-01T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "OAuth — Open Authorization",
|
||||
"description": "Learn and understand what is OAuth and how it works",
|
||||
"url": "/guides/oauth",
|
||||
"fileName": "oauth",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-06-28T19:59:14.191Z",
|
||||
"createdAt": "2020-06-28T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "JWT Authentication",
|
||||
"description": "Understand what is JWT authentication and how is it implemented",
|
||||
"url": "/guides/jwt-authentication",
|
||||
"fileName": "jwt-authentication",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-06-20T19:59:14.191Z",
|
||||
"createdAt": "2020-06-20T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Token Based Authentication",
|
||||
"description": "Understand what is token based authentication and how it is implemented",
|
||||
"url": "/guides/token-authentication",
|
||||
"fileName": "token-authentication",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-06-02T20:59:14.191Z",
|
||||
"createdAt": "2020-06-02T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Session Based Authentication",
|
||||
"description": "Understand what is session based authentication and how it is implemented",
|
||||
"url": "/guides/session-authentication",
|
||||
"fileName": "session-authentication",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-05-26T20:59:14.191Z",
|
||||
"createdAt": "2020-05-26T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Basic Authentication",
|
||||
"description": "Understand what is basic authentication and how it is implemented",
|
||||
"url": "/guides/basic-authentication",
|
||||
"fileName": "basic-authentication",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-05-19T20:59:14.191Z",
|
||||
"createdAt": "2020-05-19T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Character Encodings",
|
||||
"description": "Covers the basics of character encodings and explains ASCII vs Unicode",
|
||||
"url": "/guides/character-encodings",
|
||||
"fileName": "character-encodings",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-05-14T20:59:14.191Z",
|
||||
"createdAt": "2020-05-14T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Unfamiliar Codebase",
|
||||
"description": "Tips on getting getting familiar with an unfamiliar codebase",
|
||||
"url": "/guides/unfamiliar-codebase",
|
||||
"fileName": "unfamiliar-codebase",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-05-04T20:59:14.191Z",
|
||||
"createdAt": "2020-05-04T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Build it and they will come?",
|
||||
"description": "Why “build it and they will come” alone won’t work anymore",
|
||||
"url": "/guides/why-build-it-and-they-will-come-wont-work-anymore",
|
||||
"fileName": "why-build-it-and-they-will-come-wont-work-anymore",
|
||||
"featured": true,
|
||||
"author": "spekulatius",
|
||||
"updatedAt": "2020-05-04T12:59:14.191Z",
|
||||
"createdAt": "2020-05-04T12:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "DHCP in One Picture",
|
||||
"description": "Here is what happens when a new device joins the network.",
|
||||
"url": "/guides/dhcp-in-one-picture",
|
||||
"fileName": "dhcp-in-one-picture",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-04-28T15:48:21.191Z",
|
||||
"createdAt": "2020-04-28T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "SSL vs TLS vs SSH",
|
||||
"description": "Quick tidbit on the differences between SSL, TLS, HTTPS and SSH",
|
||||
"url": "/guides/ssl-tls-https-ssh",
|
||||
"fileName": "ssl-tls-https-ssh",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-04-22T15:48:21.191Z",
|
||||
"createdAt": "2020-04-22T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Asymptotic Notation",
|
||||
"description": "Learn the basics of measuring the time and space complexity of algorithms",
|
||||
"url": "/guides/asymptotic-notation",
|
||||
"fileName": "asymptotic-notation",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-04-03T15:48:21.191Z",
|
||||
"createdAt": "2020-04-03T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Big-O Notation",
|
||||
"description": "Easy to understand explanation of Big-O notation without any fancy terms",
|
||||
"url": "/guides/big-o-notation",
|
||||
"fileName": "big-o-notation",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-03-15T15:48:21.191Z",
|
||||
"createdAt": "2020-03-15T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Random Numbers: Are they?",
|
||||
"description": "Learn how they are generated and why they may not be truly random.",
|
||||
"url": "/guides/random-numbers",
|
||||
"fileName": "random-numbers",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-03-14T15:48:21.191Z",
|
||||
"createdAt": "2020-03-14T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Scaling Databases",
|
||||
"description": "Learn the ups and downs of different database scaling strategies",
|
||||
"url": "/guides/scaling-databases",
|
||||
"fileName": "scaling-databases",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-02-18T15:48:21.191Z",
|
||||
"createdAt": "2020-02-18T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "How does the internet work?",
|
||||
"description": "Learn the basics of internet and everything involved with this short video series",
|
||||
"url": "/guides/what-is-internet",
|
||||
"fileName": "what-is-internet",
|
||||
"featured": true,
|
||||
"author": "dmytrobol",
|
||||
"updatedAt": "2020-02-29T15:48:21.191Z",
|
||||
"createdAt": "2020-02-29T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Building a BitTorrent Client",
|
||||
"description": "Learn everything you need to know about BitTorrent by writing a client in Go",
|
||||
"url": "/guides/torrent-client",
|
||||
"fileName": "torrent-client",
|
||||
"featured": true,
|
||||
"author": "jesse",
|
||||
"updatedAt": "2020-01-17T15:48:21.191Z",
|
||||
"createdAt": "2020-01-17T15:48:21.191Z",
|
||||
"canonical": "https://blog.jse.li/posts/torrent/"
|
||||
},
|
||||
{
|
||||
"title": "Levels of Seniority",
|
||||
"description": "How to Step Up as a Junior, Mid Level or a Senior Developer?",
|
||||
"url": "/guides/levels-of-seniority",
|
||||
"fileName": "levels-of-seniority",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2019-12-03T12:13:00.860Z",
|
||||
"createdAt": "2019-12-03T12:13:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "Design Patterns for Humans",
|
||||
"description": "A language agnostic, ultra-simplified explanation to design patterns",
|
||||
"url": "/guides/design-patterns-for-humans",
|
||||
"fileName": "design-patterns-for-humans",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2019-10-09T12:00:00.860Z",
|
||||
"createdAt": "2019-01-23T17:00:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "Journey to HTTP/2",
|
||||
"description": "The evolution of HTTP. How it all started and where we stand today",
|
||||
"url": "/guides/journey-to-http2",
|
||||
"fileName": "journey-to-http2",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"createdAt": "2018-12-04T12:00:00.860Z",
|
||||
"updatedAt": "2018-12-04T12:00:00.860Z",
|
||||
"draft": true
|
||||
},
|
||||
{
|
||||
"title": "DNS in One Picture",
|
||||
"description": "Quick illustrative guide on how a website is found on the internet.",
|
||||
"url": "/guides/dns-in-one-picture",
|
||||
"fileName": "dns-in-one-picture",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2018-12-04T12:00:00.860Z",
|
||||
"createdAt": "2018-12-04T17:00:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "HTTP Caching",
|
||||
"description": "Everything you need to know about web caching",
|
||||
"url": "/guides/http-caching",
|
||||
"fileName": "http-caching",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"createdAt": "2018-11-29T17:00:00.860Z",
|
||||
"updatedAt": "2018-11-29T17:00:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "Brief History of JavaScript",
|
||||
"description": "How JavaScript was introduced and evolved over the years",
|
||||
"url": "/guides/history-of-javascript",
|
||||
"fileName": "history-of-javascript",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"createdAt": "2017-10-28T17:00:00.860Z",
|
||||
"updatedAt": "2017-10-28T17:00:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "Proxy Servers",
|
||||
"description": "How do proxy servers work and what are forward and reverse proxies?",
|
||||
"url": "/guides/proxy-servers",
|
||||
"fileName": "proxy-servers",
|
||||
"featured": true,
|
||||
"author": "ebrahimbharmal007",
|
||||
"createdAt": "2020-07-24T12:40:18",
|
||||
"updatedAt": "2020-07-24T12:40:18"
|
||||
}
|
||||
]
|
||||
5
content/guides/asymptotic-notation.md
Normal file
5
content/guides/asymptotic-notation.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Asymptotic notation is the standard way of measuring the time and space that an algorithm will consume as the input grows. In one of my last guides, I covered "Big-O notation" and a lot of you asked for a similar one for Asymptotic notation. You can find the [previous guide here](/guides/big-o-notation).
|
||||
|
||||
[](/guides/asymptotic-notation.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1243861514907418624) where this image was posted.
|
||||
3
content/guides/basic-authentication.md
Normal file
3
content/guides/basic-authentication.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/basic-authentication.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1261783266044063748) where this image was posted.
|
||||
5
content/guides/big-o-notation.md
Normal file
5
content/guides/big-o-notation.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Big-O notation is the mathematical notation that helps analyse the algorithms to get an idea about how they might perform as the input grows. The image below explains Big-O in a simple way without using any fancy terminology.
|
||||
|
||||
[](/guides/big-o-notation.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1235708842610212864) where this image was posted.
|
||||
3
content/guides/character-encodings.md
Normal file
3
content/guides/character-encodings.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/character-encodings.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1259631582362689537) where this image was posted.
|
||||
5
content/guides/ci-cd.md
Normal file
5
content/guides/ci-cd.md
Normal file
@@ -0,0 +1,5 @@
|
||||
The image below details the differences between the continuous integration and continuous delivery. Also, here is the [accompanying video on implementing that with GitHub actions](https://www.youtube.com/watch?v=nyKZTKQS_EQ).
|
||||
|
||||
[](/guides/ci-cd.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1282806173939511298) where this image was posted.
|
||||
@@ -1,25 +1,3 @@
|
||||
---
|
||||
title: "Design Patterns for Humans"
|
||||
description: "A language agnostic, ultra-simplified explanation to design patterns"
|
||||
author:
|
||||
name: "Kamran Ahmed"
|
||||
url: "https://twitter.com/kamranahmedse"
|
||||
imageUrl: "/authors/kamranahmedse.jpeg"
|
||||
seo:
|
||||
title: "Design Patterns for Humans - roadmap.sh"
|
||||
description: "A language agnostic, ultra-simplified explanation to design patterns"
|
||||
isNew: false
|
||||
type: "textual"
|
||||
date: 2019-01-23
|
||||
sitemap:
|
||||
priority: 0.7
|
||||
changefreq: "weekly"
|
||||
tags:
|
||||
- "guide"
|
||||
- "textual-guide"
|
||||
- "guide-sitemap"
|
||||
---
|
||||
|
||||
Design patterns are solutions to recurring problems; **guidelines on how to tackle certain problems**. They are not classes, packages or libraries that you can plug into your application and wait for the magic to happen. These are, rather, guidelines on how to tackle certain problems in certain situations.
|
||||
|
||||
> Design patterns are solutions to recurring problems; guidelines on how to tackle certain problems
|
||||
3
content/guides/dhcp-in-one-picture.md
Normal file
3
content/guides/dhcp-in-one-picture.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/dhcp.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1254142557417857025) where this image was posted.
|
||||
@@ -1,27 +1,5 @@
|
||||
---
|
||||
title: "DNS in One Picture"
|
||||
description: "Quick illustrative guide on how a website is found on the internet."
|
||||
author:
|
||||
name: "Kamran Ahmed"
|
||||
url: "https://twitter.com/kamranahmedse"
|
||||
imageUrl: "/authors/kamranahmedse.jpeg"
|
||||
seo:
|
||||
title: "DNS in One Picture - roadmap.sh"
|
||||
description: "Quick illustrative guide on how a website is found on the internet."
|
||||
isNew: false
|
||||
type: "visual"
|
||||
date: 2018-12-04
|
||||
sitemap:
|
||||
priority: 0.7
|
||||
changefreq: "weekly"
|
||||
tags:
|
||||
- "guide"
|
||||
- "visual-guide"
|
||||
- "guide-sitemap"
|
||||
---
|
||||
|
||||
DNS or Domain Name System is one of the fundamental blocks of the internet. As a developer, you should have at-least the basic understanding of how it works. This article is a brief introduction to what is DNS and how it works.
|
||||
|
||||
DNS at its simplest is like a phonebook on your mobile phone. Whenever you have to call one of your contacts, you can either dial their number from your memory or use their name which will then be used by your mobile phone to search their number in your phone book to call them. Every time you make a new friend, or your existing friend gets a mobile phone, you have to memorize their phone number or save it in your phonebook to be able to call them later on. DNS or Domain Name System, in a similar fashion, is a mechanism that allows you to browse websites on the internet. Just like your mobile phone does not know how to call without knowing the phone number, your browser does not know how to open a website just by the domain name; it needs to know the IP Address for the website to open. You can either type the IP Address to open, or provide the domain name and press enter which will then be used by your browser to find the IP address by going through several hoops. The picture below is the illustration of how your browser finds a website on the internet.
|
||||
|
||||
[](https://i.imgur.com/z9rwm5A.png)
|
||||
[](https://i.imgur.com/z9rwm5A.png)
|
||||
@@ -1,29 +1,7 @@
|
||||
---
|
||||
title: "Brief History of JavaScript"
|
||||
description: "How JavaScript was introduced and evolved over the years"
|
||||
author:
|
||||
name: "Kamran Ahmed"
|
||||
url: "https://twitter.com/kamranahmedse"
|
||||
imageUrl: "/authors/kamranahmedse.jpeg"
|
||||
seo:
|
||||
title: "Brief History of JavaScript - roadmap.sh"
|
||||
description: "How JavaScript was introduced and evolved over the years"
|
||||
isNew: false
|
||||
type: "textual"
|
||||
date: 2017-10-28
|
||||
sitemap:
|
||||
priority: 0.7
|
||||
changefreq: "weekly"
|
||||
tags:
|
||||
- "guide"
|
||||
- "textual-guide"
|
||||
- "guide-sitemap"
|
||||
---
|
||||
|
||||
Around 10 years ago, Jeff Atwood (the founder of stackoverflow) made a case that JavaScript is going to be the future and he coined the “Atwood Law” which states that *Any application that can be written in JavaScript will eventually be written in JavaScript*. Fast-forward to today, 10 years later, if you look at it it rings truer than ever. JavaScript is continuing to gain more and more adoption.
|
||||
|
||||
### JavaScript is announced
|
||||
JavaScript was initially created by [Brendan Eich](https://twitter.com/BrendanEich) of NetScape and was first announced in a press release by Netscape in 1995. It has a bizarre history of naming; initially it was named `Mocha` by the creator, which was later renamed to `LiveScript`. In 1996, about a year later after the release, NetScape decided to rename it to be `JavaScript` with hopes of capitalizing on the Java community (although JavaScript did not have any relationship with Java) and released Netscape 2.0 with the official support of JavaScript.
|
||||
JavaScript was initially created by [Brendan Eich](https://twitter.com/BrendanEich) of NetScape and was first announced in a press release by Netscape in 1995. It has a bizarre history of naming; initally it was named `Mocha` by the creator, which was later renamed to `LiveScript`. In 1996, about a year later after the release, NetScape decided to rename it to be `JavaScript` with hopes of capitalizing on the Java community (although JavaScript did not have any relationship with Java) and released Netscape 2.0 with the official support of JavaScript.
|
||||
|
||||
### ES1, ES2 and ES3
|
||||
In 1996, Netscape decided to submit it to [ECMA International](https://en.wikipedia.org/wiki/Ecma_International) with the hopes of getting it standardized. First edition of the standard specification was released in 1997 and the language was standardized. After the initial release, `ECMAScript` was continued to be worked upon and in no-time two more versions were released ECMAScript 2 in 1998 and ECMAScript 3 in 1999.
|
||||
@@ -1,30 +1,8 @@
|
||||
---
|
||||
title: "HTTP Caching"
|
||||
description: "Everything you need to know about web caching"
|
||||
author:
|
||||
name: "Kamran Ahmed"
|
||||
url: "https://twitter.com/kamranahmedse"
|
||||
imageUrl: "/authors/kamranahmedse.jpeg"
|
||||
seo:
|
||||
title: "HTTP Caching - roadmap.sh"
|
||||
description: "Everything you need to know about web caching"
|
||||
isNew: false
|
||||
type: "textual"
|
||||
date: 2018-11-29
|
||||
sitemap:
|
||||
priority: 0.7
|
||||
changefreq: "weekly"
|
||||
tags:
|
||||
- "guide"
|
||||
- "textual-guide"
|
||||
- "guide-sitemap"
|
||||
---
|
||||
|
||||
As users, we easily get frustrated by the buffering of videos, the images that take seconds to load, and pages that got stuck because the content is being loaded. Loading the resources from some cache is much faster than fetching the same from the originating server. It reduces latency, speeds up the loading of resources, decreases the load on the server, cuts down the bandwidth costs etc.
|
||||
As users, we easily get frustrated by the buffering videos, the images that take seconds to load, pages that got stuck because the content is being loaded. Loading the resources from some cache is much faster than fetching the same from the originating server. It reduces latency, speeds up the loading of resources, decreases the load on server, cuts down the bandwidth costs etc.
|
||||
|
||||
### Introduction
|
||||
|
||||
What is a web cache? It is something that sits somewhere between the client and the server, continuously looking at the requests and their responses, looking for any responses that can be cached. So that there is less time consumed when the same request is made again.
|
||||
What is web cache? It is something that sits somewhere between the client and the server, continuously looking at the requests and their responses, looking for any responses that can be cached. So that there is less time consumed when the same request is made again.
|
||||
|
||||

|
||||
|
||||
@@ -33,13 +11,13 @@ What is a web cache? It is something that sits somewhere between the client and
|
||||
Before we get into further details, let me give you an overview of the terms that will be used, further in the article
|
||||
|
||||
- **Client** could be your browser or any application requesting the server for some resource
|
||||
- **Origin Server**, the source of truth, houses all the content required by the client and is responsible for fulfilling the client's requests.
|
||||
- **Stale Content** is cached but expired content
|
||||
- **Fresh Content** is the content available in the cache that hasn't expired yet
|
||||
- **Origin Server**, the source of truth, houses all the content required by the client and is responsible for fulfilling the client requests.
|
||||
- **Stale Content** is the cached but expired content
|
||||
- **Fresh Content** is the content available in cache that hasn't expired yet
|
||||
- **Cache Validation** is the process of contacting the server to check the validity of the cached content and get it updated for when it is going to expire
|
||||
- **Cache Invalidation** is the process of removing any stale content available in the cache
|
||||
|
||||

|
||||

|
||||
|
||||
### Caching Locations
|
||||
|
||||
@@ -65,56 +43,47 @@ Unlike browser cache which serves a single user, proxy caches may serve hundreds
|
||||
|
||||
#### Reverse Proxy Cache
|
||||
|
||||
A Reverse proxy cache or surrogate cache is implemented close to the origin servers in order to reduce the load on the server. Unlike proxy caches which are implemented by ISPs etc to reduce the bandwidth usage in a network, surrogates or reverse proxy caches are implemented near the origin servers by the server administrators to reduce the load on the server.
|
||||
Reverse proxy cache or surrogate cache is implemented close to the origin servers in order to reduce the load on server. Unlike proxy caches which are implemented by ISPs etc to reduce the bandwidth usage in a network, surrogates or reverse proxy caches are implemented near to the origin servers by the server administrators to reduce the load on server.
|
||||
|
||||

|
||||
|
||||
Although you can control the reverse proxy caches (since it is implemented by you on your server) you can not avoid or control browser and proxy caches. And if your website is not configured to use these caches properly, it will still be cached using whatever defaults are set on these caches.
|
||||
Although you can control the reverse proxy caches (since it is implemented by you on your server) you can not avoid or control browser and proxy caches. And if your website is not configured to use these caches properly, it will still be cached using whatever the defaults are set on these caches.
|
||||
|
||||
### Caching Headers
|
||||
|
||||
So, how do we control the web cache? Whenever the server emits some response, it is accompanied by some HTTP headers to guide the caches on whether and how to cache this response. The content provider is the one that has to make sure to return proper HTTP headers to force the caches on how to cache the content.
|
||||
So, how do we control the web cache? Whenever the server emits some response, it is accompanied with some HTTP headers to guide the caches whether and how to cache this response. Content provider is the one that has to make sure to return proper HTTP headers to force the caches on how to cache the content.
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Caching Locations](#caching-locations)
|
||||
- [Browser Cache](#browser-cache)
|
||||
- [Proxy Cache](#proxy-cache)
|
||||
- [Reverse Proxy Cache](#reverse-proxy-cache)
|
||||
- [Caching Headers](#caching-headers)
|
||||
- [Expires](#expires)
|
||||
- [Pragma](#pragma)
|
||||
- [Cache-Control](#cache-control)
|
||||
- [private](#private)
|
||||
- [public](#public)
|
||||
- [no-store](#no-store)
|
||||
- [no-cache](#no-cache)
|
||||
- [max-age: seconds](#max-age-seconds)
|
||||
- [s-maxage: seconds](#s-maxage-seconds)
|
||||
- [must-revalidate](#must-revalidate)
|
||||
- [proxy-revalidate](#proxy-revalidate)
|
||||
- [Mixing Values](#mixing-values)
|
||||
- [Expires](#expires)
|
||||
- [Pragma](#pragma)
|
||||
- [Cache-Control](#cache-control)
|
||||
- [private](#private)
|
||||
- [public](#public)
|
||||
- [no-store](#no-store)
|
||||
- [no-cache](#no-cache)
|
||||
- [max-age: seconds](#max-age)
|
||||
- [s-maxage: seconds](#s-maxage)
|
||||
- [must-revalidate](#must-revalidate)
|
||||
- [proxy-revalidate](#proxy-revalidate)
|
||||
- [Mixing Values](#mixing-values)
|
||||
- [Validators](#validators)
|
||||
- [ETag](#etag)
|
||||
- [Last-Modified](#last-modified)
|
||||
- [Where do I start?](#where-do-i-start)
|
||||
- [Utilizing Server](#utilizing-server)
|
||||
- [Caching Recommendations](#caching-recommendations)
|
||||
|
||||
#### Expires
|
||||
|
||||
Before HTTP/1.1 and the introduction of `Cache-Control`, there was an `Expires` header which is simply a timestamp telling the caches how long should some content be considered fresh. A possible value to this header is the absolute expiry date; where a date has to be in GMT. Below is the sample header
|
||||
Before HTTP/1.1 and introduction of `Cache-Control`, there was `Expires` header which is simply a timestamp telling the caches how long should some content be considered fresh. Possible value to this header is absolute expiry date; where date has to be in GMT. Below is the sample header
|
||||
|
||||
```html
|
||||
Expires: Mon, 13 Mar 2017 12:22:00 GMT
|
||||
```
|
||||
|
||||
It should be noted that the date cannot be more than a year and if the date format is wrong, the content will be considered stale. Also, the clock on the cache has to be in sync with the clock on the server, otherwise, the desired results might not be achieved.
|
||||
It should be noted that the date cannot be more than a year and if the date format is wrong, content will be considered stale. Also, the clock on cache has to be in sync with the clock on server, otherwise the desired results might not be achieved.
|
||||
|
||||
Although the `Expires` header is still valid and is supported widely by the caches, preference should be given to HTTP/1.1 successor of it i.e. `Cache-Control`.
|
||||
Although, `Expires` header is still valid and is supported widely by the caches, preference should be given to HTTP/1.1 successor of it i.e. `Cache-Control`.
|
||||
|
||||
#### Pragma
|
||||
|
||||
Another one from the old, pre HTTP/1.1 days, is `Pragma`. Everything that it could do is now possible using the cache-control header given below. However, one thing I would like to point out about it is, that you might see `Pragma: no-cache` being used here and there in hopes of stopping the response from being cached. It might not necessarily work; as HTTP specification discusses it in the request headers and there is no mention of it in the response headers. Rather `Cache-Control` header should be used to control the caching.
|
||||
Another one from the old, pre HTTP/1.1 days, is `Pragma`. Everything that it could do is now possible using the cache-control header given below. However, one thing I would like to point out about it is, you might see `Pragma: no-cache` being used here and there in hopes of stopping the response from being cached. It might not necessarily work; as HTTP specification discusses it in the request headers and there is no mention of it in the response headers. Rather `Cache-Control` header should be used to control the caching.
|
||||
|
||||
#### Cache-Control
|
||||
|
||||
@@ -129,7 +98,7 @@ Setting the cache to `private` means that the content will not be cached in any
|
||||
Cache-Control: private
|
||||
```
|
||||
|
||||
Having said that, don't let it fool you into thinking that setting this header will make your data any secure; you still have to use SSL for that purpose.
|
||||
Having said that, don't let it fool you in to thinking that setting this header will make your data any secure; you still have to use SSL for that purpose.
|
||||
|
||||
##### public
|
||||
|
||||
@@ -169,7 +138,7 @@ Cache-Control: s-maxage=3600, public
|
||||
```
|
||||
|
||||
##### must-revalidate
|
||||
**`must-revalidate`** it might happen sometimes that if you have network problems and the content cannot be retrieved from the server, the browser may serve stale content without validation. `must-revalidate` avoids that. If this directive is present, it means that stale content cannot be served in any case and the data must be re-validated from the server before serving.
|
||||
**`must-revalidate`** it might happen sometimes that if you have network problems and the content cannot be retrieved from the server, browser may serve stale content without validation. `must-revalidate` avoids that. If this directive is present, it means that stale content cannot be served in any case and the data must be re-validated from the server before serving.
|
||||
|
||||
```html
|
||||
Cache-Control: max-age=3600, public, must-revalidate
|
||||
@@ -208,7 +177,7 @@ ETag: "j82j8232ha7sdh0q2882" - Strong Etag
|
||||
ETag: W/"j82j8232ha7sdh0q2882" - Weak Etag (prefixed with `W/`)
|
||||
```
|
||||
|
||||
A strong validating ETag means that two resources are **exactly** same and there is no difference between them at all. While a weak ETag means that two resources although not strictly the same but could be considered the same. Weak etags might be useful for dynamic content, for example.
|
||||
A strong validating ETag means that two resources are **exactly** same and there is no difference between them at all. While a weak ETag means that two resources are although not strictly same but could be considered same. Weak etags might be useful for dynamic content, for example.
|
||||
|
||||
Now you know what etags are but how does the browser make this request? by making a request to server while sending the available Etag in `If-None-Match` header.
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
---
|
||||
title: "Journey to HTTP/2"
|
||||
description: "The evolution of HTTP. How it all started and where we stand today"
|
||||
author:
|
||||
name: "Kamran Ahmed"
|
||||
url: "https://twitter.com/kamranahmedse"
|
||||
imageUrl: "/authors/kamranahmedse.jpeg"
|
||||
seo:
|
||||
title: "Journey to HTTP/2 - roadmap.sh"
|
||||
description: "The evolution of HTTP. How it all started and where we stand today"
|
||||
isNew: false
|
||||
type: "textual"
|
||||
date: 2018-12-04
|
||||
sitemap:
|
||||
priority: 0.7
|
||||
changefreq: "weekly"
|
||||
tags:
|
||||
- "guide"
|
||||
- "textual-guide"
|
||||
- "guide-sitemap"
|
||||
---
|
||||
|
||||
HTTP is the protocol that every web developer should know as it powers the whole web and knowing it is definitely going to help you develop better applications. In this guide, I am going to be discussing what HTTP is, how it came to be, where it is today and how did we get here.
|
||||
|
||||
### What is HTTP?
|
||||
@@ -93,9 +71,9 @@ Three-way handshake in its simplest form is that all the `TCP` connections begin
|
||||
- `SYN ACK` - Server acknowledges the request by sending an `ACK` packet back to the client which is made up of a random number, let's say `y` picked up by server and the number `x+1` where `x` is the number that was sent by the client
|
||||
- `ACK` - Client increments the number `y` received from the server and sends an `ACK` packet back with the number `y+1`
|
||||
|
||||
Once the three-way handshake is completed, the data sharing between the client and server may begin. It should be noted that the client may start sending the application data as soon as it dispatches the last `ACK` packet but the server will still have to wait for the `ACK` packet to be received in order to fulfill the request.
|
||||
Once the three-way handshake is completed, the data sharing between the client and server may begin. It should be noted that the client may start sending the application data as soon as it dispatches the last `ACK` packet but the server will still have to wait for the `ACK` packet to be recieved in order to fulfill the request.
|
||||
|
||||

|
||||

|
||||
|
||||
> Please note that there is a minor issue with the image, the last `ACK` packet sent by the client to end the handshake contains only `y+1` i.e. it should have been `ACK:y+1` instead of `ACK: x+1, y+1`
|
||||
|
||||
@@ -111,11 +89,11 @@ After merely 3 years of `HTTP/1.0`, the next version i.e. `HTTP/1.1` was release
|
||||
|
||||
- **Hostname Identification** In `HTTP/1.0` `Host` header wasn't required but `HTTP/1.1` made it required.
|
||||
|
||||
- **Persistent Connections** As discussed above, in `HTTP/1.0` there was only one request per connection and the connection was closed as soon as the request was fulfilled which resulted in acute performance hit and latency problems. `HTTP/1.1` introduced the persistent connections i.e. **connections weren't closed by default** and were kept open which allowed multiple sequential requests. To close the connections, the header `Connection: close` had to be available on the request. Clients usually send this header in the last request to safely close the connection.
|
||||
- **Persistent Connections** As discussed above, in `HTTP/1.0` there was only one request per connection and the connection was closed as soon as the request was fulfilled which resulted in accute performance hit and latency problems. `HTTP/1.1` introduced the persistent connections i.e. **connections weren't closed by default** and were kept open which allowed multiple sequential requests. To close the connections, the header `Connection: close` had to be available on the request. Clients usually send this header in the last request to safely close the connection.
|
||||
|
||||
- **Pipelining** It also introduced the support for pipelining, where the client could send multiple requests to the server without waiting for the response from server on the same connection and server had to send the response in the same sequence in which requests were received. But how does the client know that this is the point where first response download completes and the content for next response starts, you may ask! Well, to solve this, there must be `Content-Length` header present which clients can use to identify where the response ends and it can start waiting for the next response.
|
||||
|
||||
> It should be noted that in order to benefit from persistent connections or pipelining, `Content-Length` header must be available on the response, because this would let the client know when the transmission completes and it can send the next request (in normal sequential way of sending requests) or start waiting for the next response (when pipelining is enabled).
|
||||
> It should be noted that in order to benefit from persistent connections or pipelining, `Content-Length` header must be available on the response, because this would let the client know when the transmission completes and it can send the next request (in normal sequential way of sending requests) or start waiting for the the next response (when pipelining is enabled).
|
||||
|
||||
> But there was still an issue with this approach. And that is, what if the data is dynamic and server cannot find the content length before hand? Well in that case, you really can't benefit from persistent connections, could you?! In order to solve this `HTTP/1.1` introduced chunked encoding. In such cases server may omit content-Length in favor of chunked encoding (more to it in a moment). However, if none of them are available, then the connection must be closed at the end of request.
|
||||
|
||||
@@ -131,9 +109,9 @@ After merely 3 years of `HTTP/1.0`, the next version i.e. `HTTP/1.1` was release
|
||||
- New status codes
|
||||
- ..and more
|
||||
|
||||
I am not going to dwell about all the `HTTP/1.1` features in this post as it is a topic in itself and you can already find a lot about it. The one such document that I would recommend you to read is [Key differences between `HTTP/1.0` and HTTP/1.1](https://www.ra.ethz.ch/cdstore/www8/data/2136/pdf/pd1.pdf) and here is the link to [original RFC](https://tools.ietf.org/html/rfc2616) for the overachievers.
|
||||
I am not going to dwell about all the `HTTP/1.1` features in this post as it is a topic in itself and you can already find a lot about it. The one such document that I would recommend you to read is [Key differences between `HTTP/1.0` and HTTP/1.1](http://www.ra.ethz.ch/cdstore/www8/data/2136/pdf/pd1.pdf) and here is the link to [original RFC](https://tools.ietf.org/html/rfc2616) for the overachievers.
|
||||
|
||||
`HTTP/1.1` was introduced in 1999 and it had been a standard for many years. Although, it improved alot over its predecessor; with the web changing everyday, it started to show its age. Loading a web page these days is more resource-intensive than it ever was. A simple webpage these days has to open more than 30 connections. Well `HTTP/1.1` has persistent connections, then why so many connections? you say! The reason is, in `HTTP/1.1` it can only have one outstanding connection at any moment of time. `HTTP/1.1` tried to fix this by introducing pipelining but it didn't completely address the issue because of the **head-of-line blocking** where a slow or heavy request may block the requests behind and once a request gets stuck in a pipeline, it will have to wait for the next requests to be fulfilled. To overcome these shortcomings of `HTTP/1.1`, the developers started implementing the workarounds, for example use of spritesheets, encoded images in CSS, single humongous CSS/Javascript files, [domain sharding](https://www.maxcdn.com/one/visual-glossary/domain-sharding-2/) etc.
|
||||
`HTTP/1.1` was introduced in 1999 and it had been a standard for many years. Although, it improved alot over its predecessor; with the web changing everyday, it started to show its age. Loading a web page these days is more resource-intensive than it ever was. A simple webpage these days has to open more than 30 connections. Well `HTTP/1.1` has persistent connections, then why so many connections? you say! The reason is, in `HTTP/1.1` it can only have one outstanding connection at any moment of time. `HTTP/1.1` tried to fix this by introducing pipelining but it didn't completely address the issue because of the **head-of-line blocking** where a slow or heavy request may block the requests behind and once a request gets stuck in a pipeline, it will have to wait for the next requests to be fulfilled. To overcome these shortcomings of `HTTP/1.1`, the developers started implementing the workarounds, for example use of spritesheets, encoded images in CSS, single humungous CSS/Javascript files, [domain sharding](https://www.maxcdn.com/one/visual-glossary/domain-sharding-2/) etc.
|
||||
|
||||
### SPDY - 2009
|
||||
|
||||
@@ -143,7 +121,7 @@ Google went ahead and started experimenting with alternative protocols to make t
|
||||
|
||||
It was seen that if we keep increasing the bandwidth, the network performance increases in the beginning but a point comes when there is not much of a performance gain. But if you do the same with latency i.e. if we keep dropping the latency, there is a constant performance gain. This was the core idea for performance gain behind `SPDY`, decrease the latency to increase the network performance.
|
||||
|
||||
> For those who don't know the difference, latency is the delay i.e. how long it takes for data to travel between the source and destination (measured in milliseconds) and bandwidth is the amount of data transferred per second (bits per second).
|
||||
> For those who don't know the difference, latency is the delay i.e. how long it takes for data to travel between the source and destination (measured in milliseconds) and bandwidth is the amount of data transfered per second (bits per second).
|
||||
|
||||
The features of `SPDY` included, multiplexing, compression, prioritization, security etc. I am not going to get into the details of SPDY, as you will get the idea when we get into the nitty gritty of `HTTP/2` in the next section as I said `HTTP/2` is mostly inspired from SPDY.
|
||||
|
||||
@@ -162,7 +140,7 @@ By now, you must be convinced that why we needed another revision of the HTTP pr
|
||||
- Request Prioritization
|
||||
- Security
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
#### 1. Binary Protocol
|
||||
@@ -171,7 +149,7 @@ By now, you must be convinced that why we needed another revision of the HTTP pr
|
||||
|
||||
##### Frames and Streams
|
||||
|
||||
HTTP messages are now composed of one or more frames. There is a `HEADERS` frame for the meta data and `DATA` frame for the payload and there exist several other types of frames (`HEADERS`, `DATA`, `RST_STREAM`, `SETTINGS`, `PRIORITY` etc) that you can check through [the `HTTP/2` specs](https:/http2.github.iohttp2-spec/#FrameTypes).
|
||||
HTTP messages are now composed of one or more frames. There is a `HEADERS` frame for the meta data and `DATA` frame for the payload and there exist several other types of frames (`HEADERS`, `DATA`, `RST_STREAM`, `SETTINGS`, `PRIORITY` etc) that you can check through [the `HTTP/2` specs](https://http2.github.io/http2-spec/#FrameTypes).
|
||||
|
||||
Every `HTTP/2` request and response is given a unique stream ID and it is divided into frames. Frames are nothing but binary pieces of data. A collection of frames is called a Stream. Each frame has a stream id that identifies the stream to which it belongs and each frame has a common header. Also, apart from stream ID being unique, it is worth mentioning that, any request initiated by client uses odd numbers and the response from server has even numbers stream IDs.
|
||||
|
||||
@@ -187,7 +165,7 @@ Since `HTTP/2` is now a binary protocol and as I said above that it uses frames
|
||||
|
||||
It was part of a separate RFC which was specifically aimed at optimizing the sent headers. The essence of it is that when we are constantly accessing the server from a same client there is alot of redundant data that we are sending in the headers over and over, and sometimes there might be cookies increasing the headers size which results in bandwidth usage and increased latency. To overcome this, `HTTP/2` introduced header compression.
|
||||
|
||||

|
||||

|
||||
|
||||
Unlike request and response, headers are not compressed in `gzip` or `compress` etc formats but there is a different mechanism in place for header compression which is literal values are encoded using Huffman code and a headers table is maintained by the client and server and both the client and server omit any repetitive headers (e.g. user agent etc) in the subsequent requests and reference them using the headers table maintained by both.
|
||||
|
||||
@@ -198,7 +176,7 @@ While we are talking headers, let me add here that the headers are still the sam
|
||||
|
||||
Server push is another tremendous feature of `HTTP/2` where the server, knowing that the client is going to ask for a certain resource, can push it to the client without even client asking for it. For example, let's say a browser loads a web page, it parses the whole page to find out the remote content that it has to load from the server and then sends consequent requests to the server to get that content.
|
||||
|
||||
Server push allows the server to decrease the round trips by pushing the data that it knows that client is going to demand. How it is done is, server sends a special frame called `PUSH_PROMISE` notifying the client that, "Hey, I am about to send this resource to you! Do not ask me for it." The `PUSH_PROMISE` frame is associated with the stream that caused the push to happen and it contains the promised stream ID i.e. the stream on which the server will send the resource to be pushed.
|
||||
Server push allows the server to decrease the roundtrips by pushing the data that it knows that client is going to demand. How it is done is, server sends a special frame called `PUSH_PROMISE` notifying the client that, "Hey, I am about to send this resource to you! Do not ask me for it." The `PUSH_PROMISE` frame is associated with the stream that caused the push to happen and it contains the promised stream ID i.e. the stream on which the server will send the resource to be pushed.
|
||||
|
||||
#### 5. Request Prioritization
|
||||
|
||||
@@ -208,10 +186,10 @@ Without any priority information, server processes the requests asynchronously i
|
||||
|
||||
#### 6. Security
|
||||
|
||||
There was extensive discussion on whether security (through `TLS`) should be made mandatory for `HTTP/2` or not. In the end, it was decided not to make it mandatory. However, most vendors stated that they will only support `HTTP/2` when it is used over `TLS`. So, although `HTTP/2` doesn't require encryption by specs but it has kind of become mandatory by default anyway. With that out of the way, `HTTP/2` when implemented over `TLS` does impose some requirements i.e. `TLS` version `1.2` or higher must be used, there must be a certain level of minimum key sizes, ephemeral keys are required etc.
|
||||
There was extensive discussion on whether security (through `TLS`) should be made mandatory for `HTTP/2` or not. In the end, it was decided not to make it mandatory. However, most vendors stated that they will only support `HTTP/2` when it is used over `TLS`. So, although `HTTP/2` doesn't require encryption by specs but it has kind of become mandatory by default anyway. With that out of the way, `HTTP/2` when implemented over `TLS` does impose some requirementsi.e. `TLS` version `1.2` or higher must be used, there must be a certain level of minimum keysizes, ephemeral keys are required etc.
|
||||
|
||||
`HTTP/2` is here and it has already [surpassed SPDY in adaption](https://caniuse.com/#search=http2) which is gradually increasing. `HTTP/2` has alot to offer in terms of performance gain and it is about time we should start using it.
|
||||
`HTTP/2` is here and it has already [surpassed SPDY in adaption](http://caniuse.com/#search=http2) which is gradually increasing. `HTTP/2` has alot to offer in terms of performance gain and it is about time we should start using it.
|
||||
|
||||
For anyone interested in further details here is the [link to specs](https:/http2.github.iohttp2-spec) and a link [demonstrating the performance benefits of `HTTP/2`](https://www.http2demo.io/).
|
||||
For anyone interested in further details here is the [link to specs](https://http2.github.io/http2-spec) and a link [demonstrating the performance benefits of `HTTP/2`](http://www.http2demo.io/).
|
||||
|
||||
And that about wraps it up. Until next time! stay tuned.
|
||||
3
content/guides/jwt-authentication.md
Normal file
3
content/guides/jwt-authentication.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/jwt-authentication.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1273375903511465990) where this image was posted.
|
||||
@@ -1,25 +1,3 @@
|
||||
---
|
||||
title: "Levels of Seniority"
|
||||
description: "How to Step Up as a Junior, Mid Level or a Senior Developer?"
|
||||
author:
|
||||
name: "Kamran Ahmed"
|
||||
url: "https://twitter.com/kamranahmedse"
|
||||
imageUrl: "/authors/kamranahmedse.jpeg"
|
||||
seo:
|
||||
title: "Levels of Seniority - roadmap.sh"
|
||||
description: "How to Step Up as a Junior, Mid Level or a Senior Developer?"
|
||||
isNew: false
|
||||
type: "textual"
|
||||
date: 2020-12-03
|
||||
sitemap:
|
||||
priority: 0.7
|
||||
changefreq: "weekly"
|
||||
tags:
|
||||
- "guide"
|
||||
- "textual-guide"
|
||||
- "guide-sitemap"
|
||||
---
|
||||
|
||||
I have been working on redoing the [roadmaps](https://roadmap.sh) – splitting the skillset based on the seniority levels to make them easier to follow and not scare the new developers away. Since the roadmaps are going to be just about the technical knowledge, I thought it would be a good idea to reiterate and have an article on what I think of different seniority roles.
|
||||
|
||||
I have seen many organizations decide the seniority of developers by giving more significance to the years of experience than they should. I have seen developers labeled "Junior" doing the work of Senior Developers and I have seen "Lead" developers who weren't even qualified to be called "Senior". The seniority of a developer cannot just be decided by their age, years of experience or technical knowledge that they have got. There are other factors in play here -- their perception of work, how they interact with their peers and how they approach problems. We discuss these three key factors in detail for each of the seniority levels below.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user