diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..91f4c5fe
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,27 @@
+name: Test Building Works
+
+on:
+ push:
+ branches-ignore:
+ - main
+ - deploy
+
+jobs:
+ test-build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v2
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '21' # Set the Node.js version
+
+ - name: Build Project
+ run: |
+ cd ./client
+ npm install typescript@4.x.x --save-dev # Force TypeScript to a compatible version
+ npm install
+ npm run build
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 00000000..9f0c0ce8
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,47 @@
+name: Deploy to Server
+
+on:
+ push:
+ branches:
+ - main
+ - deploy
+
+jobs:
+ build-and-deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v2
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '21' # Set the Node.js version
+
+ - name: Build Project
+ run: |
+ cd ./client
+ npm install typescript@4.x.x --save-dev # Force TypeScript to a compatible version
+ npm install
+ npm run build
+
+ - name: Copy files to the server
+ uses: appleboy/scp-action@master
+ with:
+ host: ${{ secrets.REMOTE_HOST }}
+ username: ${{ secrets.REMOTE_USER }}
+ key: ${{ secrets.SSH_PRIVATE_KEY }}
+ port: 22
+ source: "./client/build/*"
+ target: "/var/www/llcode.tech/html/"
+
+ - name: Reload Nginx
+ uses: appleboy/ssh-action@master
+ with:
+ host: ${{ secrets.REMOTE_HOST }}
+ username: ${{ secrets.REMOTE_USER }}
+ key: ${{ secrets.SSH_PRIVATE_KEY }}
+ port: 22
+ script: "nginx -s reload"
+
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
deleted file mode 100644
index 731e5f74..00000000
--- a/.github/workflows/rust.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: Rust
-
-on:
- push:
- branches: [ "main" ]
- pull_request:
- branches: [ "main" ]
-
-env:
- CARGO_TERM_COLOR: always
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
- - name: Build
- run: | cd ./server
- cargo build --verbose
- - name: Run tests
- run: | cd ./server
- cargo test --verbose
diff --git a/.projectile b/.projectile
new file mode 100644
index 00000000..a985be44
--- /dev/null
+++ b/.projectile
@@ -0,0 +1,11 @@
+-.dot
+-.jcs
+-.svg
+-.txt
+-.png
+-.woff2
+/fonts/
+/server/
+/public/
+/node_modules/
+
diff --git a/client/.babelrc.js b/client/.babelrc.js
new file mode 100644
index 00000000..e69de29b
diff --git a/client/.eslintignore b/client/.eslintignore
new file mode 100644
index 00000000..f59ec20a
--- /dev/null
+++ b/client/.eslintignore
@@ -0,0 +1 @@
+*
\ No newline at end of file
diff --git a/client/README.md b/client/README.md
index 58beeacc..8dfb1ce8 100644
--- a/client/README.md
+++ b/client/README.md
@@ -1,70 +1,10 @@
-# Getting Started with Create React App
-
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
-
-## Available Scripts
-
-In the project directory, you can run:
-
-### `npm start`
-
-Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
-
-The page will reload when you make changes.\
-You may also see any lint errors in the console.
-
-### `npm test`
-
-Launches the test runner in the interactive watch mode.\
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
-
-### `npm run build`
-
-Builds the app for production to the `build` folder.\
-It correctly bundles React in production mode and optimizes the build for the best performance.
-
-The build is minified and the filenames include the hashes.\
-Your app is ready to be deployed!
-
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-
-### `npm run eject`
-
-**Note: this is a one-way operation. Once you `eject`, you can't go back!**
-
-If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
-
-You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
-
-### Code Splitting
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
-
-### Analyzing the Bundle Size
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
-
-### Making a Progressive Web App
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
-
-### Advanced Configuration
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
+# Client
### Deployment
-This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
-
-### `npm run build` fails to minify
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
+```sh
+cd ./build
+npm run build
+scp -r * root@170.64.250.107:/var/www/llcode.tech/html/
+ssh root@170.64.250.107 'nginx -s reload'
+```
diff --git a/client/package-lock.json b/client/package-lock.json
index 01964fa8..fc1776e3 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -11,9 +11,12 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "dompurify": "^3.0.6",
"gsap": "^3.12.2",
"localforage": "^1.10.0",
+ "marked": "^10.0.0",
"match-sorter": "^6.3.1",
+ "prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.11.0",
@@ -25,6 +28,7 @@
"web-vitals": "^2.1.4"
},
"devDependencies": {
+ "babel-plugin-prismjs": "^2.1.0",
"nodemon": "^2.0.22"
}
},
@@ -3097,9 +3101,9 @@
}
},
"node_modules/@remix-run/router": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.8.0.tgz",
- "integrity": "sha512-mrfKqIHnSZRyIzBcanNJmVQELTnX+qagEDlcKO90RgRBVOZGSGvZKeDihTRfWcqoDn5N/NkUcwWTccnpN18Tfg==",
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz",
+ "integrity": "sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==",
"engines": {
"node": ">=14.0.0"
}
@@ -5254,6 +5258,15 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/babel-plugin-prismjs": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-prismjs/-/babel-plugin-prismjs-2.1.0.tgz",
+ "integrity": "sha512-ehzSKYfeAz4U78zi/sfwsjDPlq0LvDKxNefcZTJ/iKBu+plsHsLqZhUeGf1+82LAcA35UZGbU6ksEx2Utphc/g==",
+ "dev": true,
+ "peerDependencies": {
+ "prismjs": "^1.18.0"
+ }
+ },
"node_modules/babel-plugin-transform-react-remove-prop-types": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz",
@@ -6770,6 +6783,11 @@
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
+ "node_modules/dompurify": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.6.tgz",
+ "integrity": "sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w=="
+ },
"node_modules/domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -11944,6 +11962,17 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/marked": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-10.0.0.tgz",
+ "integrity": "sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA==",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/match-sorter": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
@@ -14868,6 +14897,14 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/prismjs": {
+ "version": "1.29.0",
+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
+ "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -15289,11 +15326,11 @@
}
},
"node_modules/react-router": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.15.0.tgz",
- "integrity": "sha512-NIytlzvzLwJkCQj2HLefmeakxxWHWAP+02EGqWEZy+DgfHHKQMUoBBjUQLOtFInBMhWtb3hiUy6MfFgwLjXhqg==",
+ "version": "6.20.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz",
+ "integrity": "sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==",
"dependencies": {
- "@remix-run/router": "1.8.0"
+ "@remix-run/router": "1.13.1"
},
"engines": {
"node": ">=14.0.0"
@@ -15303,12 +15340,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.15.0.tgz",
- "integrity": "sha512-aR42t0fs7brintwBGAv2+mGlCtgtFQeOzK0BM1/OiqEzRejOZtpMZepvgkscpMUnKb8YO84G7s3LsHnnDNonbQ==",
+ "version": "6.20.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.1.tgz",
+ "integrity": "sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==",
"dependencies": {
- "@remix-run/router": "1.8.0",
- "react-router": "6.15.0"
+ "@remix-run/router": "1.13.1",
+ "react-router": "6.20.1"
},
"engines": {
"node": ">=14.0.0"
@@ -20513,9 +20550,9 @@
}
},
"@remix-run/router": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.8.0.tgz",
- "integrity": "sha512-mrfKqIHnSZRyIzBcanNJmVQELTnX+qagEDlcKO90RgRBVOZGSGvZKeDihTRfWcqoDn5N/NkUcwWTccnpN18Tfg=="
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz",
+ "integrity": "sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q=="
},
"@rollup/plugin-babel": {
"version": "5.3.1",
@@ -22134,6 +22171,13 @@
"@babel/helper-define-polyfill-provider": "^0.3.3"
}
},
+ "babel-plugin-prismjs": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-prismjs/-/babel-plugin-prismjs-2.1.0.tgz",
+ "integrity": "sha512-ehzSKYfeAz4U78zi/sfwsjDPlq0LvDKxNefcZTJ/iKBu+plsHsLqZhUeGf1+82LAcA35UZGbU6ksEx2Utphc/g==",
+ "dev": true,
+ "requires": {}
+ },
"babel-plugin-transform-react-remove-prop-types": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz",
@@ -23238,6 +23282,11 @@
"domelementtype": "^2.2.0"
}
},
+ "dompurify": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.6.tgz",
+ "integrity": "sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w=="
+ },
"domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -26990,6 +27039,11 @@
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
"integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw=="
},
+ "marked": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-10.0.0.tgz",
+ "integrity": "sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA=="
+ },
"match-sorter": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
@@ -28813,6 +28867,11 @@
}
}
},
+ "prismjs": {
+ "version": "1.29.0",
+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
+ "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="
+ },
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -29131,20 +29190,20 @@
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
},
"react-router": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.15.0.tgz",
- "integrity": "sha512-NIytlzvzLwJkCQj2HLefmeakxxWHWAP+02EGqWEZy+DgfHHKQMUoBBjUQLOtFInBMhWtb3hiUy6MfFgwLjXhqg==",
+ "version": "6.20.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz",
+ "integrity": "sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==",
"requires": {
- "@remix-run/router": "1.8.0"
+ "@remix-run/router": "1.13.1"
}
},
"react-router-dom": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.15.0.tgz",
- "integrity": "sha512-aR42t0fs7brintwBGAv2+mGlCtgtFQeOzK0BM1/OiqEzRejOZtpMZepvgkscpMUnKb8YO84G7s3LsHnnDNonbQ==",
+ "version": "6.20.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.1.tgz",
+ "integrity": "sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==",
"requires": {
- "@remix-run/router": "1.8.0",
- "react-router": "6.15.0"
+ "@remix-run/router": "1.13.1",
+ "react-router": "6.20.1"
}
},
"react-scripts": {
diff --git a/client/package.json b/client/package.json
index 11646300..651ee777 100644
--- a/client/package.json
+++ b/client/package.json
@@ -6,9 +6,12 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "dompurify": "^3.0.6",
"gsap": "^3.12.2",
"localforage": "^1.10.0",
+ "marked": "^10.0.0",
"match-sorter": "^6.3.1",
+ "prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.11.0",
@@ -44,6 +47,7 @@
]
},
"devDependencies": {
+ "babel-plugin-prismjs": "^2.1.0",
"nodemon": "^2.0.22"
}
}
diff --git a/client/src/App.css b/client/src/App.css
index 470b02aa..546e6a9b 100644
--- a/client/src/App.css
+++ b/client/src/App.css
@@ -1,5 +1,8 @@
+
:root {
--page-top-margin: 68px;
+ --navbar-height: 68px;
+
--page-bottom-margin: 120px;
--common-card-margin: 10vh;
@@ -17,20 +20,10 @@
--dark-mode-background-color: #0d0d0d;
--zenburn-background: #3F3F3F;
--zenburn-foreground: #DCDCCC;
-
- --phone-screen-media-selector-width-threshold: 1000px;
-}
-
-body {
- background: #EEE;
- font-family: 'Proxima Nova';
- overflow-x: hidden
}
-.App {
- text-align: center;
-}
+.App {}
.App-logo {
height: 40vmin;
@@ -125,6 +118,33 @@ a {
flex-direction: column;
justify-content: flex-start;
align-items: center;
+ position: relative;
+ background-image: radial-gradient(rgba(194, 194, 194, 1) 1px, rgba(194, 194, 194, 0) 0);
+ background-size: 8px 8px;
+ background-position: -19px -19px;
+ animation: background-scroll 8s linear infinite;
+}
+
+@keyframes background-scroll {
+ 0% {
+ background-position: -19px -19px;
+ }
+ 100% {
+ background-position: 19px 19px;
+ }
+}
+
+.grid-background--line {
+ background-image:
+ linear-gradient(to right, #EEE 1px, transparent 1px),
+ linear-gradient(to bottom, #EEE 1px, transparent 1px);
+}
+
+
+.grid-background--dot {
+ background-image: radial-gradient(#CCC 1px, transparent 0);
+ background-size: 4px 4px;
+ background-position: -19px -19px;
}
.fixed {
@@ -135,7 +155,23 @@ a {
-webkit-transform: translate(-50%, -50%) !important;
}
+/* Common CSS Without Tailwind */
+
+.flex {
+ display: flex;
+}
+.flex-col {
+ flex-direction: column;
+}
+
+.justify-center {
+ justify-content: center;
+}
+
+.items-center {
+ align-items: center;
+}
.flex-column-centered-centered {
display: flex;
@@ -157,4 +193,46 @@ a {
user-select: none;
}
+.border-1-solid-black {
+ border: 1px solid black
+}
+
+.w-full {
+ width: 100%;
+}
+.w-80 {
+ width: 80%;
+}
+
+.w-half {
+ width: 50%;
+}
+
+.translucent-white {
+ background: rgba(255, 255, 255, 0.2)
+}
+
+
+.box-shadow-medium {
+ box-shadow: 1px 1px 2px rgba(139, 101, 77, 0.2), /* Bottom shadow */
+ 1px 1px 2px rgba(139, 101, 77, 0.2); /* Right shadow */
+}
+
+.box-shadow {
+ box-shadow: .5px .5px 1px rgba(139, 101, 77, 0.2), /* Bottom shadow */
+ .5px .5px 1px rgba(139, 101, 77, 0.2); /* Right shadow */
+}
+
+/*****************************************************************************/
+/* Global Animation Keyframes */
+/*****************************************************************************/
+
+@keyframes loading {
+ 0% {
+ background-position: 200% 0;
+ }
+ 100% {
+ background-position: -200% 0;
+ }
+}
diff --git a/client/src/App.tsx b/client/src/App.tsx
index e2506da0..736706ac 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -12,7 +12,6 @@ import LogInPage from "./pages/LogInPage/LogInPage";
import RegisterPage from "./pages/RegisterPage/RegisterPage";
import React, { useState, useEffect } from 'react';
import { useNavigate, BrowserRouter, Route, Routes } from 'react-router-dom';
-import ObservedComponent from "./components/ObservedComponent/ObservedComponent";
import './App.css';
interface IAppStateInterface {
@@ -25,6 +24,7 @@ const RedirectToRoot = (props: { link: string }): React.ReactElement<{ link: str
React.useEffect(() => {
navigate(props.link);
}, [navigate]);
+
return null;
}
@@ -54,6 +54,8 @@ function App() {
useEffect(() => {
let scrollTimeout: NodeJS.Timeout;
+ const timeToCheckScrollingHasStoppedMiliseconds = 2;
+
scrollTimeout = setInterval(() => {
if (appState.scrolling === true) {
setAppState(prevState => ({
@@ -61,7 +63,7 @@ function App() {
scrolling: false
}));
}
- }, 10);
+ }, timeToCheckScrollingHasStoppedMiliseconds);
return () => {
window.clearInterval(scrollTimeout);
@@ -76,9 +78,8 @@ function App() {
-
- } />
+
+ } />
} />
} />
} />
diff --git a/client/src/components/Card/Card.css b/client/src/components/Card/Card.css
index c94fa066..bb55173d 100644
--- a/client/src/components/Card/Card.css
+++ b/client/src/components/Card/Card.css
@@ -54,6 +54,8 @@
.card-item {
text-align: left;
background: transparent;
+ background: rgba(200, 200, 200, .1);
+ backdrop-filter: blur(2px);
border: none;
border-bottom: 1px solid #CCC;
padding: 40px 0 40px 2vw;
@@ -61,13 +63,11 @@
border-radius: .5vh;
width: 90%;
max-width: 1000px;
- transition: all .2s ease-in;
+ transition: all .1s ease-in;
opacity: 0;
transform: translateY(100px) rotate(-3deg);
}
-.card-item
-
.card-item__heading {
margin: 2px 0;
padding: 10px 0;
diff --git a/client/src/components/Card/Card.tsx b/client/src/components/Card/Card.tsx
index 6295357a..3c094d8f 100644
--- a/client/src/components/Card/Card.tsx
+++ b/client/src/components/Card/Card.tsx
@@ -2,53 +2,34 @@ import { Component, createRef, RefObject } from "react";
import "./Card.css";
import { NavLink } from "react-router-dom";
import { cardGradientEffect } from "../../components/Utility/MouseUtility";
-import { truncateTextBody, stripAwayHashSymbols, isoDateFormatToString } from "../../components/Utility/StringUtility";
+import { isoDateFormatToString } from "../../components/Utility/StringUtility";
import TagCloud from "../TagCloud/TagCloud";
-import SkeletonImage from "../SkeletonComponents/SkeletonImage/SkeletonImage";
-import ImageRepository from "../../repositories/ImageRepository";
+import Image from "../Image/Image";
import ICardProps from "./Interface/ICardProps";
import ICardState from "./Interface/ICardState";
import DynamicLoadQueue from "../../stores/DynamicLoadQueue/DynamicLoadQueue";
class Card extends Component {
iframePreviewRef = createRef();
- imageRepository: ImageRepository;
cardItemRef: RefObject;
dynamicLoadQueue: DynamicLoadQueue;
- static defaultProps = {
- defaultImage: "http://llcode.tech/api/image/651942aaf9b642fb30be59ae",
- defaultImageId: "651942aaf9b642fb30be59ae",
- defaultAuthorImage: "http://llcode.tech/api/image/65194be0f9b642fb30be59af",
- defaultAuthorImageId: "65194be0f9b642fb30be59af"
- };
-
constructor(props: ICardProps) {
super(props);
- this.imageRepository = ImageRepository.getInstance();
this.cardItemRef = createRef();
this.dynamicLoadQueue = DynamicLoadQueue.getInstance();
this.state = {
fetchedImageUrl: null,
- fetchedAuthorImageUrl: Card.defaultProps.defaultAuthorImage
};
}
componentDidMount() {
- this.updateImage();
-
if (this.cardItemRef.current) {
this.dynamicLoadQueue.addToQueue(this.cardItemRef.current);
}
}
- componentDidUpdate(prevProps: ICardProps) {
- if (this.props.image !== prevProps.image) {
- this.updateImage();
- }
- }
-
checkDateIsValid(): boolean {
return this.props.date_created !== "";
}
@@ -68,36 +49,15 @@ class Card extends Component {
}
}
- async updateImage() {
- try {
- const imageId = this.props.image ?? Card.defaultProps.defaultImageId;
- const authorImageId = this.props.authorImage ?? Card.defaultProps.defaultAuthorImageId;
-
- const [imageUrl, authorImageUrl] = await Promise.all([
- this.imageRepository.getImageById(imageId),
- this.imageRepository.getImageById(authorImageId)
- ]);
-
- this.setState({
- fetchedImageUrl: imageUrl,
- fetchedAuthorImageUrl: authorImageUrl
- });
-
- } catch (error) {
- console.error("Error fetching images:", error);
- }
- }
-
render() {
- const { link, authorImage, author, heading, minuteRead, tags, date_created } = this.props;
- const { fetchedImageUrl } = this.state;
+ const { link, authorImage, image, author, heading, minuteRead, tags, date_created } = this.props;
const displayMinuteRead = `${minuteRead || "X"} min read`;
const displayDateCreated = date_created ? isoDateFormatToString(new Date(date_created)) : '';
return (
-
+ {
}
{author}
@@ -105,11 +65,7 @@ class Card extends Component
{
{`${displayMinuteRead} | ${displayDateCreated}`}
- {
- fetchedImageUrl ?
-
- :
- }
+ {
}
diff --git a/client/src/components/CodingCat/ChristmasHat/ChristmasHat.css b/client/src/components/CodingCat/ChristmasHat/ChristmasHat.css
new file mode 100644
index 00000000..5048bc56
--- /dev/null
+++ b/client/src/components/CodingCat/ChristmasHat/ChristmasHat.css
@@ -0,0 +1,70 @@
+.christmas_hat {
+ display: none;
+ width: 12.5rem;
+ margin-bottom: -150px;
+ margin-right: 3.5vw;
+ position: relative;
+ z-index: 2;
+ transform: rotate(8deg)
+}
+
+@media screen and (max-width: 2000px) and (min-width: 1200px) {
+ .christmas_hat {
+ display: inline;
+ }
+}
+
+.christmas_hat__ball {
+ background: #f6f6f6;
+ border-radius: 2rem;
+ color:#f6f6f6;
+ width:8.8rem;
+ position: relative;
+ left:1rem;
+ z-index:-1;
+ border: 1px solid #CCC;
+}
+
+.christmas_hat__body {
+ position: relative;
+ left: 1.75rem;
+ width:7.43rem;
+ height: 5.6rem;
+ background-color: #ee2b47;
+ border-radius: 300% 0% 0%;
+ z-index:-3;
+}
+
+.christmas_hat__ball {
+ width:2rem;
+ height:2rem;
+ background:#f6f6f6;
+ border-radius:50%;
+ position:relative;
+ left:69%;
+ top:1rem;
+ z-index:-1;
+ border: .1px solid #CCC;
+}
+
+.christmas_hat__circle {
+ width:3rem;
+ height:5.6rem;
+ background:#F9F9F9;
+ border-radius:50%;
+ top:25%;
+ left:65%;
+ position:absolute;
+ z-index:-2;
+}
+
+.christmas_hat__base {
+ background: #f6f6f6;
+ border-radius: 2rem;
+ color:#f6f6f6;
+ width:8.8rem;
+ position: relative;
+ left:1rem;
+ z-index:-1;
+ border: .1px solid #CCC;
+}
diff --git a/client/src/components/CodingCat/ChristmasHat/ChristmasHat.tsx b/client/src/components/CodingCat/ChristmasHat/ChristmasHat.tsx
new file mode 100644
index 00000000..0ed2144d
--- /dev/null
+++ b/client/src/components/CodingCat/ChristmasHat/ChristmasHat.tsx
@@ -0,0 +1,21 @@
+import { Component } from "react";
+import "./ChristmasHat.css";
+
+class ChristmasHat extends Component<{}, {}> {
+ constructor(props: {}) {
+ super(props);
+ }
+
+ render() {
+ return (
+
+ )
+ }
+}
+
+export default ChristmasHat;
diff --git a/client/src/components/CodingCat/CodingCat.css b/client/src/components/CodingCat/CodingCat.css
index 9428d485..a3004214 100644
--- a/client/src/components/CodingCat/CodingCat.css
+++ b/client/src/components/CodingCat/CodingCat.css
@@ -1,65 +1,72 @@
:root {
- --bg: #1a1e2d;
- --green: #a5ea9b;
- --pink: #ff61d8;
- --blue: #569cfa;
- --orange: #ffcc81;
- --cyan: #7ed1e2;
+--bg: #1a1e2d;
+--green: #a5ea9b;
+--pink: #ff61d8;
+--blue: #569cfa;
+--orange: #ffcc81;
+--cyan: #7ed1e2;
}
.coding-cat-container>svg {
- height: 100%;
- width: 100%;
- max-width: 450px;
- overflow: visible;
+height: 100%;
+width: 100%;
+max-width: 450px;
+overflow: visible;
+}
+
+.coding-cat-container {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
}
@media screen and (max-width: 768px) {
- .coding-cat-container {
- transform: scale(0.5);
- }
+.coding-cat-container {
+transform: scale(0.5);
+}
+
}
#coding-cat {
- fill: var(--bg);
- stroke-linecap: round;
- stroke-linejoin: round;
- stroke-width: 4;
+fill: var(--bg);
+stroke-linecap: round;
+stroke-linejoin: round;
+stroke-width: 4;
}
#coding-cat .laptop-cover, #coding-cat .headphone .band {
- fill: none;
+fill: none;
}
#coding-cat .paw, #coding-cat .head {
- stroke: var(--orange);
+stroke: var(--orange);
}
#coding-cat .laptop-keyboard {
- stroke-width: 2;
+stroke-width: 2;
}
#coding-cat .terminal-code {
- stroke-width: 5;
+stroke-width: 5;
}
#coding-cat .music .note, #coding-cat .laptop-base, #coding-cat .laptop-cover, #coding-cat .paw .pads {
- stroke: var(--pink);
+stroke: var(--pink);
}
#coding-cat .table line, #coding-cat .headphone .band, #coding-cat .headphone .speaker path:nth-child(3) {
- stroke: var(--green);
+stroke: var(--green);
}
#coding-cat .terminal-frame, #coding-cat .laptop-keyboard, #coding-cat .headphone .speaker path:nth-child(2) {
- stroke: var(--blue);
+stroke: var(--blue);
}
#coding-cat .terminal-code, #coding-cat .headphone .speaker path:first-child {
- stroke: var(--cyan);
+stroke: var(--cyan);
}
.stop-music-note-animation, .stop-music-note-animation * {
- animation-play-state: paused !important;
- -webkit-animation-play-state: paused !important;
+animation-play-state: paused !important;
+-webkit-animation-play-state: paused !important;
}
diff --git a/client/src/components/CodingCat/CodingCat.tsx b/client/src/components/CodingCat/CodingCat.tsx
index a3d86bde..efecbfcd 100644
--- a/client/src/components/CodingCat/CodingCat.tsx
+++ b/client/src/components/CodingCat/CodingCat.tsx
@@ -5,6 +5,8 @@ import { gsap } from 'gsap';
import ICodingCatProps from './Interface/ICodingCatProps';
import ICodingCatState from './Interface/ICodingCatState';
+import ChristmasHat from "./ChristmasHat/ChristmasHat";
+
class CodingCat extends Component {
constructor(props: ICodingCatProps) {
super(props);
diff --git a/client/src/components/ExperienceSection/ExperienceSection.css b/client/src/components/ExperienceSection/ExperienceSection.css
index 30460acf..ff6cf396 100644
--- a/client/src/components/ExperienceSection/ExperienceSection.css
+++ b/client/src/components/ExperienceSection/ExperienceSection.css
@@ -1,5 +1,4 @@
.experience-section-content-container {
- width: auto;
padding-left: 40vw;
width: min-content;
display: inline-flex;
@@ -14,22 +13,20 @@
position: absolute;
z-index: -1;
top: 50%;
- border-top: 5px dotted #aeaeae;
+ border-top: 1px solid #aeaeae;
}
.experience-section-parent-container {
overflow-x: hidden;
- text-align: left;
- position: -webkit-sticky;
position: sticky;
- top: 1%;
+ top: var(--navbar-height);
+ height: calc(95.5vh - var(--navbar-height));
}
.experience-section-wrapper {
width: 100%;
display: flex;
justify-content: center;
- text-align: left;
margin-bottom: var(--common-card-margin);
}
@@ -37,23 +34,13 @@
flex-shrink: 0; /* Prevents children from shrinking if space is tight */
}
-.experience-section-timeline-line {
- width: calc(400px * 12);
- height: 10px;
- background: #000;
- display: none;
-}
-
.experience-section-container {
width: calc(var(--home-container-width) - 3vw);
- max-width: calc( var(--home-container-max-width) - 3vw);
+ max-width: calc(var(--home-container-max-width) - 3vw);
display: flex;
flex-direction: column;
- width: calc(var(--home-container-width) - 3vw);
}
.experience-section-card.below {
margin-top: 40vh;
}
-
-
diff --git a/client/src/components/ExperienceSection/ExperienceSection.tsx b/client/src/components/ExperienceSection/ExperienceSection.tsx
index b361f875..b638ae4b 100644
--- a/client/src/components/ExperienceSection/ExperienceSection.tsx
+++ b/client/src/components/ExperienceSection/ExperienceSection.tsx
@@ -49,6 +49,7 @@ class ExperienceSection extends Component h2,
+.experience-section-card__date,
+.experience-section-card__job-title {
+ align-self: flex-start;
+ color: #555; /* Combined color property */
+}
+
+.experience-section-card__detailed-text {
+ text-align: center;
+ font-size: 13px;
+ width: 80%;
+ color: #555; /* Moved color property here to avoid repetition */
+}
+
+.experience-section-card__location {
+ display: flex;
+ font-size: 9px;
+ text-align: left;
+ margin-top: min(10px, 1vh);
+ flex-direction: row;
+ justify-content: center;
align-items: center;
+}
+
+.experience-section-card__location > svg {
+ color: #000;
+ font-size: 25px;
+}
+
+.experience-section-card__location > * {
+ margin: 0 5px;
+}
+
+.experience-section-card__image-wrapper {
+ display: flex;
+ justify-content: center;
+}
+
+.experience-section-card__image-wrapper--image,
+.experience-section-card__image-wrapper--experience {
+ display: flex;
+}
+
+.experience-section-card__image-wrapper--image {
+ height: 80%;
+}
+
+.experience-section-card__image-wrapper--experience {
+ height: 50%;
+ align-items: center;
+}
+
+.experience-section-card__image {
max-height: 80px;
border-radius: 10px;
+ margin-top: 10px;
}
+@media screen and (max-height: 850px) {
+ .experience-section-card {
+ width: 180px;
+ height: 200px;
+ }
-@media screen and (max-width: 1000px) {
.experience-section-card h2 {
font-size: 12px;
}
- .experience-section-card .light-black-text {
+
+ .experience-section-card div {
font-size: 10px;
color: #333;
font-weight: 200;
}
- .experience-section-card {
- width: 180px;
- height: 160px;
+ .experience-section-card__location {
+ display: none;
}
}
diff --git a/client/src/components/ExperienceSection/ExperienceSectionEvent/ExperienceSectionEvent.tsx b/client/src/components/ExperienceSection/ExperienceSectionEvent/ExperienceSectionEvent.tsx
index c9252ef2..db81e289 100644
--- a/client/src/components/ExperienceSection/ExperienceSectionEvent/ExperienceSectionEvent.tsx
+++ b/client/src/components/ExperienceSection/ExperienceSectionEvent/ExperienceSectionEvent.tsx
@@ -7,52 +7,53 @@ const ExperienceSectionEvent: React.FC<{ item: ExperienceSectionItem, index: num
const displayIsImage = item.display === "IMAGE";
const defaultDisplay = item.display === "NORMAL" || !item.display;
+ const experienceSectionCardIndexIsEvenNumber = index % 2 === 0;
+
+ const experienceSectionCardClassName = ["card experience-section-card"];
+
+ experienceSectionCardIndexIsEvenNumber ? experienceSectionCardClassName.push("above")
+ : experienceSectionCardClassName.push("below");
+
+ const imageWrapperclassName = ["experience-section-card__image-wrapper"];
+
+ displayIsImage ? imageWrapperclassName.push("experience-section-card__image-wrapper--image")
+ : imageWrapperclassName.push("experience-section-card__image-wrapper--experience");
+
+ const experienceSectionCardTextExperienceBody = (): React.ReactElement => (
+
+
{item.cardTitle}
+
{item.dateTime}
+
{item.cardSubtitle}
+
+ )
+
+ const experienceSectionCardTextImageBody = (): React.ReactElement => (
+
+
{item.cardDetailedText}
+
+ {item.location && (
)}
+
{item.location}
+
+
+ )
+
return (
-
- {
- defaultDisplay && (
- <>
-
{item.cardTitle}
-
{item.dateTime}
-
{item.cardSubtitle}
- >
- )
- }
- {
- displayIsImage && (
-
-
- {item.location && (
)}
-
{item.location}
-
- {item.cardDetailedText}
-
- )
- }
-
-
-
+ className={experienceSectionCardClassName.join(" ")}>
+
+ {defaultDisplay && experienceSectionCardTextExperienceBody()}
+
+
+
+
+ {displayIsImage && experienceSectionCardTextImageBody()}
);
}
export default ExperienceSectionEvent;
-
-
diff --git a/client/src/components/FeaturedContentSection/FeaturedContentSection.tsx b/client/src/components/FeaturedContentSection/FeaturedContentSection.tsx
index a602da62..3b3d17bb 100644
--- a/client/src/components/FeaturedContentSection/FeaturedContentSection.tsx
+++ b/client/src/components/FeaturedContentSection/FeaturedContentSection.tsx
@@ -65,7 +65,7 @@ class FeaturedContentSection extends Component
diff --git a/client/src/components/Gallery/GalleryItem/GalleryItem.css b/client/src/components/Gallery/GalleryItem/GalleryItem.css
index 709dff0e..6c184595 100644
--- a/client/src/components/Gallery/GalleryItem/GalleryItem.css
+++ b/client/src/components/Gallery/GalleryItem/GalleryItem.css
@@ -1,9 +1,9 @@
.gallery-item {
display: flex;
- transition: all .3s ease-in;
+ transition: all .1s ease-in;
flex-direction: column;
opacity: 0;
- justify-content: start;
+ justify-content: flex-start;
align-items: center;
transform: translateX(100px);
width: 400px;
@@ -13,6 +13,14 @@
margin: 10px 0;
}
+.gallery-item--no-boundary {
+ background: transparent;
+ background: rgba(200, 200, 200, .1);
+ backdrop-filter: blur(2px);
+ border: none;
+ margin: 20px 0;
+}
+
.initially-hidden {
opacity: 0;
}
@@ -82,8 +90,7 @@
.gallery-item__type {
height: 60px;
width: 120px;
- border-bottom: 1px solid #888;
- /* box-shadow: 1px 1px 1px 1px #AAA; */
+ /* border-bottom: .2px solid #888; */
background-color: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(12px);
position: absolute;
diff --git a/client/src/components/Gallery/GalleryItem/GalleryItem.tsx b/client/src/components/Gallery/GalleryItem/GalleryItem.tsx
index 0ae45866..e5a7c0e5 100644
--- a/client/src/components/Gallery/GalleryItem/GalleryItem.tsx
+++ b/client/src/components/Gallery/GalleryItem/GalleryItem.tsx
@@ -5,14 +5,13 @@ import { cardGradientEffect } from "../../../components/Utility/MouseUtility";
import "./GalleryItem.css";
import IGalleryItemProps from "./Interface/IGalleryItemProps";
import IGalleryItemState from "./Interface/IGalleryItemState";
-import ImageRepository from "../../../repositories/ImageRepository";
import { TbToolsOff } from "react-icons/tb";
import { CgWebsite } from "react-icons/cg";
import TagCloud from "../../TagCloud/TagCloud";
import DynamicLoadQueue from "../../../stores/DynamicLoadQueue/DynamicLoadQueue";
+import Image from "../../Image/Image";
class GalleryItem extends Component {
- imageRepository: ImageRepository;
galleryItemRef: RefObject;
dynamicLoadQueue: DynamicLoadQueue;
@@ -20,41 +19,15 @@ class GalleryItem extends Component {
super(props);
this.dynamicLoadQueue = DynamicLoadQueue.getInstance();
this.galleryItemRef = createRef();
- this.imageRepository = ImageRepository.getInstance();
this.state = {};
}
componentDidMount() {
- this.updateImage();
-
if (this.galleryItemRef.current) {
this.dynamicLoadQueue.addToQueue(this.galleryItemRef.current);
}
}
- componentDidUpdate(prevProps: IGalleryItemProps) {
- if (this.props.image !== prevProps.image) {
- this.updateImage();
- }
- }
-
- async updateImage() {
- try {
- const imageId = this.props.image ?? "";
-
- const [imageUrl] = await Promise.all([
- this.imageRepository.getImageById(imageId),
- ]);
-
- this.setState({
- fetchedImageUrl: imageUrl,
- });
-
- } catch (error) {
- console.error("Error fetching images:", error);
- }
- }
-
get GalleryItemTypeSegment(): React.ReactElement | undefined {
switch (this.props.type) {
case "blog":
@@ -77,6 +50,7 @@ class GalleryItem extends Component {
render() {
const style: CSSProperties = this.props.style || {};
+ const { image } = this.props;
return (
@@ -85,9 +59,9 @@ class GalleryItem extends Component {
style={style}
key={this.props.key}
onMouseMove={cardGradientEffect}
- className="gallery-item card">
+ className="gallery-item gallery-item--no-boundary card">
{this.GalleryItemTypeSegment}
-
+
{this.props.name}
{
this.props.minuteRead && this.props.dateCreated &&
diff --git a/client/src/components/HeroSection/HeroSection.css b/client/src/components/HeroSection/HeroSection.css
index d3127fa0..d52a113d 100644
--- a/client/src/components/HeroSection/HeroSection.css
+++ b/client/src/components/HeroSection/HeroSection.css
@@ -1,6 +1,6 @@
@media screen and (max-width: 1000px) {
.hero-section-badge__container {
- display: none !important;
+ /* display: none !important; */
}
.hero-section__content__left {
@@ -31,16 +31,16 @@
}
}
-@media screen and (min-width: 1000px) {
- .hero-section-badge__container {
- display: flex;
- width: 100%;
- justify-content: center;
- align-items: center;
- z-index: 1;
- }
-
+.hero-section-badge__container {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+ z-index: 1;
+ overflow-x: auto;
+}
+@media screen and (min-width: 1000px) {
.hero-section__content__left {
padding: 0 5vw 0 5vw;
z-index: 1;
diff --git a/client/src/components/HeroSection/HeroSection.tsx b/client/src/components/HeroSection/HeroSection.tsx
index ab377336..81c16035 100644
--- a/client/src/components/HeroSection/HeroSection.tsx
+++ b/client/src/components/HeroSection/HeroSection.tsx
@@ -18,7 +18,7 @@ class HeroSection extends Component {
super(props);
this.state = {
backgrounds: [],
- introduction: "I am a motivated software engineering student with a diverse array of skills and experiences, \
+ introduction: "I am a motivated software engineering grad with a diverse array of skills and experiences, \
ranging from web and mobile app development to machine learning research. \
I pride myself on my efficient time and effort management abilities and my aptitude for continuous learning.",
mainContent: {
@@ -112,13 +112,13 @@ I pride myself on my efficient time and effort management abilities and my aptit
- {/*
+ {
heroSectionState.linkToMyOtherSocialMedia.map((item: any, index: number) => (
))
- */}
+ }
diff --git a/client/src/components/Image/Image.css b/client/src/components/Image/Image.css
new file mode 100644
index 00000000..e69de29b
diff --git a/client/src/components/Image/Image.tsx b/client/src/components/Image/Image.tsx
new file mode 100644
index 00000000..5f8ff1a6
--- /dev/null
+++ b/client/src/components/Image/Image.tsx
@@ -0,0 +1,69 @@
+import React, { Component } from 'react';
+import SkeletonImage from './SkeletonImage/SkeletonImage';
+import IImageProps from './Interface/IImageProps';
+import IImageState from './Interface/IImageState';
+import ImageRepository from "../../repositories/ImageRepository";
+
+class Image extends Component {
+ imageRepository: ImageRepository;
+
+ static defaultProps = {
+ defaultImage: "http://llcode.tech/api/image/651942aaf9b642fb30be59ae",
+ defaultImageId: "651942aaf9b642fb30be59ae",
+ defaultAuthorImage: "http://llcode.tech/api/image/65194be0f9b642fb30be59af",
+ defaultAuthorImageId: "65194be0f9b642fb30be59af"
+ };
+
+ componentDidMount(): void {
+ this.updateImage();
+ }
+
+ componentDidUpdate(prevProps: IImageProps) {
+ if (this.props.src !== prevProps.src) {
+ this.updateImage();
+ }
+ }
+
+ constructor(props: IImageProps) {
+ super(props);
+
+ this.imageRepository = ImageRepository.getInstance();
+
+ this.state = {
+ fetchedImageUrl: null
+ }
+ }
+
+ async updateImage() {
+ try {
+ const imageId = this.props.src ?? Image.defaultProps.defaultImageId;
+
+ const [imageUrl] = await Promise.all([
+ this.imageRepository.getImageById(imageId),
+ ]);
+
+ this.setState({
+ fetchedImageUrl: imageUrl,
+ });
+
+ } catch (error) {
+ console.error("Error fetching images:", error);
+ }
+ }
+
+ render() {
+ const { fetchedImageUrl } = this.state;
+
+ return (
+ <>
+ {
+ fetchedImageUrl ?
+
+ :
+ }
+ >
+ )
+ }
+}
+
+export default Image;
diff --git a/client/src/components/Image/Interface/IImageProps.ts b/client/src/components/Image/Interface/IImageProps.ts
new file mode 100644
index 00000000..15e57d44
--- /dev/null
+++ b/client/src/components/Image/Interface/IImageProps.ts
@@ -0,0 +1,10 @@
+// import { JSXStyles } from 'react';
+
+class IImageProps {
+ src: string;
+ className?: string;
+ style?: any;
+ alt?: string
+}
+
+export default IImageProps;
diff --git a/client/src/components/Image/Interface/IImageState.ts b/client/src/components/Image/Interface/IImageState.ts
new file mode 100644
index 00000000..c40e6648
--- /dev/null
+++ b/client/src/components/Image/Interface/IImageState.ts
@@ -0,0 +1,5 @@
+interface IImageState {
+ fetchedImageUrl: string
+}
+
+export default IImageState;
diff --git a/client/src/components/SkeletonComponents/SkeletonImage/Interface/ISkeletonImageProps.ts b/client/src/components/Image/SkeletonImage/Interface/ISkeletonImageProps.ts
similarity index 100%
rename from client/src/components/SkeletonComponents/SkeletonImage/Interface/ISkeletonImageProps.ts
rename to client/src/components/Image/SkeletonImage/Interface/ISkeletonImageProps.ts
diff --git a/client/src/components/SkeletonComponents/SkeletonImage/SkeletonImage.css b/client/src/components/Image/SkeletonImage/SkeletonImage.css
similarity index 92%
rename from client/src/components/SkeletonComponents/SkeletonImage/SkeletonImage.css
rename to client/src/components/Image/SkeletonImage/SkeletonImage.css
index aaa8bd23..3cc65006 100644
--- a/client/src/components/SkeletonComponents/SkeletonImage/SkeletonImage.css
+++ b/client/src/components/Image/SkeletonImage/SkeletonImage.css
@@ -1,8 +1,7 @@
.image-skeleton {
--loader-background-color: #EEEEEE;
+ --loader-background-color: transparent;
--loader-highlight-color: #DEDEDE;
- width: 300px;
- height: 200px;
position: relative;
z-index: -1;
background: linear-gradient(90deg, var(--loader-background-color) 25%, var(--loader-highlight-color) 50%, var(--loader-background-color) 75%);
diff --git a/client/src/components/SkeletonComponents/SkeletonImage/SkeletonImage.tsx b/client/src/components/Image/SkeletonImage/SkeletonImage.tsx
similarity index 84%
rename from client/src/components/SkeletonComponents/SkeletonImage/SkeletonImage.tsx
rename to client/src/components/Image/SkeletonImage/SkeletonImage.tsx
index 39537592..c7983e3d 100644
--- a/client/src/components/SkeletonComponents/SkeletonImage/SkeletonImage.tsx
+++ b/client/src/components/Image/SkeletonImage/SkeletonImage.tsx
@@ -8,7 +8,7 @@ class SkeletonImage extends Component {
}
render() {
- const className = ["image-skeleton", ...this.props.class].join(" ");
+ const className = `image-skeleton ${this.props.class}`;
return (
);
diff --git a/client/src/components/Navbar/Interface/INavbarState.tsx b/client/src/components/Navbar/Interface/INavbarState.tsx
index a31c4891..c3106c8f 100644
--- a/client/src/components/Navbar/Interface/INavbarState.tsx
+++ b/client/src/components/Navbar/Interface/INavbarState.tsx
@@ -5,9 +5,9 @@ interface INavbarState {
links: Link[];
currentlyHoveredNavbarLinkName: string | null;
lastScrollY: number;
+ isNavbarHidden: boolean;
hideNavBarScrollSensitivity: number;
navBarDetached: boolean;
- showBurgerPanel: boolean;
dropdownMenuLinkDisplay: ReactNode[]
}
diff --git a/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.css b/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.css
new file mode 100644
index 00000000..5a9a9b44
--- /dev/null
+++ b/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.css
@@ -0,0 +1,62 @@
+/* Styles for screens wider than 900px */
+@media screen and (min-width: 900px) {
+ .navbar-burger, .navbar-burger-icon {
+ display: none;
+ }
+
+ .nav-burger-panel {
+ display: none;
+ }
+}
+
+/* Styles for screens narrower than 900px */
+@media screen and (max-width: 900px) {
+ .nav-burger {
+ width: 50px;
+ height: 50px;
+ margin-left: auto;
+ margin-right: 10px;
+ border-radius: 5px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ border: 0.1px solid rgba(255, 255, 255, 0.3);
+ }
+
+ .nav-burger-panel {
+ top: 0;
+ right: 0;
+ position: fixed;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ z-index: 11;
+ width: 30%;
+ padding-top: 5vh;
+ height: 100%;
+ background: rgba(0, 0, 0, .7);
+ backdrop-filter: blur(15px);
+ transition: transform 0.2s ease-in-out;
+ transform: translateY(var(--navbar-height));
+ }
+
+ .nav-burger-panel-hide {
+ transform: translate(100%, var(--navbar-height));
+ }
+
+ .burger-item {
+ color: #FFF;
+ padding: 2px;
+ margin: 2px;
+ width: 27vw;
+ border-radius: 5px;
+ text-decoration: none;
+ }
+}
+
+.detached .navbar-content .nav-burger {
+ border: 1px solid rgba(255, 255, 255, 1);
+ background: rgba(255, 255, 255, 0.5);
+}
diff --git a/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.tsx b/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.tsx
index 48659eb1..3cdd9e8a 100644
--- a/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.tsx
+++ b/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.tsx
@@ -1,7 +1,7 @@
import { Component } from 'react';
import { NavLink } from "react-router-dom";
-
import INavBurgerPanelProps from "./Interface/INavBurgerPanelProps";
+import "./NavBurgerPanel.css";
class NavBurgerPanel extends Component {
constructor(props: INavBurgerPanelProps) {
diff --git a/client/src/components/Navbar/Navbar.css b/client/src/components/Navbar/Navbar.css
index e784b63b..11084b50 100644
--- a/client/src/components/Navbar/Navbar.css
+++ b/client/src/components/Navbar/Navbar.css
@@ -3,6 +3,7 @@
opacity: 0;
transform: translateX(var(--dynamic-translate));
width: var(--navbar-item-width);
+ max-width: var(--navbar-item-max-width);
padding: var(--navbar-item-padding);
color: var(--selected-navlink-window-color);
border-radius: var(--navbar-item-border-radius);
@@ -23,10 +24,10 @@
align-items: center;
text-decoration: none;
cursor: pointer;
- color: #FFF;
color: #000;
padding: var(--navbar-item-padding);
width: var(--navbar-item-width);
+ max-width: var(--navbar-item-max-width);
transition: background .3s ease-in;
border-radius: var(--navbar-item-border-radius);
z-index: 1;
@@ -50,6 +51,7 @@
--progress-height: 3.5px;
--navbar-item-border-radius: 4px;
--navbar-item-width: 8.5vw;
+ --navbar-item-max-width: 180px;
--navbar-item-margin: 0.1vw;
--navbar-item-padding: 10px 0 10px;
--navbar-transition: background .2s ease-in-out, box-shadow .1s ease-in-out, transform .1s ease-in-out;
@@ -73,14 +75,6 @@
}
@media screen and (min-width: 900px) {
- .navbar-burger, .navbar-burger-icon {
- display: none;
- }
-
- .nav-burger-panel {
- display: none;
- }
-
.navbar-left {
padding: 10px;
display: flex;
@@ -101,9 +95,7 @@
--transition: background .2s ease-in-out;
border: .1px solid #CCC;
opacity: 0;
- display: flex;
- flex-direction: column;
- justify-content: center;
+ display: none;
align-items: center;
top: calc( var(--navbar-height) - var(--dropdown-top-offset));
position: fixed;
@@ -111,7 +103,6 @@
height: fit-content;
padding: 10px;
border-radius: 0 0 12px 12px;
- background: var(--zenburn-background); /* For dark mode */
background: rgba(250,250,250,0.8);
backdrop-filter: blur(28px);
transition: var(--transition);
@@ -141,85 +132,37 @@
display: none;
}
- .nav-burger {
- width: 50px;
- height: 50px;
- margin-left: auto;
- margin-right: 10px;
- border-radius: 5px;
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: pointer;
- /* background: rgba(255, 255, 255, 0.1); */
- border: .1px solid rgba(255, 255, 255, 0.3);
- }
-
-
.navbar-content svg {
stroke: rgba(0, 0, 0, .35);
}
-
- .nav-burger-panel {
- --navbar-height: 61px;
- top: 0;
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- align-items: center;
- z-index: 11;
- right: 0;
- position: fixed;
- width: 30%;
- padding-top: 5vh;
- height: 100%;
- background: var(--dark-mode-black);
- transition: transform .2s ease-in-out;
- }
-
- .nav-burger-panel-move-lower {
- transform: translateY(var(--navbar-height));
- }
-
- .nav-burger-panel-hide {
- transform: translate(100%, var(--navbar-height));
- }
-
- .burger-item {
- color: #FFF;
- padding: 2px;
- margin: 2px;
- width: 27vw;
- border-radius: 5px;
- text-decoration: none;
- }
-
}
.hidden {
- transform: translateY(calc(-var(--navbar-height)));
+ transform: translateY(calc(var(--navbar-height)));
transform: translateY(-59px); /* For some reason the above doesn't work so I put this here for now */
}
.detached {
position: fixed;
- backdrop-filter: blur(8px);
- background: rgba(200,200,200,.1);
+ backdrop-filter: blur(12px);
+ background: rgba(200,200,200, .4);
box-shadow: .1px .1px 1px #888;
}
.detached .navbar-content .navbar-left .navbar-item {
- color: #555;
+ color: #000;
font-weight: 700;
+ text-shadow: 1px 1px 3px rgba(255, 255, 255, 0.5), -1px -1px 3px rgba(255, 255, 255, 0.5); /* Subtle text shadow for contrast */
}
+
+
.logo {
- --primary-font: 'Proxima Nova Bold', monospace;
- --font-weight-bold: 900;
+ --font-weight-bold: 800;
--font-size-large: 20px;
display: inline-block;
- font-family: var(--primary-font);
+ font-family: 'Proxima Nova Bold',Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
font-size: var(--font-size-large);
font-weight: var(--font-weight-bold);
background: linear-gradient(
@@ -233,6 +176,7 @@
background-size: 200% 200%;
width: 220px;
background-clip: text;
+ text-align: center;
text-fill-color: transparent;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
@@ -247,11 +191,6 @@
-webkit-text-stroke-color: #1E1E1E;
}
-.detached .navbar-content .nav-burger {
- border: 1px solid rgba(255, 255, 255, 1);
- background: rgba(255, 255, 255, .5)
-}
-
.detached .navbar-content .logo {
background: linear-gradient(
90deg,
@@ -290,6 +229,7 @@
.active-link {
background: var(--dark-mode-purple-2);
color: #FEFEFE !important;
+ text-shadow: none !important; /* No need to have text shadow when background is opaque duh */
}
/*****************************************************************************/
diff --git a/client/src/components/Navbar/Navbar.tsx b/client/src/components/Navbar/Navbar.tsx
index a0fd786e..01d24acf 100644
--- a/client/src/components/Navbar/Navbar.tsx
+++ b/client/src/components/Navbar/Navbar.tsx
@@ -39,7 +39,7 @@ class NavBar extends Component {
to: "/digital_chronicles/scenic_memories",
}, {
name: "🧩 My Daily Leetcode",
- to: "/digital_chronicles/daily_leetcode",
+ to: "/digital_chronicles/blogs?tag=daily-leetcode",
},
]
},
@@ -106,18 +106,15 @@ class NavBar extends Component {
navBarDetached: false,
currentlyHoveredNavbarLinkName: null,
hideNavBarScrollSensitivity: 1,
- showBurgerPanel: false,
+ isNavbarHidden: false,
name: "~/llcode.tech",
dropdownMenuLinkDisplay: []
};
}
get navBarHeight(): number {
- const selectedNavlinkWindow = this.selectedNavlinkWindow.current!;
-
const element = this.navbar.current!;
const height = element?.getBoundingClientRect().height || 0;
- selectedNavlinkWindow.style.setProperty("--navbar-height", `${height}px`);
return height;
}
@@ -130,11 +127,13 @@ class NavBar extends Component {
const { scrollStatus } = this.props;
const { lastScrollY, hideNavBarScrollSensitivity } = this.state;
- if (scrollStatus.scrolled! - Math.max(0, lastScrollY) >= hideNavBarScrollSensitivity) {
+ if (scrollStatus.scrolled! - Math.max(0, lastScrollY) >= hideNavBarScrollSensitivity && !this.state.isNavbarHidden) {
this.hideNavBar();
- } else if (Math.max(0, lastScrollY - scrollStatus.scrolled!) >= hideNavBarScrollSensitivity) {
+ } else if (Math.max(0, lastScrollY - scrollStatus.scrolled!) >= hideNavBarScrollSensitivity && this.state.isNavbarHidden) {
this.showNavBar();
}
+
+ this.setState({ lastScrollY: scrollStatus.scrolled! });
};
listenContinuousScrolled = () => {
@@ -148,7 +147,6 @@ class NavBar extends Component {
const pageHeight = document.documentElement.scrollHeight - window.innerHeight;
this.updateScrolledProgress(scrollStatus.scrolled! / pageHeight);
- this.setState({ lastScrollY: scrollStatus.scrolled! });
};
addBurgerClickOutEventLister() {
@@ -173,7 +171,7 @@ class NavBar extends Component {
if (child !== selectedNavlinkWindow) {
child.addEventListener("mouseover", () => {
const factor = navbarLeft.children.length - index - 1;
- const translateXValue = `calc(-${factor}*( var(--navbar-item-width) + var(--navbar-item-margin)) + var(--navbar-item-margin) )`;
+ const translateXValue = `calc(-${factor}*( min(var(--navbar-item-width), var(--navbar-item-max-width)) + var(--navbar-item-margin)) + var(--navbar-item-margin) )`;
selectedNavlinkWindow.style.setProperty("--dynamic-translate", `${translateXValue}`);
});
}
@@ -193,6 +191,7 @@ class NavBar extends Component {
private updateScrollingBehavior(prevProps: INavbarProps) {
const { scrollStatus } = this.props;
+
if (scrollStatus.scrolling !== prevProps.scrollStatus.scrolling) {
this.listenDeltaScrolled();
}
@@ -221,13 +220,26 @@ class NavBar extends Component {
};
hideNavBar = () => {
+ // If the navbar is not already hidden, hide it and set the navbar height to 0px
this.navbar.current?.classList.add("hidden");
- this.burgerPanel.current?.classList.remove("nav-burger-panel-move-lower");
+ document.documentElement.style.setProperty('--navbar-height', '0px');
+
+ this.setState({
+ ...this.state,
+ isNavbarHidden: true
+ });
};
showNavBar = () => {
this.navbar.current?.classList.remove("hidden");
- this.burgerPanel.current?.classList.add("nav-burger-panel-move-lower");
+
+ // Connascence of value here /Users/blackfish/personal-portfolio/client/src/App.css:5
+ document.documentElement.style.setProperty('--navbar-height', `${this.navBarHeight}px`);
+
+ this.setState({
+ ...this.state,
+ isNavbarHidden: false
+ });
};
toggleBurgerMenu = () => {
@@ -276,7 +288,7 @@ class NavBar extends Component {
return (
<>
this.hideDropdownMenu()}
ref={this.navbar}>
diff --git a/client/src/components/ObservedComponent/ObservedComponent.tsx b/client/src/components/ObservedComponent/ObservedComponent.tsx
deleted file mode 100644
index 834d6786..00000000
--- a/client/src/components/ObservedComponent/ObservedComponent.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import React, { useEffect, useState, createRef, RefObject, Component } from 'react';
-
-interface IObservedComponentProps {
- children?: React.ReactNode;
-}
-
-class ObservedComponent extends Component
{
- private containerRef: RefObject;
- private observer: IntersectionObserver;
- private queueRef: Element[];
- constructor(props: IObservedComponentProps) {
- super(props);
- this.containerRef = createRef();
- this.queueRef = [];
- }
-
- processQueue() {
- console.log(this.queueRef);
-
- if (this.queueRef.length === 0) return;
-
- const element = this.queueRef.shift();
-
- if (element) {
- this.fadeInElement(element);
- }
-
- setTimeout(this.processQueue, 2000);
- };
-
- fadeInElement(element: Element) {
- (element as HTMLElement).style.opacity = '1';
- (element as HTMLElement).style.transition = 'opacity .1s ease-in';
- };
-
- componentDidMount(): void {
- setTimeout(() => {
- this.observer = new IntersectionObserver((entries) => {
- entries.forEach((entry) => {
- if (entry.isIntersecting && entry.target instanceof HTMLElement) {
- this.queueRef.push(entry.target);
- }
- });
- }, { threshold: 0.1 }
- );
-
- this.processQueue();
-
- const observedElements = document.querySelectorAll('.gallery-item');
-
- Array.from(observedElements).forEach((element) => this.observer.observe(element));
- }, 1000);
- }
-
- componentWillUnmount(): void {
- if (this.observer) this.observer.disconnect();
-
- }
-
- render() {
- return (
-
- {this.props.children}
-
- )
- };
-}
-
-export default ObservedComponent;
diff --git a/client/src/components/TagCloud/TagCloud.css b/client/src/components/TagCloud/TagCloud.css
index 5cbb27ef..402b74e3 100644
--- a/client/src/components/TagCloud/TagCloud.css
+++ b/client/src/components/TagCloud/TagCloud.css
@@ -4,6 +4,8 @@
min-height: 7px;
width: 91%;
flex-wrap: wrap;
+ overflow-wrap: break-word;
+ line-height: 2;
}
.card-item__tags>span {
diff --git a/client/src/components/Utility/MarkdownUtility.tsx b/client/src/components/Utility/MarkdownUtility.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/client/src/components/Utility/MouseUtility.tsx b/client/src/components/Utility/MouseUtility.ts
similarity index 100%
rename from client/src/components/Utility/MouseUtility.tsx
rename to client/src/components/Utility/MouseUtility.ts
diff --git a/client/src/components/Utility/ScrollUtility.tsx b/client/src/components/Utility/ScrollUtility.ts
similarity index 100%
rename from client/src/components/Utility/ScrollUtility.tsx
rename to client/src/components/Utility/ScrollUtility.ts
diff --git a/client/src/components/Utility/StringUtility.tsx b/client/src/components/Utility/StringUtility.ts
similarity index 100%
rename from client/src/components/Utility/StringUtility.tsx
rename to client/src/components/Utility/StringUtility.ts
diff --git a/client/src/components/Waves/Waves.css b/client/src/components/Waves/Waves.css
index 751e1af5..69ab842f 100644
--- a/client/src/components/Waves/Waves.css
+++ b/client/src/components/Waves/Waves.css
@@ -3,16 +3,16 @@
width: 200%;
height: 50px;
left: 0;
- transform: translate(-25%, 40%) scale(170%, 90%);
- top: 120%;
+ transform: translate(-25%, 40%) scale(170%, 40%);
+ top: 90%;
border-radius: 5px;
}
@media screen and (min-width: 2400px) {
.wave__container {
- transform: translate(-25%, 40%) scale(220%, 70%);
- top: 120%;
+ transform: translate(-25%, 40%) scale(210%, 40%);
+ top: 95%;
}
}
@@ -24,80 +24,71 @@
}
.wave {
- animation: drift 80s infinite linear;
opacity: 0.4;
position: absolute;
top: 4%;
left: 45%;
- border: 1px solid #0af;
width: 110%;
height: auto;
padding-bottom: 100%;
margin-left: -50%;
transform-origin: 50% 48%;
border-radius: 43%;
- transition: all 1s ease-in;
}
-.wave:hover {
- background: #0af;
+.wave.-one {
+ --target-rotation: 20deg;
+ border: 1px solid #0af;
+ transform: rotate(var(--target-rotation));
+ background-color: rgba(0, 85, 170, 0.8);
+ /* animation: drift 66s infinite linear; */
+ animation: drift 2s ease-out;
}
.wave.-three {
--red-component: 0;
--green-component: 0;
--blue-component: 0;
- --alpha-component: 0;
+ --alpha-component: .1;
+ --target-rotation: 120deg;
+ transform: rotate(var(--target-rotation));
border: .8px solid black;
- transform: rotate(20deg);
background-color: rgba(var(--red-component), var(--green-component), var(--blue-component), var(--alpha-component));
- animation: drift 66s infinite linear;
-}
-
-.wave.-three:hover {
- background: black;
+ animation: drift 1s ease-out;
}
.wave.-two {
--red-component: 255;
- --green-component: 255;
+ --green-component: 85;
--blue-component: 0;
- animation: drift 69s infinite linear;
+ --alpha-component: 0.8;
+ --target-rotation: 160deg;
opacity: 0.1;
- transform: rotate(80deg);
+ transform: rotate(var(--target-rotation));
border: 1px solid orange;
-}
-
-.wave.-two:hover {
- background: yellow;
+ background-color: rgba(var(--red-component), var(--green-component), var(--blue-component), var(--alpha-component));
+ /* animation: drift 69s infinite linear; */
+ animation: drift 3s ease-out;
}
.wave.-four {
--red-component: 73;
--green-component: 30;
--blue-component: 211;
- animation: drift 123s infinite linear;
+ --alpha-component: .9;
+ --target-rotation: 110deg;
opacity: 0.2;
- transform: rotate(360deg);
+ transform: rotate(var(--target-rotation));
border: 1px solid var(--dark-mode-purple);
-}
-
-.wave.-four:hover {
- background: var(--dark-mode-purple);
+ background-color: rgba(var(--red-component), var(--green-component), var(--blue-component), var(--alpha-component));
+ animation: drift 2s ease-out;
}
@keyframes drift {
20% {
transform: rotate(0deg);
- /* background-color: rgba(var(--red-component), var(--green-component), var(--blue-component), .2); */
- }
- 50% {
-
- transform: rotate(180deg);
- /* background-color: rgba(var(--red-component), var(--green-component), var(--blue-component), 0); */
}
100% {
- transform: rotate(360deg);
- /* background-color: rgba(var(--red-component), var(--green-component), var(--blue-component), .2); */
+ transform: rotate(--target-rotation);
}
}
diff --git a/client/src/declarations.d.ts b/client/src/declarations.d.ts
index 6769fcce..2de3889c 100644
--- a/client/src/declarations.d.ts
+++ b/client/src/declarations.d.ts
@@ -3,3 +3,6 @@ declare module "*.jpeg";
declare module "*.png";
declare module "*.svg";
declare module "*.gif";
+
+declare module "prismjs";
+declare module 'dompurify';
diff --git a/client/src/fonts/FiraCode/FiraCode-Regular.woff2 b/client/src/fonts/FiraCode/FiraCode-Regular.woff2
new file mode 100644
index 00000000..f8b63fb0
Binary files /dev/null and b/client/src/fonts/FiraCode/FiraCode-Regular.woff2 differ
diff --git a/client/src/fonts/ProximaNova/ProximaNova-Bold.woff2 b/client/src/fonts/ProximaNova/ProximaNova-Bold.woff2
new file mode 100644
index 00000000..474b9a98
Binary files /dev/null and b/client/src/fonts/ProximaNova/ProximaNova-Bold.woff2 differ
diff --git a/client/src/fonts/ProximaNova/ProximaNova-Regular.woff2 b/client/src/fonts/ProximaNova/ProximaNova-Regular.woff2
new file mode 100644
index 00000000..60ee9a9b
Binary files /dev/null and b/client/src/fonts/ProximaNova/ProximaNova-Regular.woff2 differ
diff --git a/client/src/index.css b/client/src/index.css
index 53aeeff2..2e3213b9 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -1,28 +1,34 @@
+code {
+ font-family: 'Fira Code', source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
+
@font-face {
font-family: 'Fira Code';
- src: url('fonts/FiraCode/FiraCode-Regular.ttf') format('truetype');
+ src: url('fonts/FiraCode/FiraCode-Regular.ttf') format('woff2');
}
@font-face {
font-family: 'Proxima Nova';
- src: url('fonts/ProximaNova/ProximaNova-Regular.otf') format('opentype');
+ src: url('fonts/ProximaNova/ProximaNova-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'Proxima Nova Bold';
- src: url('fonts/ProximaNova/ProximaNova-Bold.otf') format('opentype');
+ src: url('fonts/ProximaNova/ProximaNova-Bold.woff2') format('woff2');
}
body {
margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;
+ background: #EEE;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ overflow-x: hidden;
+ with: 100vw !important;
}
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
+html {
+ font-family: 'Proxima Nova', ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI, Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
}
+
+
diff --git a/client/src/pages/BlogPage/BlogContent/BlogContent.css b/client/src/pages/BlogPage/BlogContent/BlogContent.css
index 1daae2e5..c6f22a99 100644
--- a/client/src/pages/BlogPage/BlogContent/BlogContent.css
+++ b/client/src/pages/BlogPage/BlogContent/BlogContent.css
@@ -1,41 +1,55 @@
+.blog-content__side-components {
+ position: -webkit-sticky;
+ position: sticky;
+
+ left: 0;
+ top: calc(var(--navbar-height) + 15px);
+ margin-top: 7vh;
+
+ backdrop-filter: blur(2px);
+
+ font-size: min(max(12px, .5vw), 18px);
+ transition: color 1s ease-in;
+
+ height: fit-content;
+ max-height: 80vh;
+ max-width: 200px;
+ min-width: 200px;
+
+ transition: .15s top ease-in;
+}
+
.blog-content__header {
width: fit-content;
+ width: 100%;
}
.blog-content {
--padding-left-right: min( 5vw, 500px );
--padding-top-bottom: min( 3vh, 200px );
--margin-top: min(2vh, 200px);
- --margin-left: min(8vw, 500px);
+ width: 500px;
padding: var(--padding-top-bottom) var(--padding-left-right);
margin-top: var( --margin-top );
- text-align: left;
display: flex;
- flex-direction: column;
- width: 40vw;
- margin-left: var(--margin-left); /* Leave room for table of contents */
align-items: center;
+ flex-direction: column;
+ background: rgba(238, 238, 238, .1);
+ backdrop-filter: blur(2px);
+ border-radius: 12px;
+ border: .1px solid #CCC
}
.blog-content-body {
border-bottom: 1px #CCC solid;
+ width: 100%;
border-radius: .5vh;
- font-size: 16px;
- width: fit-content;
+ font-size: 1rem;
text-decoration: none;
color: #000;
transition: background .3s ease-in;
}
-.blog-content-body pre {
- font-size: max(min( 14px, 1.8vw ), 10px);
- width: 90%;
- white-space: pre-wrap; /* Allow text to wrap */
- word-wrap: break-word; /* Allow long words to be broken and wrap onto the next line */
- border: 1px solid #ccc;
- padding: 10px;
-}
-
.blog-content__header>h1 {
margin-bottom: 12px;
}
@@ -51,7 +65,7 @@
}
.blog-content__image {
- --blog-content__image-top-bottom-margin: min(10vh, 50px);
+ --blog-content__image-top-bottom-margin: min(10vh, 25px);
width: 98%;
height: min(30vh, 400px);
margin: var(--blog-content__image-top-bottom-margin) var(--blog-content__image-top-bottom-margin);
@@ -73,33 +87,50 @@
margin-right: min(10px, 3vw);
}
-.blog-content__wrapper {}
+.blog-content__wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ width: 100%;
+}
-@media screen and (max-width: 768px) {
- .blog-content-body, .blog-content__header {
- min-width: 300px;
- max-width: 800px;
- }
+.table-of-content--small-screen {
+ display: none
+}
- .blog-content {
- min-width: 90vw;
- margin-left: 0; /* no more table of contents for small sreen */
+@media screen and (max-width: 1000px) {
+ .blog-content__side-components {
+ display: none
}
-}
-@media screen and (min-width: 768px) {
- .blog-content-body, .blog-content__header {
- min-width: 500px;
- max-width: 1100px;
+ .table-of-content--small-screen {
+ display: flex
}
.blog-content {
- min-width: 500px;
+ width: 80%
}
}
-@media screen and (min-width: 1200px) {
- .blog-content {
- width: 30vw;
- }
+
+/* Default Code block wrapped inside ````` rendered by markdown renderer from Markdown */
+.code-block--native__container {
+ --header-height: 20px;
+ position: relative;
+ width: 95%;
+ font-size: max(min(14px, 1.8vw), 10px);
+ padding: 10px;
+ background: #DDD;
+ margin-top: calc(var(--header-height) * 2);
+ border-radius: 0 0 6px 6px;
+}
+
+.code-block--native {
+ margin: 0;
+ overflow-x: auto;
+ padding-bottom: .25em; /* Extra space for scrollbar */
+ box-sizing: border-box; /* Ensure padding doesn't make it wider than parent */
+}
+
+.code-block--native>code {
}
diff --git a/client/src/pages/BlogPage/BlogContent/BlogContent.tsx b/client/src/pages/BlogPage/BlogContent/BlogContent.tsx
index 1c0922c0..89ac1151 100644
--- a/client/src/pages/BlogPage/BlogContent/BlogContent.tsx
+++ b/client/src/pages/BlogPage/BlogContent/BlogContent.tsx
@@ -1,20 +1,22 @@
import { Component } from "react";
-import ReactMarkdown from "react-markdown"
+import { marked } from 'marked';
import PostRepository from "../../../repositories/PostRepository";
import { IBlogContentState } from "./Interface/IBlogContentState";
+import { isoDateFormatToString } from "../../../components/Utility/StringUtility";
+import MarkdownRenderer from "./MarkdownRenderer/MarkdownRenderer";
+import SkeletonBlogContent from "./SkeletonBlogContent/SkeletonBlogContent";
+import TagCloud from "../../../components/TagCloud/TagCloud";
import IBlogContentProps from "./Interface/IBlogContentProps";
-import remarkGfm from "remark-gfm";
import JsonToMarkdown from "./Utilities/JsonToMarkdown";
import BlogPostResponse from "../../../repositories/Response/BlogPostResponse";
import ImageRepository from "../../../repositories/ImageRepository";
-import { isoDateFormatToString } from "../../../components/Utility/StringUtility";
import TableOfContent from "./TableOfContents/TableOfContents";
+import Image from "../../../components/Image/Image";
import "./BlogContent.css";
class BlogContent extends Component {
jsonToMarkdown: JsonToMarkdown;
- defaultAuthorImage: string = "http://llcode.tech/api/image/65194be0f9b642fb30be59af";
- defaultAuthorImageId: string = "65194be0f9b642fb30be59af";
+ defaultAuthorImage: string = "http://llcode.tech/api/image/65817ae96c73ceb16ba51731";
postRepository: PostRepository;
imageRepository: ImageRepository;
@@ -26,39 +28,12 @@ class BlogContent extends Component {
this.imageRepository = ImageRepository.getInstance();
this.state = {
headings: [],
+ activeSection: [],
cache: {
fetchedImageUrl: "",
fetchedAuthorImageUrl: ""
},
- content: {
- heading: null,
- body: null,
- author: null
- }
- }
- }
-
- async updateImage() {
- try {
- const imageId = this.state.content.image?.$oid ?? "";
- const authorImageId = this.defaultAuthorImageId;
-
-
- const [imageUrl, authorImageUrl] = await Promise.all([
- this.imageRepository.getImageById(imageId),
- this.imageRepository.getImageById(authorImageId)
- ]);
-
-
- this.setState({
- cache: {
- fetchedImageUrl: imageUrl,
- fetchedAuthorImageUrl: authorImageUrl
- }
- });
-
- } catch (error) {
- console.error("Error fetching images:", error);
+ content: null
}
}
@@ -69,24 +44,20 @@ class BlogContent extends Component {
});
}
- updateBlogContentHeadings(): void {
- const regexReplaceCode = /(```.+?```)/gms
- const regexRemoveLinks = /\[(.*?)\]\(.*?\)/g
- const regXHeader = /#{1,6}.+/g
-
- const markdownWithoutLinks = this.state.content.body?.replace(regexRemoveLinks, "")
- const markdownWithoutCodeBlocks = markdownWithoutLinks.replace(regexReplaceCode, "")
- const headingsList = markdownWithoutCodeBlocks.match(regXHeader)
-
- const headings = headingsList.map(heading => {
- const level = heading.lastIndexOf('#') - heading.indexOf('#');
- return {
- title: heading.replace(/^#+\s/, ''),
- level: level
- };
- });
+ updateBlogContentHeadings() {
+ const renderer = new marked.Renderer();
+ const originalHeadingRenderer = renderer.heading.bind(renderer);
- this.setState({headings : headings});
+ let headings: {title: string, level: number}[] = [];
+
+ renderer.heading = (text, level) => {
+ headings.push({ title: text, level: level });
+ return originalHeadingRenderer(text, level);
+ };
+
+ marked(this.state.content?.body, { renderer });
+
+ this.setState({ headings: headings });
}
async getBlogContentFromQuery(): Promise {
@@ -110,12 +81,10 @@ class BlogContent extends Component {
prevState: Readonly,
snapshot?: any
): void {
- if (this.state.content.body !== prevState.content.body) {
- this.updateBlogContentHeadings();
- }
-
- if (this.state.content.image !== prevState.content.image) {
- this.updateImage();
+ if (this.state.content && this.state.content !== prevState.content) {
+ if (this.state.content.body !== prevState.content?.body) {
+ this.updateBlogContentHeadings();
+ }
}
}
@@ -133,37 +102,51 @@ class BlogContent extends Component {
const color = getTextColor(level);
return (
- {title}
+ {title}
)
});
}
+
+ renderBlogContent(): React.ReactNode {
+ const { date_created, tags, image , body, author } = this.state.content;
+ const displayDateCreated = isoDateFormatToString(new Date(date_created));
+ const imageId = image?.$oid;
+
+ return (
+
+
+
{this.state.content.heading}
+
+
+
+ {author}
+ {displayDateCreated}
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+
render() {
- const displayDateCreated = isoDateFormatToString(new Date(this.state.content.date_created));
- const authorName = this.state.content.author;
- const image = this.state.content.image && ( );
- const blogContentBody = this.state.content.body;
return (
-
-
-
-
{this.state.content.heading}
-
-
-
- {authorName}
- {displayDateCreated}
-
-
-
- {image}
-
-
-
-
+
+ {this.state.content ? this.renderBlogContent() :
}
+
)
diff --git a/client/src/pages/BlogPage/BlogContent/Interface/IBlogContentProps.tsx b/client/src/pages/BlogPage/BlogContent/Interface/IBlogContentProps.tsx
index 20eac986..c00359b0 100644
--- a/client/src/pages/BlogPage/BlogContent/Interface/IBlogContentProps.tsx
+++ b/client/src/pages/BlogPage/BlogContent/Interface/IBlogContentProps.tsx
@@ -1,4 +1,3 @@
-interface BlogContentProps {
-}
+interface BlogContentProps {}
export default BlogContentProps;
diff --git a/client/src/pages/BlogPage/BlogContent/Interface/IBlogContentState.tsx b/client/src/pages/BlogPage/BlogContent/Interface/IBlogContentState.tsx
index e82b1a3b..d78caef8 100644
--- a/client/src/pages/BlogPage/BlogContent/Interface/IBlogContentState.tsx
+++ b/client/src/pages/BlogPage/BlogContent/Interface/IBlogContentState.tsx
@@ -8,6 +8,7 @@ export type IBlogHeading = {
export interface IBlogContentState {
content: BlogPostResponse,
headings: IBlogHeading[],
+ activeSection: string[],
cache: {
fetchedImageUrl?: string,
fetchedAuthorImageUrl?: string
diff --git a/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.css b/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.css
new file mode 100644
index 00000000..f404f996
--- /dev/null
+++ b/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.css
@@ -0,0 +1,94 @@
+/* Comments and Documentation */
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: #6b7380; /* A muted grayish-blue for comments and documentation */
+}
+
+/* Punctuation */
+.token.punctuation {
+ color: #444; /* A darker gray for punctuation */
+}
+
+/* Namespace */
+.token.namespace {
+ opacity: .7;
+ color: #2e2987; /* A slightly muted blue for namespace */
+}
+
+/* Keywords and Control Flow */
+.token.keyword {
+ color: #0077b6; /* A vibrant blue for keywords */
+}
+
+/* Properties, Tags, and Constants */
+.token.property,
+.token.tag,
+.token.constant {
+ color: #e94560; /* A bright red for properties, tags, and constants */
+}
+
+/* Strings and Characters */
+.token.string,
+.token.char {
+ color: #00a17e; /* A teal-green for strings and characters */
+}
+
+/* Numbers and Booleans */
+.token.number,
+.token.boolean {
+ color: #ff6f61; /* A vivid red for numbers and booleans */
+}
+
+/* Selectors and Attribute Names */
+.token.selector,
+.token.attr-name {
+ color: #f9a03f; /* A warm orange for selectors and attribute names */
+}
+
+/* Built-in and Inserted */
+.token.builtin,
+.token.inserted {
+ color: #52cc52; /* A bright green for built-in and inserted content */
+}
+
+/* Operators, Entities, URLs, and Variables */
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string,
+.token.variable {
+ color: #9a6ded; /* A rich violet for operators, entities, URLs, and variables */
+}
+
+/* At-rules, Attribute Values, Functions, and Class Names */
+.token.atrule,
+.token.attr-value,
+.token.function,
+.token.class-name {
+ color: #3b14a7; /* A deep blue for at-rules, attribute values, functions, and class names */
+}
+
+/* Regular Expressions and Important Keywords */
+.token.regex,
+.token.important {
+ color: #ff9a41; /* An orange for regular expressions and important keywords */
+}
+
+/* Bold Text */
+.token.important,
+.token.bold {
+ font-weight: bold;
+}
+
+/* Italic Text */
+.token.italic {
+ font-style: italic;
+}
+
+/* Cursor Style */
+.token.entity {
+ cursor: help;
+}
diff --git a/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.tsx b/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.tsx
new file mode 100644
index 00000000..3f0f03f4
--- /dev/null
+++ b/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.tsx
@@ -0,0 +1,106 @@
+import React, { useEffect } from 'react';
+import { marked } from 'marked';
+import DOMPurify from 'dompurify';
+import Prism from "prismjs";
+
+// import "prismjs/themes/prism-okaidia.css";
+import 'prismjs/components/prism-javascript';
+import 'prismjs/components/prism-bash';
+import 'prismjs/components/prism-python';
+import 'prismjs/components/prism-nginx';
+import 'prismjs/components/prism-rust';
+import 'prismjs/components/prism-toml';
+
+import "./MarkdownRenderer.css";
+
+type MarkdownRendererProps = {
+ markdown: string;
+};
+
+const MarkdownRenderer: React.FC = ({ markdown }) => {
+ const renderer = new marked.Renderer();
+
+ useEffect(() => {
+ Prism.highlightAll();
+ }, [])
+
+ function getIdFromHeading(str: string): number {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ const char = str.charCodeAt(i);
+ hash = ((hash << 5) - hash) + char;
+ hash |= 0; // Convert to 32bit integer
+ }
+
+ return hash;
+ }
+
+ renderer.heading = (text, level): string => {
+ const className = `header-${level}`;
+ const id = `${getIdFromHeading(text)}`;
+
+ if (level !== 1)
+ return `${text} `;
+ else return "";
+ };
+
+ renderer.codespan = (code) => {
+ return `${code} `;
+ };
+
+ renderer.blockquote = (quote) => {
+ return `${quote} `;
+ };
+
+ // Override the code method to return a React component
+ renderer.code = (code, language) => {
+ if (language === "sh") {
+ language = "bash";
+ } else if (language === "rs") {
+ language = "rust";
+ } else if (language === "js") {
+ language = "javascript";
+ } else if (language === "py") {
+ language = "python";
+ }
+
+ return ``;
+ };
+
+ renderer.strong = (text) => {
+ return `${text} `;
+ };
+
+ renderer.em = (text) => {
+ return `${text} `;
+ };
+
+ renderer.link = (href, title, text) => {
+ const titleAttr = title ? ` title="${title}"` : '';
+ return `${text} `;
+ };
+
+ renderer.list = (body, ordered) => {
+ const type = ordered ? 'ol' : 'ul';
+ const className = ordered ? 'ordered-list' : 'unordered-list';
+ return `<${type} class="${className}">${body}${type}>`;
+ };
+
+ renderer.listitem = (text) => {
+ return `${text} `;
+ };
+
+ let html: string;
+
+ try {
+ html = DOMPurify.sanitize(marked.parse(markdown, { renderer }));
+ } catch (error) {
+ console.error('Error parsing markdown:', error);
+ return null;
+ }
+
+ return (
);
+};
+
+
+export default MarkdownRenderer;
diff --git a/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.css b/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.css
new file mode 100644
index 00000000..d1c1c294
--- /dev/null
+++ b/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.css
@@ -0,0 +1,37 @@
+.skeleton-blog-content {
+ --loader-background-color: #EEEEEE;
+ --loader-highlight-color: #DEDEDE;
+ --margin-top: min(2vh, 200px);
+ width: 40vw;
+ margin-top: var( --margin-top );
+ height: 90vh;
+ display: flex;
+ justify-content: flex-start;
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.skeleton-blog-content__line {
+ height: 50px;
+ width: 100%;
+}
+
+.skeleton-blog-content__square {
+ width: 300px;
+ height: 300px;
+}
+
+.skeleton-blog-content__item {
+ margin-bottom: 20px;
+ background: linear-gradient(90deg, var(--loader-background-color) 25%, var(--loader-highlight-color) 50%, var(--loader-background-color) 75%);
+ animation: loading 2s infinite ease-in-out;
+ position: relative;
+ background-size: 200% 100%;
+}
+
+@media screen and (max-width: 768px) {
+ .skeleton-blog-content {
+ min-width: 90vw;
+ margin-left: 0;
+ }
+}
diff --git a/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.tsx b/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.tsx
new file mode 100644
index 00000000..98df7578
--- /dev/null
+++ b/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.tsx
@@ -0,0 +1,26 @@
+import React, { Component } from "react";
+import "./SkeletonBlogContent.css";
+
+class SkeletonBlogContent extends Component {
+ constructor(props: {}) {
+ super(props);
+ }
+
+ render(): React.ReactNode {
+ return (
+
+ )
+ }
+}
+
+export default SkeletonBlogContent;
diff --git a/client/src/pages/BlogPage/BlogContent/TableOfContents/Interface/ItableOfContentsProps.ts b/client/src/pages/BlogPage/BlogContent/TableOfContents/Interface/ItableOfContentsProps.ts
index e9475599..bd80301b 100644
--- a/client/src/pages/BlogPage/BlogContent/TableOfContents/Interface/ItableOfContentsProps.ts
+++ b/client/src/pages/BlogPage/BlogContent/TableOfContents/Interface/ItableOfContentsProps.ts
@@ -1,5 +1,7 @@
import { IBlogHeading } from "../../Interface/IBlogContentState";
export default interface ItableOfContentsProps {
- headings: IBlogHeading[]
+ headings: IBlogHeading[];
+ activeSectionIds?: string[];
+ className?: string
}
diff --git a/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.css b/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.css
index 708f7eac..3fe785ce 100644
--- a/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.css
+++ b/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.css
@@ -1,34 +1,46 @@
-.blog-content__table-of-contents {
+.table-of-contents {
display: flex;
- justify-content: center;
- align-items: flex-end;
- border-right: 1px solid #888;
flex-direction: column;
- font-size: min(max(12px, .5vw), 18px);
- position: fixed;
- left: 25%;
- transform: translateX(-100%);
- top: 10vh;
- width: fit-content;
- height: min(75vh, calc(fit-content + 10px));
- transition: color 1s ease-in;
+ align-items: flex-start;
+
+ width: 100%;
+ height: 50vw;
+
+ padding: 10px;
+ overflow: auto;
+
+ overflow-x: visible;
+ overflow-y: auto
}
- .blog-content__table-of-contents span {
- width: 200px;
- text-align: right;
+.table-of-contents div {
+ text-align: left;
+ border-bottom: .1px dashed #DEDEDE;
+ cursor: pointer;
+ border-radius: 7px;
+ font-weight: bold;
+ border: 1px solid transparent;
+ transition: all .2s ease-in;
+ word-wrap: break-word;
}
- .blog-content__table-of-contents>span:hover {
+ .table-of-contents>div:hover {
color: #333;
+ border: 1px solid #333;
}
+@media screen and (max-width: 1000px) {
+ .table-of-contents {
+ height: fit-content;
+ }
+}
+
@media screen and (max-width: 1400px) {
- .blog-content__table-of-contents {
- display: none;
+ .table-of-contents {
+ left: 28%;
}
}
-.blog-content__table-of-contents>span {
+.table-of-contents>span {
cursor: pointer;
}
diff --git a/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.tsx b/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.tsx
index 64246a94..fefd9194 100644
--- a/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.tsx
+++ b/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.tsx
@@ -8,29 +8,59 @@ class TableOfContents extends Component, id: string) => {
+
+ const targetElement = document.getElementById(id);
+
+ document.documentElement.style.scrollBehavior = "smooth";
+
+ if (targetElement) {
+ targetElement.scrollIntoView({ block: 'center', behavior: 'smooth' });
+ }
+ };
renderTableOfContents(): React.ReactNode {
- function getTextColor(level: number): string {
+ const getTextColor = (level: number): string => {
const lightness = level * 20;
return `hsl(0, 0%, ${lightness}%)`;
- }
-
- const subheadings = this.props.headings?.filter(({ title, level }) => level !== 0);
+ };
+ const subheadings = this.props.headings?.filter(({ level }) => level !== 0);
return subheadings?.map(({ title, level }, idx: number) => {
- const indentation = `${level * 20}px`;
- const marginBottom = `${22 - 4.5 * (level)}px`;
+ const indentation = `${Math.max((level - 1), 0) * 20}px`;
+
+ const marginBottom = `${(22 - 4.5 * level) / 2}px`;
const color = getTextColor(level);
+ const id = this.getIdFromHeading(title);
return (
- {title}
- )
+ this.handleClick(e, id.toString())}>
+ {title}
+
+ );
});
}
render() {
+ const className = ["table-of-contents", this.props.className].join(" ");
+
return (
- {this.renderTableOfContents()}
+
+
Table of Contents:
+ {this.renderTableOfContents()}
+
)
}
}
diff --git a/client/src/pages/BlogPage/BlogPage.css b/client/src/pages/BlogPage/BlogPage.css
index aff6ea5c..c8646655 100644
--- a/client/src/pages/BlogPage/BlogPage.css
+++ b/client/src/pages/BlogPage/BlogPage.css
@@ -35,6 +35,7 @@
.blog__featured {
width: 30%;
+ position: relative;
border-left: .1px solid #888;
padding: 4vh 0 0 4vw;
}
@@ -53,6 +54,24 @@
flex-wrap: wrap;
}
+.blog__tag-container--selected {
+ position: sticky;
+ background: rgba(255, 255, 255, .2);
+ padding: min(12px, .5vw) 0;
+ border-radius: 0 0 10px 10px;
+ backdrop-filter: blur(12px);
+ z-index: 2;
+ top: var(--navbar-height);
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: flex-start;
+ width: 100%;
+ margin-bottom: 2vh;
+ flex-wrap: wrap;
+ transition: .15s top ease-in;
+}
+
@media screen and (max-width: 1000px) {
.blog__featured {
@@ -76,27 +95,27 @@
justify-content: center;
padding: 5px 9px;
cursor: pointer;
- background: #555;
- color: #EEE;
font-size: min(max(12px, .5vw), 15px);
border-radius: 4px;
- box-shadow: .2px .2px .1px #888;
- border: .1px solid #888;
+ box-shadow: none;
margin: 3px 2px;
white-space: nowrap;
text-overflow: ellipsis;
- transition: all .5s ease-in;
-}
-
-.blog__tag--unselect {
- border: .1px dashed #888;
+ transition: all .2s ease-in;
background: #EEE;
+ border: .1px dashed #888;
color: #555;
- box-shadow: none;
}
-.blog__tag:hover {
+.blog__tag--selected {
background: #555;
+ color: #EEE;
+ border: .1px solid #888;
+ box-shadow: .2px .2px .1px #888;
+}
+
+.blog__tag:hover {
+ background: #1e90ff;
border: .1px solid #888;
color: #EEE;
}
diff --git a/client/src/pages/BlogPage/BlogPage.tsx b/client/src/pages/BlogPage/BlogPage.tsx
index 2d9aaa8a..f89b7ca1 100644
--- a/client/src/pages/BlogPage/BlogPage.tsx
+++ b/client/src/pages/BlogPage/BlogPage.tsx
@@ -6,11 +6,14 @@ import BlogPostResponse from "../../repositories/Response/BlogPostResponse";
import IBlogPageProps from "./Interface/IBlogPageProps";
import Card from "../../components/Card/Card";
import GalleryItem from "../../components/Gallery/GalleryItem/GalleryItem";
+import { Link } from 'react-router-dom';
+
+class BlogPage extends Component {
+ // Put any for props because for some reaosn i can't import `RouteComponentProps` for location
-class BlogPage extends Component {
postRepository: PostRepository;
- constructor(props: IBlogPageProps) {
+ constructor(props: IBlogPageProps | any) {
super(props);
this.postRepository = PostRepository.getInstance();
@@ -73,39 +76,18 @@ class BlogPage extends Component {
});
}
- getContrastTextColor(hexColor: string): string {
- const r = parseInt(hexColor.slice(1, 3), 16);
- const g = parseInt(hexColor.slice(3, 5), 16);
- const b = parseInt(hexColor.slice(5, 7), 16);
-
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
-
- return luminance > 0.5 ? '#000' : '#FFF';
- }
-
sortPostsByDate(posts: any[]): any[] {
return posts.sort((a, b) => new Date(b.date_created).getTime() - new Date(a.date_created).getTime());
}
- stringToColour = (str: string) => {
- const seed = "ramen, noodles, sushi, pizza, gym";
-
- let hash = 0;
- str = str + seed;
+ renderPostsSortedByDateDescending = (): React.ReactNode => {
+ const selectedTags = this.currentSelectedTags;
- str.split('').forEach(char => {
- hash = char.charCodeAt(0) + ((hash << 5) - hash)
- })
- let colour = '#'
- for (let i = 0; i < 3; i++) {
- const value = (hash >> (i * 8)) & 0xff
- colour += value.toString(16).padStart(2, '0')
+ const isSubset = (array1: string[], array2: string[]) => {
+ return array1.every(item => array2.includes(item));
}
- return colour
- }
- renderPostsSortedByDateDescending = (): React.ReactNode => {
- return this.sortPostsByDate(this.state.content).map((content, idx) => (
+ return this.sortPostsByDate(this.state.content).filter(({ tags }) => isSubset(selectedTags, tags) || !selectedTags).map((content, _) => (
{
);
}
+ get currentSelectedTags(): string[] {
+ const currentSearch = window.location.search;
+ const queryParams = new URLSearchParams(currentSearch);
+ const tag = queryParams.get('tag');
+ const tagsIntoArr = tag ? tag.split(",") : [];
+ return tagsIntoArr;
+ }
+
+ renderSelectedTags = (): React.ReactNode | null => {
+ const baseUrlLink = "/digital_chronicles/blogs";
- renderTags = (): React.ReactNode | null => {
return [...this.state.allTags].map((tagName) => {
- return (
- #{tagName}
- );
+ const tagAlreadySelected = this.currentSelectedTags.includes(tagName);
+
+ if (tagAlreadySelected) {
+ let selectedTagsString: string[] = [];
+ selectedTagsString = this.currentSelectedTags.filter(tag => tag !== tagName);
+ const tagClassName = ['blog__tag', 'noselect', tagAlreadySelected ? 'blog__tag--selected' : ''].join(" ");
+
+ return (
+ #{tagName}
+ );
+ }
+ })
+ };
+
+ renderUnSelectedTags = (): React.ReactNode | null => {
+ const baseUrlLink = "/digital_chronicles/blogs";
+
+ return [...this.state.allTags].map((tagName) => {
+ const tagAlreadySelected = this.currentSelectedTags.includes(tagName);
+
+ if (!tagAlreadySelected) {
+ let selectedTagsString: string[] = [];
+ selectedTagsString = this.currentSelectedTags.concat(tagName);
+ const tagClassName = ['blog__tag', 'noselect', tagAlreadySelected ? 'blog__tag--selected' : ''].join(" ");
+
+ return (
+ #{tagName}
+ );
+ }
})
};
@@ -159,7 +176,8 @@ class BlogPage extends Component {
return (
-
{this.renderTags()}
+ {this.currentSelectedTags.length > 0 && (
{this.renderSelectedTags()}
)}
+
{this.renderUnSelectedTags()}
2023
diff --git a/client/src/stores/DynamicLoadQueue/DynamicLoadQueue.ts b/client/src/stores/DynamicLoadQueue/DynamicLoadQueue.ts
index 613cd362..dd76f333 100644
--- a/client/src/stores/DynamicLoadQueue/DynamicLoadQueue.ts
+++ b/client/src/stores/DynamicLoadQueue/DynamicLoadQueue.ts
@@ -26,7 +26,6 @@ class DynamicLoadQueue {
}
addToQueue(element: TargetObservedElement) {
- console.log(`observing ${element}`)
this.observer.observe(element);
};
@@ -55,7 +54,6 @@ class DynamicLoadQueue {
fadeInElement(element: Element) {
(element as HTMLElement).style.opacity = '1';
- // (element as HTMLElement).style.transition = 'all .5s ease-in';
(element as HTMLElement).style.transform = 'translateX(0)';
};
}