feat: blog (test 1 bc im sure this isnt going to work at first)

This commit is contained in:
2023-08-23 15:25:54 +02:00
parent 2e63760d14
commit 668adb1cf5
18 changed files with 2157 additions and 24 deletions

3
.gitignore vendored
View File

@@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# various stuff
blogPosts.json

View File

@@ -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",

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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))

View File

@@ -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;

View File

@@ -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>,
)

View File

@@ -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" }]
}

View File

@@ -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
}

1834
yarn.lock

File diff suppressed because it is too large Load Diff