portosaurus 1.18.6 → 1.18.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -4
- package/src/img/icon.svg +19 -19
- package/src/internal/src/pages/index.js +1 -82
- package/src/internal/src/pages/notes.js +1 -84
- package/src/internal/src/pages/tasks.js +1 -310
- package/src/pages/Home.js +83 -0
- package/src/pages/Notes.js +84 -0
- package/src/pages/Tasks.js +310 -0
- package/src/utils/packageManager.js +3 -1
- package/src/config/.exports.js +0 -4
- /package/src/{internal/src/components → components}/AboutSection/index.js +0 -0
- /package/src/{internal/src/components → components}/AboutSection/styles.module.css +0 -0
- /package/src/{internal/src/components → components}/ContactSection/index.js +0 -0
- /package/src/{internal/src/components → components}/ContactSection/styles.module.css +0 -0
- /package/src/{internal/src/components → components}/ExperienceSection/index.js +0 -0
- /package/src/{internal/src/components → components}/ExperienceSection/styles.module.css +0 -0
- /package/src/{internal/src/components → components}/HeroSection/index.js +0 -0
- /package/src/{internal/src/components → components}/HeroSection/styles.module.css +0 -0
- /package/src/{internal/src/components → components}/NoteIndex/index.js +0 -0
- /package/src/{internal/src/components → components}/NoteIndex/styles.module.css +0 -0
- /package/src/{internal/src/components → components}/ProjectsSection/index.js +0 -0
- /package/src/{internal/src/components → components}/ProjectsSection/styles.module.css +0 -0
- /package/src/{internal/src/components → components}/ScrollToTop/index.js +0 -0
- /package/src/{internal/src/components → components}/ScrollToTop/styles.module.css +0 -0
- /package/src/{internal/src/components → components}/SocialLinks/index.js +0 -0
- /package/src/{internal/src/components → components}/SocialLinks/styles.module.css +0 -0
- /package/src/{internal/src/components → components}/Tooltip/index.js +0 -0
- /package/src/{internal/src/components → components}/Tooltip/styles.module.css +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portosaurus",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.8",
|
|
4
4
|
"author": "soymadip",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"description": "Complete portfolio cum personal website solution for your digital personality.",
|
|
@@ -26,14 +26,15 @@
|
|
|
26
26
|
"#utils/*": "./src/utils/*.js",
|
|
27
27
|
"#css/*": "./src/css/*.css",
|
|
28
28
|
"#pages/*": "./src/internal/src/pages/*.js",
|
|
29
|
-
"#components/*": "./src/
|
|
29
|
+
"#components/*": "./src/components/*",
|
|
30
30
|
"#internal/*": "./src/internal/src/*"
|
|
31
31
|
},
|
|
32
32
|
"exports": {
|
|
33
|
+
"./components/*": "./src/components/*",
|
|
33
34
|
"./utils/*": "./src/utils/*.js",
|
|
34
35
|
"./config/*": "./src/config/*.js",
|
|
35
36
|
"./css/*": "./src/css/*.css",
|
|
36
|
-
"./
|
|
37
|
+
"./pages/*": "./src/pages/*.js"
|
|
37
38
|
},
|
|
38
39
|
"bin": {
|
|
39
40
|
"portosaurus": "bin/portosaurus.js"
|
|
@@ -45,7 +46,9 @@
|
|
|
45
46
|
"src/utils/",
|
|
46
47
|
"src/config/",
|
|
47
48
|
"src/css/",
|
|
48
|
-
"src/img"
|
|
49
|
+
"src/img",
|
|
50
|
+
"src/pages/",
|
|
51
|
+
"src/components/"
|
|
49
52
|
],
|
|
50
53
|
"dependencies": {
|
|
51
54
|
"@docusaurus/core": "^3.9.2",
|
package/src/img/icon.svg
CHANGED
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
3
|
|
|
4
4
|
<svg
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
version="1.1"
|
|
6
|
+
id="svg1"
|
|
7
|
+
width="495"
|
|
8
|
+
height="504"
|
|
9
|
+
viewBox="0 0 495 504"
|
|
10
|
+
sodipodi:docname="icon.svg"
|
|
11
|
+
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
|
12
|
+
xml:space="preserve"
|
|
13
|
+
inkscape:export-filename="icon.png"
|
|
14
|
+
inkscape:export-xdpi="99.636742"
|
|
15
|
+
inkscape:export-ydpi="99.636742"
|
|
16
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
17
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
18
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
19
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
|
20
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
21
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
|
22
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
|
|
23
23
|
id="title9">portosaurus Icon</title><defs
|
|
24
24
|
id="defs1"><inkscape:path-effect
|
|
25
25
|
effect="mirror_symmetry"
|
|
@@ -239,4 +239,4 @@
|
|
|
239
239
|
inkscape:transform-center-x="6.5926696"
|
|
240
240
|
inkscape:transform-center-y="21.975565" /></g></g></g><metadata
|
|
241
241
|
id="metadata9"><rdf:RDF><cc:Work
|
|
242
|
-
rdf:about=""><dc:title>portosaurus Icon</dc:title><dc:creator><cc:Agent><dc:title>soymadip</dc:title></cc:Agent></dc:creator><dc:publisher><cc:Agent><dc:title>soymadip</dc:title></cc:Agent></dc:publisher><dc:language>en</dc:language></cc:Work></rdf:RDF></metadata></svg>
|
|
242
|
+
rdf:about=""><dc:title>portosaurus Icon</dc:title><dc:creator><cc:Agent><dc:title>soymadip</dc:title></cc:Agent></dc:creator><dc:publisher><cc:Agent><dc:title>soymadip</dc:title></cc:Agent></dc:publisher><dc:language>en</dc:language></cc:Work></rdf:RDF></metadata></svg>
|
|
@@ -1,82 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
3
|
-
import { UpdateTitle } from "#utils/updateTitle";
|
|
4
|
-
|
|
5
|
-
// Import components
|
|
6
|
-
import HeroSection from "@site/src/components/HeroSection";
|
|
7
|
-
import AboutSection from "@site/src/components/AboutSection";
|
|
8
|
-
import ProjectsSection from "@site/src/components/ProjectsSection";
|
|
9
|
-
import ContactSection from "@site/src/components/ContactSection";
|
|
10
|
-
import ExperienceSection from "@site/src/components/ExperienceSection";
|
|
11
|
-
import ScrollToTop from "@site/src/components/ScrollToTop";
|
|
12
|
-
|
|
13
|
-
export default function Home() {
|
|
14
|
-
const { siteConfig } = useDocusaurusContext();
|
|
15
|
-
const { customFields } = siteConfig;
|
|
16
|
-
|
|
17
|
-
const aboutMe = customFields.aboutMe || {};
|
|
18
|
-
const projects = customFields.projects || {};
|
|
19
|
-
const socialLinks = customFields.socialLinks || {};
|
|
20
|
-
const experience = customFields.experience || {};
|
|
21
|
-
|
|
22
|
-
const sectionTitles = {
|
|
23
|
-
me: `Home | ${siteConfig.title}`,
|
|
24
|
-
about: `About Me | ${siteConfig.title}`,
|
|
25
|
-
projects: `Projects | ${siteConfig.title}`,
|
|
26
|
-
experience: `Experience | ${siteConfig.title}`,
|
|
27
|
-
contact: `Contact | ${siteConfig.title}`,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const customStyles = `
|
|
31
|
-
/* For future */
|
|
32
|
-
`;
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<Layout title="Me" description="My portfolio website">
|
|
36
|
-
{/* Custom styles */}
|
|
37
|
-
<style>{customStyles}</style>
|
|
38
|
-
|
|
39
|
-
<UpdateTitle sections={sectionTitles} defaultTitle={siteConfig.title} />
|
|
40
|
-
|
|
41
|
-
<main>
|
|
42
|
-
{/* Hero Section */}
|
|
43
|
-
<HeroSection id="me" />
|
|
44
|
-
|
|
45
|
-
{/* About Section */}
|
|
46
|
-
{aboutMe.enable !== false && (
|
|
47
|
-
<AboutSection id="about" title="About Me" />
|
|
48
|
-
)}
|
|
49
|
-
|
|
50
|
-
{/* Projects Section */}
|
|
51
|
-
{projects.enable !== false && (
|
|
52
|
-
<ProjectsSection
|
|
53
|
-
id="projects"
|
|
54
|
-
title="My Projects"
|
|
55
|
-
subtitle="A collection of all my works, with featured projects highlighted"
|
|
56
|
-
/>
|
|
57
|
-
)}
|
|
58
|
-
|
|
59
|
-
{/* Experience Section */}
|
|
60
|
-
{experience.enable !== false && (
|
|
61
|
-
<ExperienceSection
|
|
62
|
-
id="experience"
|
|
63
|
-
title="Experience"
|
|
64
|
-
subtitle="My professional journey and work experience"
|
|
65
|
-
/>
|
|
66
|
-
)}
|
|
67
|
-
|
|
68
|
-
{/* Contact Section */}
|
|
69
|
-
{socialLinks.enable !== false && (
|
|
70
|
-
<ContactSection
|
|
71
|
-
id="contact"
|
|
72
|
-
title="Get In Touch"
|
|
73
|
-
subtitle="Feel free to reach out for collaborations, questions, or just to say hello!"
|
|
74
|
-
/>
|
|
75
|
-
)}
|
|
76
|
-
|
|
77
|
-
{/* Scroll to top button */}
|
|
78
|
-
<ScrollToTop hideDelay={3500} />
|
|
79
|
-
</main>
|
|
80
|
-
</Layout>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
1
|
+
export { default } from "portosaurus/pages/Home";
|
|
@@ -1,84 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import Layout from "@theme/Layout";
|
|
3
|
-
import NoteCards from "@site/src/components/NoteIndex";
|
|
4
|
-
import { usePluginData } from "@docusaurus/useGlobalData";
|
|
5
|
-
import ScrollToTop from "../components/ScrollToTop";
|
|
6
|
-
import { HashNavigation } from "portosaurus/utils/HashNavigation";
|
|
7
|
-
|
|
8
|
-
const style = {
|
|
9
|
-
notesContainer: {
|
|
10
|
-
padding: "2rem 0",
|
|
11
|
-
maxWidth: "1200px",
|
|
12
|
-
margin: "0 auto",
|
|
13
|
-
},
|
|
14
|
-
|
|
15
|
-
pageTitle: {
|
|
16
|
-
fontSize: "2.5rem",
|
|
17
|
-
textAlign: "center",
|
|
18
|
-
marginBottom: "0.5rem",
|
|
19
|
-
color: "var(--ifm-color-primary)",
|
|
20
|
-
animation: "slideUp 0.5s ease-out forwards",
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
pageDescription: {
|
|
24
|
-
fontSize: "0.9rem",
|
|
25
|
-
textAlign: "center",
|
|
26
|
-
color: "var(--ifm-font-color-tertiary)",
|
|
27
|
-
marginBottom: "2rem",
|
|
28
|
-
animation: "slideUp 0.5s ease-out 0.2s forwards",
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
"@keyframes slideUp": {
|
|
32
|
-
from: {
|
|
33
|
-
opacity: 0,
|
|
34
|
-
transform: "translateY(20px)",
|
|
35
|
-
},
|
|
36
|
-
to: {
|
|
37
|
-
opacity: 1,
|
|
38
|
-
transform: "translateY(0)",
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
"@media (prefers-reduced-motion: reduce)": {
|
|
43
|
-
notesContainer: {
|
|
44
|
-
animation: "none !important",
|
|
45
|
-
},
|
|
46
|
-
pageTitle: {
|
|
47
|
-
animation: "none !important",
|
|
48
|
-
},
|
|
49
|
-
pageDescription: {
|
|
50
|
-
animation: "none !important",
|
|
51
|
-
opacity: 1,
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export default function Notes() {
|
|
57
|
-
const { path: docsBasePath } = usePluginData(
|
|
58
|
-
"docusaurus-plugin-content-docs",
|
|
59
|
-
);
|
|
60
|
-
const pathName = docsBasePath.replace("/", "");
|
|
61
|
-
const pageTitle = pathName.charAt(0).toUpperCase() + pathName.slice(1);
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<Layout title={pageTitle} description={`My ${pageTitle}`}>
|
|
65
|
-
<main style={style.notesContainer}>
|
|
66
|
-
<div className="container">
|
|
67
|
-
<header className="text-center mb-4">
|
|
68
|
-
<h1 style={style.pageTitle}>My Notes</h1>
|
|
69
|
-
<p style={style.pageDescription}>
|
|
70
|
-
A collection of my self written notes & reference guides
|
|
71
|
-
</p>
|
|
72
|
-
</header>
|
|
73
|
-
<NoteCards />
|
|
74
|
-
<ScrollToTop />
|
|
75
|
-
<HashNavigation
|
|
76
|
-
elementPrefix="note-"
|
|
77
|
-
elementSelector=".note-card"
|
|
78
|
-
effectDuration={6000}
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
</main>
|
|
82
|
-
</Layout>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
1
|
+
export { default } from "portosaurus/pages/Notes";
|
|
@@ -1,310 +1 @@
|
|
|
1
|
-
|
|
2
|
-
__ ___ _ ____ _
|
|
3
|
-
\ \ / (_) |__ ___ / ___|___ __| | ___
|
|
4
|
-
\ \ / /| | '_ \ / _ \ | | / _ \ / _` |/ _ \
|
|
5
|
-
\ V / | | |_) | __/ | |__| (_) | (_| | __/
|
|
6
|
-
\_/ |_|_.__/ \___| \____\___/ \__,_|\___|
|
|
7
|
-
|
|
8
|
-
This Page is completely Vibe coded. No code except small tweaks is written by me.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { useState } from "react";
|
|
12
|
-
import Layout from "@theme/Layout";
|
|
13
|
-
import Head from "@docusaurus/Head";
|
|
14
|
-
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
15
|
-
import "../css/tasks.css";
|
|
16
|
-
import {
|
|
17
|
-
FaClipboardList,
|
|
18
|
-
FaSyncAlt,
|
|
19
|
-
FaClock,
|
|
20
|
-
FaCheckCircle,
|
|
21
|
-
FaFire,
|
|
22
|
-
FaThermometerHalf,
|
|
23
|
-
FaSnowflake,
|
|
24
|
-
FaTasks,
|
|
25
|
-
FaExclamationTriangle,
|
|
26
|
-
} from "react-icons/fa";
|
|
27
|
-
|
|
28
|
-
function TaskList({ filterStatus, taskList }) {
|
|
29
|
-
if (!taskList || !Array.isArray(taskList)) {
|
|
30
|
-
return (
|
|
31
|
-
<div className="task-empty-state">
|
|
32
|
-
<FaTasks className="task-empty-icon" />
|
|
33
|
-
<p>No tasks available</p>
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const filteredTasks = taskList.filter((task) =>
|
|
39
|
-
filterStatus ? task.status === filterStatus : true,
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
if (filteredTasks.length === 0) {
|
|
43
|
-
return (
|
|
44
|
-
<div className="task-empty-state">
|
|
45
|
-
<FaTasks className="task-empty-icon" />
|
|
46
|
-
<p>No tasks in this category</p>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Sort tasks by status first (with completed tasks at bottom), then by priority
|
|
52
|
-
const sortedTasks = [...filteredTasks].sort((a, b) => {
|
|
53
|
-
const statusOrder = { active: 1, pending: 2, completed: 3 };
|
|
54
|
-
const statusDiff = statusOrder[a.status] - statusOrder[b.status];
|
|
55
|
-
|
|
56
|
-
if (statusDiff !== 0) return statusDiff;
|
|
57
|
-
|
|
58
|
-
// Priority order: high, medium, low
|
|
59
|
-
const priorityOrder = { high: 1, medium: 2, low: 3 };
|
|
60
|
-
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div className="task-list-container">
|
|
65
|
-
<div className="task-list-table">
|
|
66
|
-
<div className="task-list-header">
|
|
67
|
-
<div className="task-cell task-cell-status">Status</div>
|
|
68
|
-
<div className="task-cell task-cell-title">Task Details</div>
|
|
69
|
-
<div className="task-cell task-cell-priority">Priority</div>
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
<div className="task-rows">
|
|
73
|
-
{sortedTasks.map((task, index) => (
|
|
74
|
-
<div
|
|
75
|
-
key={index}
|
|
76
|
-
className={`task-row ${task.status === "completed" ? "task-row-completed" : ""} ${index % 2 === 1 ? "task-row-striped" : ""}`}
|
|
77
|
-
>
|
|
78
|
-
<div className="task-cell task-cell-status">
|
|
79
|
-
<span className={`badge badge-status-${task.status}`}>
|
|
80
|
-
{task.status === "completed" && (
|
|
81
|
-
<>
|
|
82
|
-
<FaCheckCircle className="badge-icon" /> Done
|
|
83
|
-
</>
|
|
84
|
-
)}
|
|
85
|
-
{task.status === "active" && (
|
|
86
|
-
<>
|
|
87
|
-
<FaSyncAlt className="badge-icon spin" /> In Progress
|
|
88
|
-
</>
|
|
89
|
-
)}
|
|
90
|
-
{task.status === "pending" && (
|
|
91
|
-
<>
|
|
92
|
-
<FaClock className="badge-icon" /> Planned
|
|
93
|
-
</>
|
|
94
|
-
)}
|
|
95
|
-
</span>
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<div className="task-cell task-cell-title">
|
|
99
|
-
<div className="task-title">{task.title}</div>
|
|
100
|
-
{task.description && (
|
|
101
|
-
<div className="task-description">{task.description}</div>
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
<div className="task-cell task-cell-priority">
|
|
106
|
-
<span className={`badge badge-priority-${task.priority}`}>
|
|
107
|
-
{task.priority === "high" && (
|
|
108
|
-
<>
|
|
109
|
-
<FaFire className="badge-icon" /> High
|
|
110
|
-
</>
|
|
111
|
-
)}
|
|
112
|
-
{task.priority === "medium" && (
|
|
113
|
-
<>
|
|
114
|
-
<FaThermometerHalf className="badge-icon" /> Medium
|
|
115
|
-
</>
|
|
116
|
-
)}
|
|
117
|
-
{task.priority === "low" && (
|
|
118
|
-
<>
|
|
119
|
-
<FaSnowflake className="badge-icon" /> Low
|
|
120
|
-
</>
|
|
121
|
-
)}
|
|
122
|
-
</span>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
))}
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function TaskStats({ taskList }) {
|
|
133
|
-
if (!taskList || !Array.isArray(taskList)) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const total = taskList.length;
|
|
138
|
-
const completed = taskList.filter(
|
|
139
|
-
(task) => task.status === "completed",
|
|
140
|
-
).length;
|
|
141
|
-
const active = taskList.filter((task) => task.status === "active").length;
|
|
142
|
-
const pending = taskList.filter((task) => task.status === "pending").length;
|
|
143
|
-
const percentComplete = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<div className="stats-container">
|
|
147
|
-
<div className="stat-box">
|
|
148
|
-
<div className="stat-label">Total Tasks</div>
|
|
149
|
-
<div className="stat-value">{total}</div>
|
|
150
|
-
</div>
|
|
151
|
-
<div className="stat-box">
|
|
152
|
-
<div className="stat-label">Completed</div>
|
|
153
|
-
<div className="stat-value stat-value-completed">{completed}</div>
|
|
154
|
-
</div>
|
|
155
|
-
<div className="stat-box">
|
|
156
|
-
<div className="stat-label">In Progress</div>
|
|
157
|
-
<div className="stat-value stat-value-active">{active}</div>
|
|
158
|
-
</div>
|
|
159
|
-
<div className="stat-box">
|
|
160
|
-
<div className="stat-label">Planned</div>
|
|
161
|
-
<div className="stat-value stat-value-pending">{pending}</div>
|
|
162
|
-
</div>
|
|
163
|
-
<div className="stat-box">
|
|
164
|
-
<div className="stat-label">Progress</div>
|
|
165
|
-
<div className="stat-value">{percentComplete}%</div>
|
|
166
|
-
<div className="progress-bar-container">
|
|
167
|
-
<div
|
|
168
|
-
className="progress-bar"
|
|
169
|
-
style={{ width: `${percentComplete}%` }}
|
|
170
|
-
></div>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
</div>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function TaskTabs({ taskList }) {
|
|
178
|
-
const [activeTab, setActiveTab] = useState("all");
|
|
179
|
-
|
|
180
|
-
if (!taskList || !Array.isArray(taskList)) {
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const tabData = [
|
|
185
|
-
{
|
|
186
|
-
id: "all",
|
|
187
|
-
label: "All Tasks",
|
|
188
|
-
icon: <FaClipboardList />,
|
|
189
|
-
count: taskList.length,
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
id: "active",
|
|
193
|
-
label: "In Progress",
|
|
194
|
-
icon: <FaSyncAlt className="spin" />,
|
|
195
|
-
count: taskList.filter((t) => t.status === "active").length,
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
id: "pending",
|
|
199
|
-
label: "Planned",
|
|
200
|
-
icon: <FaClock />,
|
|
201
|
-
count: taskList.filter((t) => t.status === "pending").length,
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
id: "completed",
|
|
205
|
-
label: "Completed",
|
|
206
|
-
icon: <FaCheckCircle />,
|
|
207
|
-
count: taskList.filter((t) => t.status === "completed").length,
|
|
208
|
-
},
|
|
209
|
-
];
|
|
210
|
-
|
|
211
|
-
return (
|
|
212
|
-
<div className="task-tabs-container">
|
|
213
|
-
<div className="task-tabs" role="tablist" aria-label="Task categories">
|
|
214
|
-
{tabData.map((tab) => (
|
|
215
|
-
<button
|
|
216
|
-
key={tab.id}
|
|
217
|
-
className={`task-tab ${activeTab === tab.id ? "task-tab-active" : ""}`}
|
|
218
|
-
onClick={() => setActiveTab(tab.id)}
|
|
219
|
-
role="tab"
|
|
220
|
-
aria-selected={activeTab === tab.id}
|
|
221
|
-
aria-controls={`tab-content-${tab.id}`}
|
|
222
|
-
id={`tab-${tab.id}`}
|
|
223
|
-
>
|
|
224
|
-
<span className="task-tab-icon" aria-hidden="true">
|
|
225
|
-
{tab.icon}
|
|
226
|
-
</span>
|
|
227
|
-
<span className="task-tab-label">{tab.label}</span>
|
|
228
|
-
<span className="task-tab-count">{tab.count}</span>
|
|
229
|
-
</button>
|
|
230
|
-
))}
|
|
231
|
-
</div>
|
|
232
|
-
|
|
233
|
-
<div
|
|
234
|
-
className="task-tab-content"
|
|
235
|
-
role="tabpanel"
|
|
236
|
-
id={`tab-content-${activeTab}`}
|
|
237
|
-
aria-labelledby={`tab-${activeTab}`}
|
|
238
|
-
>
|
|
239
|
-
{activeTab === "all" && <TaskList taskList={taskList} />}
|
|
240
|
-
{activeTab === "active" && (
|
|
241
|
-
<TaskList taskList={taskList} filterStatus="active" />
|
|
242
|
-
)}
|
|
243
|
-
{activeTab === "pending" && (
|
|
244
|
-
<TaskList taskList={taskList} filterStatus="pending" />
|
|
245
|
-
)}
|
|
246
|
-
{activeTab === "completed" && (
|
|
247
|
-
<TaskList taskList={taskList} filterStatus="completed" />
|
|
248
|
-
)}
|
|
249
|
-
</div>
|
|
250
|
-
</div>
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export default function TasksPage() {
|
|
255
|
-
const { siteConfig } = useDocusaurusContext();
|
|
256
|
-
const { customFields } = siteConfig || {};
|
|
257
|
-
|
|
258
|
-
const tasksPage = customFields?.tasksPage;
|
|
259
|
-
const title = tasksPage.title;
|
|
260
|
-
const description = tasksPage.description;
|
|
261
|
-
const taskList =
|
|
262
|
-
tasksPage.enable && tasksPage.taskList ? tasksPage.taskList : [];
|
|
263
|
-
|
|
264
|
-
// If tasks are disabled, show a notice box instead
|
|
265
|
-
if (!tasksPage || !tasksPage.enable) {
|
|
266
|
-
return (
|
|
267
|
-
<Layout
|
|
268
|
-
title="Tasks are Disabled"
|
|
269
|
-
description="Tasks are currently disabled"
|
|
270
|
-
>
|
|
271
|
-
<div className="tasks-container">
|
|
272
|
-
<div className="tasks-content">
|
|
273
|
-
<div className="tasks-disabled-notice">
|
|
274
|
-
<div className="disabled-icon">
|
|
275
|
-
<FaExclamationTriangle aria-hidden="true" />
|
|
276
|
-
</div>
|
|
277
|
-
<h2 className="disabled-title">Tasks are currently disabled</h2>
|
|
278
|
-
<p className="disabled-help">
|
|
279
|
-
To enable tasks, set <code>tasks_page.enable</code> to{" "}
|
|
280
|
-
<code>true</code>
|
|
281
|
-
</p>
|
|
282
|
-
</div>
|
|
283
|
-
</div>
|
|
284
|
-
</div>
|
|
285
|
-
</Layout>
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return (
|
|
290
|
-
<Layout title={title} description={description}>
|
|
291
|
-
<Head>
|
|
292
|
-
<meta property="og:title" content={title} />
|
|
293
|
-
<meta property="og:description" content={description} />
|
|
294
|
-
<meta name="twitter:title" content={title} />
|
|
295
|
-
<meta name="twitter:description" content={description} />
|
|
296
|
-
</Head>
|
|
297
|
-
|
|
298
|
-
<div className="tasks-container">
|
|
299
|
-
<div className="tasks-header">
|
|
300
|
-
<h1 className="tasks-heading">{title}</h1>
|
|
301
|
-
</div>
|
|
302
|
-
|
|
303
|
-
<div className="tasks-content">
|
|
304
|
-
<TaskStats taskList={taskList} />
|
|
305
|
-
<TaskTabs taskList={taskList} />
|
|
306
|
-
</div>
|
|
307
|
-
</div>
|
|
308
|
-
</Layout>
|
|
309
|
-
);
|
|
310
|
-
}
|
|
1
|
+
export { default } from "portosaurus/pages/Tasks";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Docusaurus Utils
|
|
2
|
+
import Layout from "@theme/Layout";
|
|
3
|
+
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
4
|
+
import UpdateTitle from "portosaurus/utils/updateTitle";
|
|
5
|
+
|
|
6
|
+
// Import components
|
|
7
|
+
import HeroSection from "#components/HeroSection";
|
|
8
|
+
import AboutSection from "#components/AboutSection";
|
|
9
|
+
import ProjectsSection from "#components/ProjectsSection";
|
|
10
|
+
import ContactSection from "#components/ContactSection";
|
|
11
|
+
import ExperienceSection from "#components/ExperienceSection";
|
|
12
|
+
import ScrollToTop from "#components/ScrollToTop";
|
|
13
|
+
|
|
14
|
+
export default function Home() {
|
|
15
|
+
const { siteConfig } = useDocusaurusContext();
|
|
16
|
+
const { customFields } = siteConfig;
|
|
17
|
+
|
|
18
|
+
const aboutMe = customFields.aboutMe || {};
|
|
19
|
+
const projects = customFields.projects || {};
|
|
20
|
+
const socialLinks = customFields.socialLinks || {};
|
|
21
|
+
const experience = customFields.experience || {};
|
|
22
|
+
|
|
23
|
+
const sectionTitles = {
|
|
24
|
+
me: `Home | ${siteConfig.title}`,
|
|
25
|
+
about: `About Me | ${siteConfig.title}`,
|
|
26
|
+
projects: `Projects | ${siteConfig.title}`,
|
|
27
|
+
experience: `Experience | ${siteConfig.title}`,
|
|
28
|
+
contact: `Contact | ${siteConfig.title}`,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const customStyles = `
|
|
32
|
+
/* For future */
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Layout title="Me" description="My portfolio website">
|
|
37
|
+
{/* Custom styles */}
|
|
38
|
+
<style>{customStyles}</style>
|
|
39
|
+
|
|
40
|
+
<UpdateTitle sections={sectionTitles} defaultTitle={siteConfig.title} />
|
|
41
|
+
|
|
42
|
+
<main>
|
|
43
|
+
{/* Hero Section */}
|
|
44
|
+
<HeroSection id="me" />
|
|
45
|
+
|
|
46
|
+
{/* About Section */}
|
|
47
|
+
{aboutMe.enable !== false && (
|
|
48
|
+
<AboutSection id="about" title="About Me" />
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{/* Projects Section */}
|
|
52
|
+
{projects.enable !== false && (
|
|
53
|
+
<ProjectsSection
|
|
54
|
+
id="projects"
|
|
55
|
+
title="My Projects"
|
|
56
|
+
subtitle="A collection of all my works, with featured projects highlighted"
|
|
57
|
+
/>
|
|
58
|
+
)}
|
|
59
|
+
|
|
60
|
+
{/* Experience Section */}
|
|
61
|
+
{experience.enable !== false && (
|
|
62
|
+
<ExperienceSection
|
|
63
|
+
id="experience"
|
|
64
|
+
title="Experience"
|
|
65
|
+
subtitle="My professional journey and work experience"
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{/* Contact Section */}
|
|
70
|
+
{socialLinks.enable !== false && (
|
|
71
|
+
<ContactSection
|
|
72
|
+
id="contact"
|
|
73
|
+
title="Get In Touch"
|
|
74
|
+
subtitle="Feel free to reach out for collaborations, questions, or just to say hello!"
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
{/* Scroll to top button */}
|
|
79
|
+
<ScrollToTop hideDelay={3500} />
|
|
80
|
+
</main>
|
|
81
|
+
</Layout>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Layout from "@theme/Layout";
|
|
3
|
+
import NoteCards from "#components/NoteIndex";
|
|
4
|
+
import { usePluginData } from "@docusaurus/useGlobalData";
|
|
5
|
+
import ScrollToTop from "#components/ScrollToTop";
|
|
6
|
+
import HashNavigation from "portosaurus/utils/HashNavigation";
|
|
7
|
+
|
|
8
|
+
const style = {
|
|
9
|
+
notesContainer: {
|
|
10
|
+
padding: "2rem 0",
|
|
11
|
+
maxWidth: "1200px",
|
|
12
|
+
margin: "0 auto",
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
pageTitle: {
|
|
16
|
+
fontSize: "2.5rem",
|
|
17
|
+
textAlign: "center",
|
|
18
|
+
marginBottom: "0.5rem",
|
|
19
|
+
color: "var(--ifm-color-primary)",
|
|
20
|
+
animation: "slideUp 0.5s ease-out forwards",
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
pageDescription: {
|
|
24
|
+
fontSize: "0.9rem",
|
|
25
|
+
textAlign: "center",
|
|
26
|
+
color: "var(--ifm-font-color-tertiary)",
|
|
27
|
+
marginBottom: "2rem",
|
|
28
|
+
animation: "slideUp 0.5s ease-out 0.2s forwards",
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
"@keyframes slideUp": {
|
|
32
|
+
from: {
|
|
33
|
+
opacity: 0,
|
|
34
|
+
transform: "translateY(20px)",
|
|
35
|
+
},
|
|
36
|
+
to: {
|
|
37
|
+
opacity: 1,
|
|
38
|
+
transform: "translateY(0)",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
43
|
+
notesContainer: {
|
|
44
|
+
animation: "none !important",
|
|
45
|
+
},
|
|
46
|
+
pageTitle: {
|
|
47
|
+
animation: "none !important",
|
|
48
|
+
},
|
|
49
|
+
pageDescription: {
|
|
50
|
+
animation: "none !important",
|
|
51
|
+
opacity: 1,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default function Notes() {
|
|
57
|
+
const { path: docsBasePath } = usePluginData(
|
|
58
|
+
"docusaurus-plugin-content-docs",
|
|
59
|
+
);
|
|
60
|
+
const pathName = docsBasePath.replace("/", "");
|
|
61
|
+
const pageTitle = pathName.charAt(0).toUpperCase() + pathName.slice(1);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Layout title={pageTitle} description={`My ${pageTitle}`}>
|
|
65
|
+
<main style={style.notesContainer}>
|
|
66
|
+
<div className="container">
|
|
67
|
+
<header className="text-center mb-4">
|
|
68
|
+
<h1 style={style.pageTitle}>My Notes</h1>
|
|
69
|
+
<p style={style.pageDescription}>
|
|
70
|
+
A collection of my self written notes & reference guides
|
|
71
|
+
</p>
|
|
72
|
+
</header>
|
|
73
|
+
<NoteCards />
|
|
74
|
+
<ScrollToTop />
|
|
75
|
+
<HashNavigation
|
|
76
|
+
elementPrefix="note-"
|
|
77
|
+
elementSelector=".note-card"
|
|
78
|
+
effectDuration={6000}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
</main>
|
|
82
|
+
</Layout>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/*
|
|
2
|
+
__ ___ _ ____ _
|
|
3
|
+
\ \ / (_) |__ ___ / ___|___ __| | ___
|
|
4
|
+
\ \ / /| | '_ \ / _ \ | | / _ \ / _` |/ _ \
|
|
5
|
+
\ V / | | |_) | __/ | |__| (_) | (_| | __/
|
|
6
|
+
\_/ |_|_.__/ \___| \____\___/ \__,_|\___|
|
|
7
|
+
|
|
8
|
+
This Page is completely Vibe coded. No code except small tweaks is written by me.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import Layout from "@theme/Layout";
|
|
13
|
+
import Head from "@docusaurus/Head";
|
|
14
|
+
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
15
|
+
import "portosaurus/css/tasks.css";
|
|
16
|
+
import {
|
|
17
|
+
FaClipboardList,
|
|
18
|
+
FaSyncAlt,
|
|
19
|
+
FaClock,
|
|
20
|
+
FaCheckCircle,
|
|
21
|
+
FaFire,
|
|
22
|
+
FaThermometerHalf,
|
|
23
|
+
FaSnowflake,
|
|
24
|
+
FaTasks,
|
|
25
|
+
FaExclamationTriangle,
|
|
26
|
+
} from "react-icons/fa";
|
|
27
|
+
|
|
28
|
+
function TaskList({ filterStatus, taskList }) {
|
|
29
|
+
if (!taskList || !Array.isArray(taskList)) {
|
|
30
|
+
return (
|
|
31
|
+
<div className="task-empty-state">
|
|
32
|
+
<FaTasks className="task-empty-icon" />
|
|
33
|
+
<p>No tasks available</p>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const filteredTasks = taskList.filter((task) =>
|
|
39
|
+
filterStatus ? task.status === filterStatus : true,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (filteredTasks.length === 0) {
|
|
43
|
+
return (
|
|
44
|
+
<div className="task-empty-state">
|
|
45
|
+
<FaTasks className="task-empty-icon" />
|
|
46
|
+
<p>No tasks in this category</p>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Sort tasks by status first (with completed tasks at bottom), then by priority
|
|
52
|
+
const sortedTasks = [...filteredTasks].sort((a, b) => {
|
|
53
|
+
const statusOrder = { active: 1, pending: 2, completed: 3 };
|
|
54
|
+
const statusDiff = statusOrder[a.status] - statusOrder[b.status];
|
|
55
|
+
|
|
56
|
+
if (statusDiff !== 0) return statusDiff;
|
|
57
|
+
|
|
58
|
+
// Priority order: high, medium, low
|
|
59
|
+
const priorityOrder = { high: 1, medium: 2, low: 3 };
|
|
60
|
+
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="task-list-container">
|
|
65
|
+
<div className="task-list-table">
|
|
66
|
+
<div className="task-list-header">
|
|
67
|
+
<div className="task-cell task-cell-status">Status</div>
|
|
68
|
+
<div className="task-cell task-cell-title">Task Details</div>
|
|
69
|
+
<div className="task-cell task-cell-priority">Priority</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="task-rows">
|
|
73
|
+
{sortedTasks.map((task, index) => (
|
|
74
|
+
<div
|
|
75
|
+
key={index}
|
|
76
|
+
className={`task-row ${task.status === "completed" ? "task-row-completed" : ""} ${index % 2 === 1 ? "task-row-striped" : ""}`}
|
|
77
|
+
>
|
|
78
|
+
<div className="task-cell task-cell-status">
|
|
79
|
+
<span className={`badge badge-status-${task.status}`}>
|
|
80
|
+
{task.status === "completed" && (
|
|
81
|
+
<>
|
|
82
|
+
<FaCheckCircle className="badge-icon" /> Done
|
|
83
|
+
</>
|
|
84
|
+
)}
|
|
85
|
+
{task.status === "active" && (
|
|
86
|
+
<>
|
|
87
|
+
<FaSyncAlt className="badge-icon spin" /> In Progress
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
90
|
+
{task.status === "pending" && (
|
|
91
|
+
<>
|
|
92
|
+
<FaClock className="badge-icon" /> Planned
|
|
93
|
+
</>
|
|
94
|
+
)}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div className="task-cell task-cell-title">
|
|
99
|
+
<div className="task-title">{task.title}</div>
|
|
100
|
+
{task.description && (
|
|
101
|
+
<div className="task-description">{task.description}</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div className="task-cell task-cell-priority">
|
|
106
|
+
<span className={`badge badge-priority-${task.priority}`}>
|
|
107
|
+
{task.priority === "high" && (
|
|
108
|
+
<>
|
|
109
|
+
<FaFire className="badge-icon" /> High
|
|
110
|
+
</>
|
|
111
|
+
)}
|
|
112
|
+
{task.priority === "medium" && (
|
|
113
|
+
<>
|
|
114
|
+
<FaThermometerHalf className="badge-icon" /> Medium
|
|
115
|
+
</>
|
|
116
|
+
)}
|
|
117
|
+
{task.priority === "low" && (
|
|
118
|
+
<>
|
|
119
|
+
<FaSnowflake className="badge-icon" /> Low
|
|
120
|
+
</>
|
|
121
|
+
)}
|
|
122
|
+
</span>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function TaskStats({ taskList }) {
|
|
133
|
+
if (!taskList || !Array.isArray(taskList)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const total = taskList.length;
|
|
138
|
+
const completed = taskList.filter(
|
|
139
|
+
(task) => task.status === "completed",
|
|
140
|
+
).length;
|
|
141
|
+
const active = taskList.filter((task) => task.status === "active").length;
|
|
142
|
+
const pending = taskList.filter((task) => task.status === "pending").length;
|
|
143
|
+
const percentComplete = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className="stats-container">
|
|
147
|
+
<div className="stat-box">
|
|
148
|
+
<div className="stat-label">Total Tasks</div>
|
|
149
|
+
<div className="stat-value">{total}</div>
|
|
150
|
+
</div>
|
|
151
|
+
<div className="stat-box">
|
|
152
|
+
<div className="stat-label">Completed</div>
|
|
153
|
+
<div className="stat-value stat-value-completed">{completed}</div>
|
|
154
|
+
</div>
|
|
155
|
+
<div className="stat-box">
|
|
156
|
+
<div className="stat-label">In Progress</div>
|
|
157
|
+
<div className="stat-value stat-value-active">{active}</div>
|
|
158
|
+
</div>
|
|
159
|
+
<div className="stat-box">
|
|
160
|
+
<div className="stat-label">Planned</div>
|
|
161
|
+
<div className="stat-value stat-value-pending">{pending}</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div className="stat-box">
|
|
164
|
+
<div className="stat-label">Progress</div>
|
|
165
|
+
<div className="stat-value">{percentComplete}%</div>
|
|
166
|
+
<div className="progress-bar-container">
|
|
167
|
+
<div
|
|
168
|
+
className="progress-bar"
|
|
169
|
+
style={{ width: `${percentComplete}%` }}
|
|
170
|
+
></div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function TaskTabs({ taskList }) {
|
|
178
|
+
const [activeTab, setActiveTab] = useState("all");
|
|
179
|
+
|
|
180
|
+
if (!taskList || !Array.isArray(taskList)) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const tabData = [
|
|
185
|
+
{
|
|
186
|
+
id: "all",
|
|
187
|
+
label: "All Tasks",
|
|
188
|
+
icon: <FaClipboardList />,
|
|
189
|
+
count: taskList.length,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "active",
|
|
193
|
+
label: "In Progress",
|
|
194
|
+
icon: <FaSyncAlt className="spin" />,
|
|
195
|
+
count: taskList.filter((t) => t.status === "active").length,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "pending",
|
|
199
|
+
label: "Planned",
|
|
200
|
+
icon: <FaClock />,
|
|
201
|
+
count: taskList.filter((t) => t.status === "pending").length,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "completed",
|
|
205
|
+
label: "Completed",
|
|
206
|
+
icon: <FaCheckCircle />,
|
|
207
|
+
count: taskList.filter((t) => t.status === "completed").length,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<div className="task-tabs-container">
|
|
213
|
+
<div className="task-tabs" role="tablist" aria-label="Task categories">
|
|
214
|
+
{tabData.map((tab) => (
|
|
215
|
+
<button
|
|
216
|
+
key={tab.id}
|
|
217
|
+
className={`task-tab ${activeTab === tab.id ? "task-tab-active" : ""}`}
|
|
218
|
+
onClick={() => setActiveTab(tab.id)}
|
|
219
|
+
role="tab"
|
|
220
|
+
aria-selected={activeTab === tab.id}
|
|
221
|
+
aria-controls={`tab-content-${tab.id}`}
|
|
222
|
+
id={`tab-${tab.id}`}
|
|
223
|
+
>
|
|
224
|
+
<span className="task-tab-icon" aria-hidden="true">
|
|
225
|
+
{tab.icon}
|
|
226
|
+
</span>
|
|
227
|
+
<span className="task-tab-label">{tab.label}</span>
|
|
228
|
+
<span className="task-tab-count">{tab.count}</span>
|
|
229
|
+
</button>
|
|
230
|
+
))}
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<div
|
|
234
|
+
className="task-tab-content"
|
|
235
|
+
role="tabpanel"
|
|
236
|
+
id={`tab-content-${activeTab}`}
|
|
237
|
+
aria-labelledby={`tab-${activeTab}`}
|
|
238
|
+
>
|
|
239
|
+
{activeTab === "all" && <TaskList taskList={taskList} />}
|
|
240
|
+
{activeTab === "active" && (
|
|
241
|
+
<TaskList taskList={taskList} filterStatus="active" />
|
|
242
|
+
)}
|
|
243
|
+
{activeTab === "pending" && (
|
|
244
|
+
<TaskList taskList={taskList} filterStatus="pending" />
|
|
245
|
+
)}
|
|
246
|
+
{activeTab === "completed" && (
|
|
247
|
+
<TaskList taskList={taskList} filterStatus="completed" />
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export default function TasksPage() {
|
|
255
|
+
const { siteConfig } = useDocusaurusContext();
|
|
256
|
+
const { customFields } = siteConfig || {};
|
|
257
|
+
|
|
258
|
+
const tasksPage = customFields?.tasksPage;
|
|
259
|
+
const title = tasksPage.title;
|
|
260
|
+
const description = tasksPage.description;
|
|
261
|
+
const taskList =
|
|
262
|
+
tasksPage.enable && tasksPage.taskList ? tasksPage.taskList : [];
|
|
263
|
+
|
|
264
|
+
// If tasks are disabled, show a notice box instead
|
|
265
|
+
if (!tasksPage || !tasksPage.enable) {
|
|
266
|
+
return (
|
|
267
|
+
<Layout
|
|
268
|
+
title="Tasks are Disabled"
|
|
269
|
+
description="Tasks are currently disabled"
|
|
270
|
+
>
|
|
271
|
+
<div className="tasks-container">
|
|
272
|
+
<div className="tasks-content">
|
|
273
|
+
<div className="tasks-disabled-notice">
|
|
274
|
+
<div className="disabled-icon">
|
|
275
|
+
<FaExclamationTriangle aria-hidden="true" />
|
|
276
|
+
</div>
|
|
277
|
+
<h2 className="disabled-title">Tasks are currently disabled</h2>
|
|
278
|
+
<p className="disabled-help">
|
|
279
|
+
To enable tasks, set <code>tasks_page.enable</code> to{" "}
|
|
280
|
+
<code>true</code>
|
|
281
|
+
</p>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</Layout>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<Layout title={title} description={description}>
|
|
291
|
+
<Head>
|
|
292
|
+
<meta property="og:title" content={title} />
|
|
293
|
+
<meta property="og:description" content={description} />
|
|
294
|
+
<meta name="twitter:title" content={title} />
|
|
295
|
+
<meta name="twitter:description" content={description} />
|
|
296
|
+
</Head>
|
|
297
|
+
|
|
298
|
+
<div className="tasks-container">
|
|
299
|
+
<div className="tasks-header">
|
|
300
|
+
<h1 className="tasks-heading">{title}</h1>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<div className="tasks-content">
|
|
304
|
+
<TaskStats taskList={taskList} />
|
|
305
|
+
<TaskTabs taskList={taskList} />
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
</Layout>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
@@ -44,24 +44,26 @@ export function detectPackageManager(projectRoot) {
|
|
|
44
44
|
export function getRunCommand(packageManager, binaryName, args = []) {
|
|
45
45
|
switch (packageManager) {
|
|
46
46
|
case "bun":
|
|
47
|
-
|
|
48
47
|
// Bun can run binaries directly with `bun run`
|
|
49
48
|
return {
|
|
50
49
|
command: "bun",
|
|
51
50
|
args: ["run", binaryName, ...args],
|
|
52
51
|
};
|
|
52
|
+
|
|
53
53
|
case "pnpm":
|
|
54
54
|
// pnpm uses `pnpm exec`
|
|
55
55
|
return {
|
|
56
56
|
command: "pnpm",
|
|
57
57
|
args: ["exec", binaryName, ...args],
|
|
58
58
|
};
|
|
59
|
+
|
|
59
60
|
case "yarn":
|
|
60
61
|
// Yarn uses `yarn run`
|
|
61
62
|
return {
|
|
62
63
|
command: "yarn",
|
|
63
64
|
args: ["run", binaryName, ...args],
|
|
64
65
|
};
|
|
66
|
+
|
|
65
67
|
case "npm":
|
|
66
68
|
default:
|
|
67
69
|
// npm uses `npx`
|
package/src/config/.exports.js
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|