Skip to content

Commit 934ff42

Browse files
committed
notebook
1 parent cc787b9 commit 934ff42

File tree

10 files changed

+443
-10
lines changed

10 files changed

+443
-10
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"id": "my-first-notebook",
3+
"title": "My First Notebook",
4+
"author": "Fez Code",
5+
"date": "2025-11-15",
6+
"pages": [
7+
"This is the first page of my notebook.",
8+
"This is the second page. I can write anything I want here.",
9+
"On this page, I'll write a short story. Once upon a time, in a land far, far away...",
10+
"...there was a brave developer who wanted to create a notebook application.",
11+
"The developer worked hard and...",
12+
"...finally, created a beautiful and functional notebook for everyone to use.",
13+
"This is another page.",
14+
"And another one.",
15+
"We can have as many pages as we want.",
16+
"The end."
17+
]
18+
}

public/notebooks/notebooks.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
{
3+
"id": "my-first-notebook",
4+
"title": "My First Notebook"
5+
}
6+
]

src/components/AnimatedRoutes.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import JSONGeneratorPage from '../pages/apps/JSONGeneratorPage';
4343
import SettingsPage from '../pages/SettingsPage'; // Import SettingsPage
4444

4545
import UsefulLinksPage from '../pages/UsefulLinksPage';
46+
import NotebooksPage from "../pages/notebooks/NotebooksPage";
47+
import NotebookViewerPage from "../pages/notebooks/NotebookViewerPage";
4648

4749
const pageVariants = {
4850
initial: {
@@ -222,6 +224,34 @@ function AnimatedRoutes() {
222224
</motion.div>
223225
}
224226
/>
227+
<Route
228+
path="/notebooks"
229+
element={
230+
<motion.div
231+
initial="initial"
232+
animate="in"
233+
exit="out"
234+
variants={pageVariants}
235+
transition={pageTransition}
236+
>
237+
<NotebooksPage />
238+
</motion.div>
239+
}
240+
/>
241+
<Route
242+
path="/notebooks/:notebookId"
243+
element={
244+
<motion.div
245+
initial="initial"
246+
animate="in"
247+
exit="out"
248+
variants={pageVariants}
249+
transition={pageTransition}
250+
>
251+
<NotebookViewerPage />
252+
</motion.div>
253+
}
254+
/>
225255
<Route
226256
path="*"
227257
element={

src/components/Navbar.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect } from 'react';
22
import { Link } from 'react-router-dom';
33
import Fez from './Fez';
4-
import { SidebarIcon, UserIcon, BookOpenIcon, MagnifyingGlassIcon } from '@phosphor-icons/react';
4+
import { SidebarIcon, UserIcon, BookOpenIcon, MagnifyingGlassIcon, NotebookIcon } from '@phosphor-icons/react';
55

66
const Navbar = ({ toggleSidebar, isSidebarOpen, isSearchVisible, toggleSearch }) => {
77
const [isScrolled, setIsScrolled] = useState(false);
@@ -69,13 +69,6 @@ const Navbar = ({ toggleSidebar, isSidebarOpen, isSearchVisible, toggleSearch })
6969
<UserIcon size={24} />
7070
<span className="md:hidden lg:inline">About</span>
7171
</Link>
72-
<Link
73-
to="/blog"
74-
className="flex items-center space-x-1 text-gray-300 hover:text-white hover:bg-gray-800 px-2 py-2 rounded-md transition-colors"
75-
>
76-
<BookOpenIcon size={24} />
77-
<span className="md:hidden lg:inline">Blog</span>
78-
</Link>
7972
<button
8073
onClick={toggleSearch}
8174
className="text-gray-300 hover:text-white hover:bg-gray-800 px-2 py-2 rounded-md transition-colors"

src/components/Sidebar.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
RssIcon,
2929
SquaresFourIcon,
3030
GearSix, // Import GearSix icon
31+
NotebookIcon,
3132
} from '@phosphor-icons/react';
3233

3334
import Fez from './Fez';
@@ -197,6 +198,11 @@ const Sidebar = ({ isOpen, toggleSidebar, toggleModal }) => {
197198
<SwordIcon className="text-yellow-500" size={24} />
198199
<span>S<span className="text-yellow-500"> &amp; </span>F</span>
199200
</NavLink>
201+
202+
<NavLink to="/notebooks" className={getLinkClass}>
203+
<NotebookIcon size={24} />
204+
<span>Notebooks</span>
205+
</NavLink>
200206
</nav>
201207
)}
202208
</div>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
const NotebookCover = ({ title }) => {
4+
return (
5+
<div className="w-full h-64 bg-[#d2b48c] rounded-lg shadow-md flex items-center justify-center p-4">
6+
<h2 className="text-white text-xl font-bold text-center">{title}</h2>
7+
</div>
8+
);
9+
};
10+
11+
export default NotebookCover;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { useParams } from 'react-router-dom';
3+
import '../../styles/notebook.css';
4+
import { CaretLeft, CaretRight } from '@phosphor-icons/react';
5+
6+
const Page = ({ content, pageNumber, title }) => {
7+
return (
8+
<div className="page">
9+
<div className="page-inner">
10+
<div className="notebook-header">{title}</div>
11+
<div className="page-content">
12+
<p>{content}</p>
13+
</div>
14+
<div className="notebook-footer">Page {pageNumber}</div>
15+
</div>
16+
</div>
17+
);
18+
};
19+
20+
const NotebookCover = ({ title, author, date }) => {
21+
return (
22+
<div className="page notebook-cover">
23+
<div className="page-inner">
24+
<h2>{title}</h2>
25+
{author && <p className="author">{author}</p>}
26+
{date && <p className="date">{date}</p>}
27+
</div>
28+
</div>
29+
);
30+
};
31+
32+
const NotebookViewerPage = () => {
33+
const { notebookId } = useParams();
34+
const [notebook, setNotebook] = useState(null);
35+
const [currentPage, setCurrentPage] = useState(0);
36+
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
37+
38+
useEffect(() => {
39+
fetch(`/notebooks/${notebookId}.json`)
40+
.then(response => response.json())
41+
.then(data => setNotebook(data))
42+
.catch(error => console.error('Error fetching notebook:', error));
43+
44+
const handleResize = () => {
45+
setIsMobile(window.innerWidth < 768);
46+
};
47+
window.addEventListener('resize', handleResize);
48+
return () => window.removeEventListener('resize', handleResize);
49+
}, [notebookId]);
50+
51+
const handleNextPage = () => {
52+
if (notebook) {
53+
const pageIncrement = isMobile ? 1 : 2;
54+
setCurrentPage(prev => Math.min(prev + pageIncrement, notebook.pages.length + 1));
55+
}
56+
};
57+
58+
const handlePrevPage = () => {
59+
const pageDecrement = isMobile ? 1 : 2;
60+
setCurrentPage(prev => Math.max(prev - pageDecrement, 0));
61+
};
62+
63+
if (!notebook) {
64+
return <div>Loading notebook...</div>;
65+
}
66+
67+
const renderPages = () => {
68+
if (currentPage === 0) {
69+
return <NotebookCover title={notebook.title} author={notebook.author} date={notebook.date} />;
70+
}
71+
if (currentPage > notebook.pages.length) {
72+
return <NotebookCover title="The End" />;
73+
}
74+
75+
if (isMobile) {
76+
return <Page content={notebook.pages[currentPage - 1]} pageNumber={currentPage} title={notebook.title} />;
77+
}
78+
79+
return (
80+
<div style={{ display: 'flex', width: '100%' }}>
81+
<Page content={notebook.pages[currentPage - 1]} pageNumber={currentPage} title={notebook.title} />
82+
{currentPage < notebook.pages.length && (
83+
<Page content={notebook.pages[currentPage]} pageNumber={currentPage + 1} title={notebook.title} />
84+
)}
85+
</div>
86+
);
87+
};
88+
89+
return (
90+
<div className="notebook-container">
91+
<div className="book">
92+
{currentPage > 0 && (
93+
<div className="clickable-edge left" onClick={handlePrevPage}>
94+
<CaretLeft size={32} />
95+
</div>
96+
)}
97+
{renderPages()}
98+
{notebook && currentPage < notebook.pages.length + 1 && (
99+
<div className="clickable-edge right" onClick={handleNextPage}>
100+
<CaretRight size={32} />
101+
</div>
102+
)}
103+
</div>
104+
{notebook && (
105+
<div className="slider-container">
106+
<input
107+
type="range"
108+
min="0"
109+
max={notebook.pages.length + 1}
110+
value={currentPage}
111+
onChange={(e) => setCurrentPage(parseInt(e.target.value, 10))}
112+
className="slider"
113+
/>
114+
<div className="page-number-display">
115+
Page {currentPage} of {notebook.pages.length + 1}
116+
</div>
117+
</div>
118+
)}
119+
</div>
120+
);
121+
};
122+
123+
export default NotebookViewerPage;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import NotebookCover from "./NotebookCover";
4+
5+
const NotebooksPage = () => {
6+
const [notebooks, setNotebooks] = useState([]);
7+
8+
useEffect(() => {
9+
fetch('/notebooks/notebooks.json')
10+
.then(response => response.json())
11+
.then(data => setNotebooks(data))
12+
.catch(error => console.error('Error fetching notebooks:', error));
13+
}, []);
14+
15+
return (
16+
<div className="p-4">
17+
<h1 className="text-2xl font-bold mb-4">Notebooks</h1>
18+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
19+
{notebooks.map(notebook => (
20+
<Link key={notebook.id} to={`/notebooks/${notebook.id}`}>
21+
<NotebookCover title={notebook.title} />
22+
</Link>
23+
))}
24+
</div>
25+
</div>
26+
);
27+
};
28+
29+
export default NotebooksPage;

0 commit comments

Comments
 (0)