mirror of
https://github.com/SrIzan10/mainwebsite.git
synced 2026-06-06 00:56:58 +00:00
feat: blog (test 1 bc im sure this isnt going to work at first)
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -22,3 +22,6 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# various stuff
|
||||
blogPosts.json
|
||||
24
package.json
24
package.json
@@ -4,21 +4,39 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"dev": "node src/blogPostGenerator.js && vite",
|
||||
"build": "node src/blogPostGenerator.js;tsc;vite build",
|
||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-syntax-import-attributes": "^7.22.5",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@mdx-js/react": "^2.3.0",
|
||||
"@mdx-js/rollup": "^2.3.0",
|
||||
"@mui/material": "^5.14.5",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
"dayjs": "^1.11.9",
|
||||
"glob": "^10.3.3",
|
||||
"gray-matter": "^4.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"remark-gfm": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/mdx": "^2.0.6",
|
||||
"@types/node": "^20.5.1",
|
||||
"@types/react": "^18.0.37",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
|
||||
@@ -3,21 +3,21 @@ import React from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faDiscord, faGithub, faMastodon, faTwitter } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faBlog, faCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
// sorry peppy for naming the variable Osu instead of osu! you know camel case stuff please don't kill me or ban me but at lease give me a plushie or something the taikonator one is really nice actually but who can't pass the offer of getting pippi lol she's so fucking cute
|
||||
const [osuBeat, setOsuBeat] = React.useState(false)
|
||||
return (
|
||||
<div>
|
||||
<div className='aboutMeBox'>
|
||||
<img src='https://github.com/SrIzan10.png' height='200px' />
|
||||
<img src='https://github.com/SrIzan10.png' alt='main profile picture' height='200px' />
|
||||
<p>I'm a hobbyist developer and osu! player based on Spain who loves to open-source and to work on teams.</p>
|
||||
<p>Stalk me on social media:</p>
|
||||
<div className='icons'>
|
||||
<a href='https://github.com/SrIzan10'><FontAwesomeIcon icon={faGithub} /></a>
|
||||
<a href='https://social.srizan.dev'><FontAwesomeIcon icon={faMastodon} /></a>
|
||||
<a href='https://twitter.com/itssrizan'><FontAwesomeIcon icon={faTwitter} /></a>
|
||||
<a href='https://srizan.dev/blog'><FontAwesomeIcon icon={faBlog} /></a>
|
||||
<Link to='./blog'><FontAwesomeIcon icon={faBlog} /></Link>
|
||||
<a href='https://discord.com/users/703974042700611634'><FontAwesomeIcon icon={faDiscord} /></a>
|
||||
<a href='https://osu.ppy.sh/users/25350735'><FontAwesomeIcon icon={faCircle} onMouseEnter={() => setOsuBeat(true)} onMouseLeave={() => setOsuBeat(false)} beatFade={osuBeat} /></a>
|
||||
</div>
|
||||
|
||||
27
src/Blog.css
Normal file
27
src/Blog.css
Normal file
@@ -0,0 +1,27 @@
|
||||
/* All navbar code was refactored to now be in the BlogNavBar.css file alongside with the component. */
|
||||
|
||||
/* I adjust the card's width on the BlogPostCard.css file instead of here */
|
||||
.blogPosts {
|
||||
margin-top: 80px;
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.blogPosts > * {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 20px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #191919;
|
||||
border-radius: 20px;
|
||||
border: 6px solid transparent;
|
||||
background-clip: content-box;
|
||||
}
|
||||
27
src/Blog.tsx
Normal file
27
src/Blog.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
// Blog.js
|
||||
import './Blog.css';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import blogPosts from '../blogPosts.json'
|
||||
import BlogPostCard from "./BlogPostCard.tsx";
|
||||
import {BlogNavBar} from "./BlogNavBar.tsx";
|
||||
|
||||
function Blog() {
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>Blog | Sr Izan's website</title>
|
||||
<meta name="theme-color" content="#0d0d0d" />
|
||||
</Helmet>
|
||||
<BlogNavBar />
|
||||
<div className="blogPosts">
|
||||
{blogPosts.map((post) => {
|
||||
return (
|
||||
<BlogPostCard {...post} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Blog;
|
||||
42
src/BlogNavBar.css
Normal file
42
src/BlogNavBar.css
Normal file
@@ -0,0 +1,42 @@
|
||||
.navBar {
|
||||
width: 40vw;
|
||||
height: 60px;
|
||||
background-color: #0d0d0d;
|
||||
border-radius: 15px;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.iconContainer {
|
||||
margin-left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
.iconContainer img {
|
||||
border-radius: 50px;
|
||||
}
|
||||
.iconContainer p {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.backHomeLink {
|
||||
justify-content: flex-end;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
/* some fixes for mobile and small viewports */
|
||||
@media (max-width: 1160px) {
|
||||
.navBar {
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
border-radius: 0;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
15
src/BlogNavBar.tsx
Normal file
15
src/BlogNavBar.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import './BlogNavBar.css'
|
||||
|
||||
export function BlogNavBar(props: Props) {
|
||||
return (
|
||||
<div className={'navBar'}>
|
||||
<div className="iconContainer">
|
||||
<img src="https://github.com/SrIzan10.png" alt="main profile picture" height="50vh" />
|
||||
<p>{props.title ? props.title : 'Sr Izan\'s blog'}</p>
|
||||
</div>
|
||||
<a href={'/'} className="backHomeLink">Go back home</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type Props = { title?: string }
|
||||
14
src/BlogPost.css
Normal file
14
src/BlogPost.css
Normal file
@@ -0,0 +1,14 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap');
|
||||
|
||||
.blogPostContent {
|
||||
margin-top: 100px;
|
||||
z-index: 0;
|
||||
padding: 0 20vw 0 20vw;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
@media (max-width: 1160px) {
|
||||
.blogPostContent {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
62
src/BlogPost.tsx
Normal file
62
src/BlogPost.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useEffect, useState } from "react";
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { BlogNavBar } from "./BlogNavBar.tsx";
|
||||
import './BlogPost.css';
|
||||
|
||||
export function BlogPost() {
|
||||
const id = Number(useParams().id);
|
||||
|
||||
if (isNaN(id)) {
|
||||
// if it's not a number, redirect to the blog page
|
||||
window.location.href = '/blog';
|
||||
}
|
||||
|
||||
const [jsonData, setJsonData] = useState<BlogPostJSONResponse>({
|
||||
id: 0,
|
||||
title: '',
|
||||
description: '',
|
||||
date: '',
|
||||
fileName: '',
|
||||
fileContent: ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const jsonFetch = await import('../blogPosts.json');
|
||||
const jsonDataArray = jsonFetch.default;
|
||||
const filteredPost = jsonDataArray.filter((post) => post.id === id)[0];
|
||||
if (filteredPost) {
|
||||
setJsonData(filteredPost);
|
||||
} else {
|
||||
document.location.href = '/blog';
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [id]);
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>{jsonData.title} | Sr Izan's Blog</title>
|
||||
</Helmet>
|
||||
<BlogNavBar title={jsonData.title} />
|
||||
<div className={'blogPostContent'}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{jsonData.fileContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type BlogPostJSONResponse = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
fileName: string;
|
||||
fileContent: string;
|
||||
}
|
||||
14
src/BlogPostCard.css
Normal file
14
src/BlogPostCard.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.cardBox {
|
||||
width: 40vw;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions {
|
||||
float: right
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.cardBox {
|
||||
width: 100vw;
|
||||
}
|
||||
}
|
||||
35
src/BlogPostCard.tsx
Normal file
35
src/BlogPostCard.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import dayjs from 'dayjs'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
|
||||
import './BlogPostCard.css'
|
||||
import { Button, CardActions } from "@mui/material";
|
||||
import {Link} from "react-router-dom";
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
export default function BlogPostCard(props: Props) {
|
||||
return (
|
||||
<Card>
|
||||
<Box className={'cardBox'}>
|
||||
<CardContent>
|
||||
<Typography variant="h5">
|
||||
{props.title}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" color="text.secondary" component="div">
|
||||
{dayjs(props.date, 'DD/MM/YYYY').toDate().toLocaleDateString()}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" component="div">
|
||||
{props.description}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions className={'actions'}>
|
||||
<Link to={`/blog/${props.id}`}><Button size="small">Read</Button></Link>
|
||||
</CardActions>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
type Props = { title: string, description: string, date: string, id: number }
|
||||
14
src/blog/Hey.md
Normal file
14
src/blog/Hey.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
id: 2
|
||||
title: Welcome to my new blog!
|
||||
description: This post welcomes you to my new blog
|
||||
date: 20/08/2023
|
||||
---
|
||||
# Hey!
|
||||
|
||||
This is probably the last time I'm going to make a blog. I've made a few in the past, but I've never really stuck to them. I'm hoping that this time will be different.
|
||||
This one was made entirely from scratch using React and Markdown, initially trying to use MDX, but it was a pain to set up, and it didn't end up working in the end.
|
||||
I'm hoping to post about my projects, and maybe some other stuff too. I'm not sure yet, but I'll figure it out as I go along.
|
||||
Anyways, thank you for reading. I hope you enjoyed my UX/UI for this one!
|
||||
|
||||
PD: I need some help for making the blog text look good and readable, so hit me up on my Discord if you have any ideas.
|
||||
19
src/blogPostGenerator.js
Normal file
19
src/blogPostGenerator.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import yaml from 'js-yaml'
|
||||
import { glob } from 'glob'
|
||||
import * as fs from 'node:fs'
|
||||
import gm from 'gray-matter'
|
||||
|
||||
const data = []
|
||||
|
||||
await glob('./src/blog/**/*.md').then(async (files) => {
|
||||
for (const file of files) {
|
||||
const readFile = fs.readFileSync(file)
|
||||
const dt = gm(readFile).data
|
||||
dt.fileContent = gm(readFile).content
|
||||
dt.fileName = file.replace('src/blog/', '')
|
||||
console.log(`File ${dt.fileName} read successfully`)
|
||||
data.push(dt)
|
||||
}
|
||||
})
|
||||
|
||||
fs.writeFileSync('./blogPosts.json', JSON.stringify(data))
|
||||
@@ -1,3 +1,5 @@
|
||||
/* Yes, this is a kinda unmodified index.css file from the vite template. I like the colors and stuff so I'll keep it. */
|
||||
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
@@ -5,7 +7,7 @@
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
background-color: #1d1d1d;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
33
src/main.tsx
33
src/main.tsx
@@ -2,9 +2,40 @@ import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import Blog from "./Blog.tsx";
|
||||
import {BlogPost} from "./BlogPost.tsx";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <App />,
|
||||
},
|
||||
{
|
||||
path: "/blog",
|
||||
element: <Blog />,
|
||||
},
|
||||
{
|
||||
path: "/blog/:id",
|
||||
element: <BlogPost />
|
||||
}
|
||||
]);
|
||||
|
||||
const darkTheme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
background: {
|
||||
default: '#0d0d0d',
|
||||
paper: '#0d0d0d'
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<RouterProvider router={router} />
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "src/**/*.d.ts"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import mdx from '@mdx-js/rollup'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [react(), mdx({ baseUrl: '/blog' })],
|
||||
server: {
|
||||
port: 3000
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user