navi
This commit is contained in:
16
src/App.css
Normal file
16
src/App.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.app-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
26
src/components/MainNavigation.css
Normal file
26
src/components/MainNavigation.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*src/components/MainNavigation.css*/
|
||||||
|
.main-navigation {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-navigation ul {
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-navigation ul li {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-navigation ul li a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-navigation ul li a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
32
src/components/MainNavigation.js
Normal file
32
src/components/MainNavigation.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//src/components/MainNavigation.js
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import "./MainNavigation.css";
|
||||||
|
|
||||||
|
const MainNavigation = () => {
|
||||||
|
return (
|
||||||
|
<nav className="main-navigation">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Link to="/">Home</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/login">Login</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://mail.hangman-lab.top" target="_blank" rel="noopener noreferrer">
|
||||||
|
MailBox
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://git.hangman-lab.top" target="_blank" rel="noopener noreferrer">
|
||||||
|
Git
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainNavigation;
|
||||||
0
src/components/MarkdownContent.css
Normal file
0
src/components/MarkdownContent.css
Normal file
34
src/components/MarkdownContent.js
Normal file
34
src/components/MarkdownContent.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//src/components/MarkdownContent.js
|
||||||
|
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import {fetchWithCache} from "../utils/fetchWIthCache";
|
||||||
|
|
||||||
|
const MarkdownContent = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const [content, setContent] = useState(null);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchWithCache(`/api/markdown/${id}`)
|
||||||
|
.then((data) => setContent(data))
|
||||||
|
.catch((error) => setError(error));
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div>Error: {error}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="markdown-content">
|
||||||
|
<pre>{content}</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MarkdownContent;
|
||||||
29
src/components/SideNavigation.css
Normal file
29
src/components/SideNavigation.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*src/components/SideNavigation.css*/
|
||||||
|
.side-navigation {
|
||||||
|
width: 250px;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
padding: 1rem;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-navigation h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-navigation ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-navigation ul li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-navigation ul li a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-navigation ul li a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
72
src/components/SideNavigation.js
Normal file
72
src/components/SideNavigation.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// src/components/SideNavigation.js
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import "./SideNavigation.css";
|
||||||
|
import { fetchWithCache } from "../utils/fetchWithCache";
|
||||||
|
|
||||||
|
const SideNavigation = () => {
|
||||||
|
const [markdowns, setMarkdowns] = useState([]);
|
||||||
|
const [tree, setTree] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchWithCache("/api/markdown/")
|
||||||
|
.then((data) => {
|
||||||
|
setMarkdowns(data);
|
||||||
|
setTree(buildTree(data));
|
||||||
|
})
|
||||||
|
.catch((error) => console.log(error));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function buildTree(markdowns) {
|
||||||
|
const root = {};
|
||||||
|
|
||||||
|
markdowns.forEach((markdown) => {
|
||||||
|
const segments = markdown.path.split("/").filter(Boolean);
|
||||||
|
let current = root;
|
||||||
|
|
||||||
|
segments.forEach((segment, index) => {
|
||||||
|
if (!current[segment]) {
|
||||||
|
current[segment] =
|
||||||
|
index === segments.length - 1 ? { markdown } : {};
|
||||||
|
}
|
||||||
|
current = current[segment];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTree(node, basePath = "") {
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{Object.entries(node).map(([key, value]) => {
|
||||||
|
if (value.markdown) {
|
||||||
|
return (
|
||||||
|
<li key={value.markdown.id}>
|
||||||
|
<Link to={`/markdown/${value.markdown.id}`}>
|
||||||
|
{value.markdown.title}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
<span>{key}</span>
|
||||||
|
{renderTree(value, `${basePath}/${key}`)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="side-navigation">
|
||||||
|
<h3>Markdown Directory</h3>
|
||||||
|
{tree ? renderTree(tree) : <p>Loading...</p>}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SideNavigation;
|
||||||
24
src/utils/fetchWIthCache.js
Normal file
24
src/utils/fetchWIthCache.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export async function fetchWithCache(url, cacheKey = url, cacheExpiry = 60) {
|
||||||
|
const cachedData = localStorage.getItem(cacheKey);
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (cachedData) {
|
||||||
|
const { data, timestamp } = JSON.parse(cachedData);
|
||||||
|
if (now - timestamp < cacheExpiry * 1000) {
|
||||||
|
console.log("Cache hit for:", url);
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
console.log("Cache expired for:", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
localStorage.setItem(cacheKey, JSON.stringify({ data, timestamp: now }));
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user