portosaurus 3.0.2 → 4.0.0
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/README.md +26 -126
- package/bin/portosaurus.mjs +8 -0
- package/package.json +6 -3
- package/src/assets/img/icon.png +0 -0
- package/src/assets/img/{icon.svg → svg/icon.svg} +35 -37
- package/src/assets/img/svg/project-blank.svg +140 -0
- package/src/assets/sample-resume.pdf +0 -0
- package/src/cli/build.mjs +2 -5
- package/src/cli/dev.mjs +27 -5
- package/src/cli/init.mjs +6 -12
- package/src/cli/schema.mjs +211 -0
- package/src/core/buildDocuConfig.mjs +305 -188
- package/src/core/constants.mjs +7 -1
- package/src/template/config.yml +150 -0
- package/src/template/notes/welcome.mdx +6 -0
- package/src/template/package.json +3 -3
- package/src/theme/MDXComponents.js +0 -1
- package/src/theme/components/AboutSection/index.js +32 -17
- package/src/theme/components/AboutSection/styles.module.css +151 -344
- package/src/theme/components/ContactSection/index.js +23 -14
- package/src/theme/components/ContactSection/styles.module.css +19 -8
- package/src/theme/components/ExperienceSection/index.js +12 -5
- package/src/theme/components/HeroSection/index.js +4 -3
- package/src/theme/components/HeroSection/styles.module.css +17 -16
- package/src/theme/components/NavArrow/index.js +114 -0
- package/src/theme/components/NavArrow/styles.module.css +107 -0
- package/src/theme/components/NoteIndex/index.js +66 -95
- package/src/theme/components/NoteIndex/styles.module.css +85 -89
- package/src/theme/components/Preview/components/FeedbackStates.js +3 -1
- package/src/theme/components/Preview/components/PreviewContent.js +91 -0
- package/src/theme/components/Preview/components/PreviewHeader.js +41 -33
- package/src/theme/components/Preview/components/Triggers/Pv.js +129 -72
- package/src/theme/components/Preview/components/ViewerWindow.js +198 -234
- package/src/theme/components/Preview/hooks/useAdaptiveSizing.js +115 -0
- package/src/theme/components/Preview/hooks/useDeepLinkHash.js +18 -23
- package/src/theme/components/Preview/hooks/useDockLayout.js +48 -8
- package/src/theme/components/Preview/hooks/useTouchZoom.js +118 -0
- package/src/theme/components/Preview/renderers/CodeRenderer.js +64 -25
- package/src/theme/components/Preview/state/index.js +70 -17
- package/src/theme/components/Preview/styles.module.css +181 -45
- package/src/theme/components/Preview/utils/index.js +11 -10
- package/src/theme/components/ProjectsSection/index.js +138 -148
- package/src/theme/components/ProjectsSection/styles.module.css +178 -112
- package/src/theme/components/SocialLinks/index.js +9 -7
- package/src/theme/components/Tooltip/index.js +31 -20
- package/src/theme/components/Tooltip/styles.module.css +101 -38
- package/src/theme/config/iconMappings.js +2 -0
- package/src/theme/css/custom.css +72 -0
- package/src/theme/hooks/useScrollReveal.js +30 -0
- package/src/theme/pages/index.js +7 -27
- package/src/theme/pages/notes.js +2 -2
- package/src/theme/pages/tasks.js +12 -11
- package/src/utils/cliUtils.mjs +23 -51
- package/src/utils/configUtils.mjs +95 -84
- package/src/utils/systemUtils.mjs +171 -0
- package/src/template/config.js +0 -68
- package/src/theme/components/ScrollToTop/index.js +0 -95
- package/src/theme/components/ScrollToTop/styles.module.css +0 -97
- package/src/theme/config/metaTags.js +0 -21
- /package/src/template/{.nojekyll → static/.nojekyll} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
|
|
2
2
|
import Slider from "react-slick";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
FaCode,
|
|
5
5
|
FaGlobe,
|
|
6
6
|
FaPlay,
|
|
7
7
|
FaChevronLeft,
|
|
@@ -9,16 +9,24 @@ import {
|
|
|
9
9
|
FaStar,
|
|
10
10
|
} from "react-icons/fa";
|
|
11
11
|
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
12
|
+
import useScrollReveal from "../../hooks/useScrollReveal";
|
|
13
|
+
import Tooltip from "../Tooltip/index.js";
|
|
12
14
|
import styles from "./styles.module.css";
|
|
13
15
|
|
|
14
16
|
// Import slick carousel css
|
|
15
17
|
import "slick-carousel/slick/slick.css";
|
|
16
18
|
import "slick-carousel/slick/slick-theme.css";
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
20
|
+
export default function ProjectsSection({ id, className }) {
|
|
21
21
|
const { siteConfig } = useDocusaurusContext();
|
|
22
|
+
const projectShelf = siteConfig.customFields?.projects || {};
|
|
23
|
+
|
|
24
|
+
if (projectShelf.enable === false) return null;
|
|
25
|
+
|
|
26
|
+
const isAutoplayEnabled = projectShelf.autoplay ?? true;
|
|
27
|
+
|
|
28
|
+
const displayHeading = projectShelf.heading;
|
|
29
|
+
const displaySubheading = projectShelf.subheading;
|
|
22
30
|
|
|
23
31
|
const [projects, setProjects] = useState([]);
|
|
24
32
|
const sliderRef = useRef(null);
|
|
@@ -30,36 +38,7 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
30
38
|
const activeDotRef = useRef(null);
|
|
31
39
|
const dotsContainerRef = useRef(null);
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
const projectDefaults = {
|
|
35
|
-
title: "Future Project",
|
|
36
|
-
desc: "Coming soon...",
|
|
37
|
-
image: "img/project-blank.png",
|
|
38
|
-
state: "active",
|
|
39
|
-
tags: ["planned"],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const createPlaceholders = useCallback(
|
|
43
|
-
(count, existingProjects) => {
|
|
44
|
-
if (existingProjects.length === 0) return [];
|
|
45
|
-
|
|
46
|
-
return [
|
|
47
|
-
...existingProjects,
|
|
48
|
-
...Array.from({ length: count }, (_, i) => ({
|
|
49
|
-
...projectDefaults,
|
|
50
|
-
|
|
51
|
-
// Dummy card config
|
|
52
|
-
state: "n/a",
|
|
53
|
-
title: `Project ${existingProjects.length + i + 1}`,
|
|
54
|
-
description: projectDefaults.desc,
|
|
55
|
-
image: projectDefaults.image,
|
|
56
|
-
id: `placeholder-${i}`,
|
|
57
|
-
tags: null,
|
|
58
|
-
})),
|
|
59
|
-
];
|
|
60
|
-
},
|
|
61
|
-
[projectDefaults],
|
|
62
|
-
);
|
|
41
|
+
const [sectionRef, isVisible] = useScrollReveal();
|
|
63
42
|
|
|
64
43
|
// Get current slidesToShow based on screen width
|
|
65
44
|
const getVisibleSlidesPerView = useCallback(() => {
|
|
@@ -71,62 +50,45 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
71
50
|
return 3;
|
|
72
51
|
}, []);
|
|
73
52
|
|
|
74
|
-
const prepareProjects = useCallback(
|
|
75
|
-
(projectList,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
description:
|
|
86
|
-
project.desc === undefined ? projectDefaults.desc : project.desc,
|
|
87
|
-
image:
|
|
88
|
-
project.image === undefined
|
|
89
|
-
? projectDefaults.image
|
|
90
|
-
: project.image,
|
|
91
|
-
tags:
|
|
92
|
-
project.tags === undefined
|
|
93
|
-
? [...projectDefaults.tags]
|
|
94
|
-
: project.tags,
|
|
95
|
-
state:
|
|
96
|
-
project.state === undefined
|
|
97
|
-
? projectDefaults.state
|
|
98
|
-
: project.state,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// Add ID
|
|
102
|
-
if (!processedProject.id) {
|
|
103
|
-
processedProject.id = processedProject.title
|
|
104
|
-
.toLowerCase()
|
|
105
|
-
.replace(/[^\w\s-]/g, "")
|
|
106
|
-
.replace(/\s+/g, "-")
|
|
107
|
-
.replace(/-+/g, "-");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return processedProject;
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Calculate pagination and placeholder needs
|
|
114
|
-
const totalPages = Math.ceil(sortedProjects.length / slides);
|
|
115
|
-
const slotsPerPage = slides;
|
|
116
|
-
const totalSlots = totalPages * slotsPerPage;
|
|
117
|
-
const placeholderCount = totalSlots - sortedProjects.length;
|
|
118
|
-
|
|
119
|
-
// Return prepared data
|
|
120
|
-
return {
|
|
121
|
-
projects:
|
|
122
|
-
placeholderCount > 0
|
|
123
|
-
? createPlaceholders(placeholderCount, sortedProjects)
|
|
124
|
-
: sortedProjects,
|
|
125
|
-
totalPages,
|
|
53
|
+
const prepareProjects = useCallback((projectList, slides) => {
|
|
54
|
+
if (!projectList?.length) return { projects: [], totalPages: 0 };
|
|
55
|
+
|
|
56
|
+
const processedProjects = projectList.map((project, index) => {
|
|
57
|
+
const processed = {
|
|
58
|
+
...project,
|
|
59
|
+
desc: project.desc || "N/A",
|
|
60
|
+
icon: project.icon || "img/project-blank.png",
|
|
61
|
+
bg: project.bg || null,
|
|
62
|
+
tags: project.tags || [],
|
|
63
|
+
state: project.state || "completed",
|
|
126
64
|
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
65
|
+
|
|
66
|
+
if (!processed.id) {
|
|
67
|
+
processed.id =
|
|
68
|
+
(processed.title || "project")
|
|
69
|
+
.toLowerCase()
|
|
70
|
+
.replace(/[^\w\s-]/g, "")
|
|
71
|
+
.replace(/\s+/g, "-")
|
|
72
|
+
.replace(/-+/g, "-") + `-${index}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return processed;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const totalPages = Math.ceil(processedProjects.length / slides);
|
|
79
|
+
|
|
80
|
+
// Sort: Featured first
|
|
81
|
+
processedProjects.sort((a, b) => {
|
|
82
|
+
if (a.featured && !b.featured) return -1;
|
|
83
|
+
if (!a.featured && b.featured) return 1;
|
|
84
|
+
return 0;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
projects: processedProjects,
|
|
89
|
+
totalPages,
|
|
90
|
+
};
|
|
91
|
+
}, []);
|
|
130
92
|
|
|
131
93
|
// Load and set up projects on initial load and on resize
|
|
132
94
|
useEffect(() => {
|
|
@@ -241,15 +203,19 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
241
203
|
dots: false,
|
|
242
204
|
infinite: false,
|
|
243
205
|
speed: 600,
|
|
244
|
-
slidesToShow:
|
|
206
|
+
slidesToShow: slidesToShow,
|
|
245
207
|
slidesToScroll: slidesToShow,
|
|
246
|
-
autoplay:
|
|
208
|
+
autoplay: isAutoplayEnabled,
|
|
209
|
+
|
|
210
|
+
autoplaySpeed: 5000,
|
|
211
|
+
pauseOnHover: true,
|
|
247
212
|
adaptiveHeight: false,
|
|
248
|
-
centerPadding: "
|
|
213
|
+
centerPadding: "0px",
|
|
249
214
|
centerMode: false,
|
|
250
215
|
variableWidth: false,
|
|
251
216
|
swipeToSlide: false,
|
|
252
217
|
focusOnSelect: false,
|
|
218
|
+
arrows: false,
|
|
253
219
|
responsive: [
|
|
254
220
|
{
|
|
255
221
|
breakpoint: 1024,
|
|
@@ -265,17 +231,15 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
265
231
|
slidesToShow: 1,
|
|
266
232
|
slidesToScroll: 1,
|
|
267
233
|
dots: false,
|
|
268
|
-
arrows: false,
|
|
269
234
|
},
|
|
270
235
|
},
|
|
271
236
|
],
|
|
272
237
|
className: styles.projectsCarousel,
|
|
273
|
-
beforeChange: (
|
|
238
|
+
beforeChange: (_, next) => {
|
|
274
239
|
setAtBeginning(next === 0);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
);
|
|
240
|
+
const nextSlideIndex = Math.floor(next / slidesToShow);
|
|
241
|
+
setCurrentSlide(nextSlideIndex);
|
|
242
|
+
setAtEnd(next + slidesToShow >= projects.length);
|
|
279
243
|
},
|
|
280
244
|
}),
|
|
281
245
|
[projects, slidesToShow],
|
|
@@ -296,7 +260,7 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
296
260
|
|
|
297
261
|
// Project link renderer
|
|
298
262
|
const renderProjectLink = useCallback((url, Icon, label, ariaLabel) => {
|
|
299
|
-
if (!url) return null;
|
|
263
|
+
if (!url || url === "#" || url === "") return null;
|
|
300
264
|
|
|
301
265
|
return (
|
|
302
266
|
<a
|
|
@@ -393,17 +357,15 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
393
357
|
return (
|
|
394
358
|
<div
|
|
395
359
|
id={id}
|
|
396
|
-
|
|
360
|
+
ref={sectionRef}
|
|
361
|
+
className={`${styles.projectsSection} ${isVisible ? "is-visible" : ""} ${className || ""}`}
|
|
397
362
|
role="region"
|
|
398
363
|
aria-label="Projects section"
|
|
399
364
|
>
|
|
400
365
|
<div className={styles.projectsContainer}>
|
|
401
366
|
<div className={styles.projectsHeader}>
|
|
402
|
-
<h2 className={styles.projectsTitle}>{
|
|
403
|
-
<p className={styles.projectsSubtitle}>
|
|
404
|
-
{subtitle ||
|
|
405
|
-
"A collection of all my works, with featured projects highlighted"}
|
|
406
|
-
</p>
|
|
367
|
+
<h2 className={styles.projectsTitle}>{displayHeading}</h2>
|
|
368
|
+
<p className={styles.projectsSubtitle}>{displaySubheading}</p>
|
|
407
369
|
</div>
|
|
408
370
|
|
|
409
371
|
{projects.length === 0 ? (
|
|
@@ -413,16 +375,18 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
413
375
|
) : (
|
|
414
376
|
<div className={styles.carouselContainer}>
|
|
415
377
|
{/* Desktop navigation buttons (sides) */}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
378
|
+
{projects.length > slidesToShow && (
|
|
379
|
+
<button
|
|
380
|
+
className={`${styles.carouselControl} ${styles.prevButton} ${styles.desktopOnly} ${atBeginning ? styles.disabledButton : ""}`}
|
|
381
|
+
onClick={goToPrev}
|
|
382
|
+
aria-label="View previous projects"
|
|
383
|
+
aria-disabled={atBeginning}
|
|
384
|
+
type="button"
|
|
385
|
+
disabled={atBeginning}
|
|
386
|
+
>
|
|
387
|
+
<FaChevronLeft aria-hidden="true" />
|
|
388
|
+
</button>
|
|
389
|
+
)}
|
|
426
390
|
|
|
427
391
|
<div
|
|
428
392
|
className={styles.carouselWrapper}
|
|
@@ -437,6 +401,7 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
437
401
|
data-project-id={project.id}
|
|
438
402
|
aria-roledescription="slide"
|
|
439
403
|
aria-label={`Project ${index + 1} of ${projects.length}: ${project.title}`}
|
|
404
|
+
style={{ "--card-index": index }}
|
|
440
405
|
>
|
|
441
406
|
<div
|
|
442
407
|
className={`${styles.carouselCard} ${project.featured ? styles.featuredCard : ""}`}
|
|
@@ -455,15 +420,48 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
455
420
|
</div>
|
|
456
421
|
)}
|
|
457
422
|
|
|
458
|
-
<div
|
|
459
|
-
{
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
423
|
+
<div
|
|
424
|
+
className={styles.projectImageContainer}
|
|
425
|
+
style={{
|
|
426
|
+
backgroundColor:
|
|
427
|
+
project.bg ||
|
|
428
|
+
"rgba(var(--ifm-color-primary-rgb), 0.05)",
|
|
429
|
+
}}
|
|
430
|
+
>
|
|
431
|
+
<img
|
|
432
|
+
src={project.icon}
|
|
433
|
+
alt={project.title}
|
|
434
|
+
className={styles.projectImage}
|
|
435
|
+
loading="lazy"
|
|
436
|
+
/>
|
|
437
|
+
|
|
438
|
+
{/* Project tags */}
|
|
439
|
+
{project.tags?.length > 0 &&
|
|
440
|
+
(() => {
|
|
441
|
+
const extraCount = project.tags.length - 3;
|
|
442
|
+
return (
|
|
443
|
+
<div className={styles.projectTags}>
|
|
444
|
+
{project.tags.slice(0, 3).map((tag) => (
|
|
445
|
+
<span key={tag} className={styles.projectTag}>
|
|
446
|
+
{tag}
|
|
447
|
+
</span>
|
|
448
|
+
))}
|
|
449
|
+
{extraCount > 0 && (
|
|
450
|
+
<Tooltip
|
|
451
|
+
msg={project.tags.slice(3).join(", ")}
|
|
452
|
+
underline={false}
|
|
453
|
+
gap={13}
|
|
454
|
+
>
|
|
455
|
+
<span
|
|
456
|
+
className={`${styles.projectTag} ${styles.extraTagBtn}`}
|
|
457
|
+
>
|
|
458
|
+
+{extraCount}
|
|
459
|
+
</span>
|
|
460
|
+
</Tooltip>
|
|
461
|
+
)}
|
|
462
|
+
</div>
|
|
463
|
+
);
|
|
464
|
+
})()}
|
|
467
465
|
|
|
468
466
|
{/* Featured badge */}
|
|
469
467
|
{project.featured && (
|
|
@@ -480,18 +478,8 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
480
478
|
<div className={styles.projectContent}>
|
|
481
479
|
<h3 className={styles.projectTitle}>{project.title}</h3>
|
|
482
480
|
|
|
483
|
-
{project.tags?.length > 0 && (
|
|
484
|
-
<div className={styles.projectTags}>
|
|
485
|
-
{project.tags.map((tag) => (
|
|
486
|
-
<span key={tag} className={styles.projectTag}>
|
|
487
|
-
{tag}
|
|
488
|
-
</span>
|
|
489
|
-
))}
|
|
490
|
-
</div>
|
|
491
|
-
)}
|
|
492
|
-
|
|
493
481
|
<p className={styles.projectDescription}>
|
|
494
|
-
{project.
|
|
482
|
+
{project.desc}
|
|
495
483
|
</p>
|
|
496
484
|
</div>
|
|
497
485
|
|
|
@@ -504,14 +492,14 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
504
492
|
)}
|
|
505
493
|
|
|
506
494
|
{renderProjectLink(
|
|
507
|
-
project.
|
|
508
|
-
|
|
495
|
+
project.repo,
|
|
496
|
+
FaCode,
|
|
509
497
|
"Source",
|
|
510
498
|
`Repository with source code`,
|
|
511
499
|
)}
|
|
512
500
|
|
|
513
501
|
{renderProjectLink(
|
|
514
|
-
project.
|
|
502
|
+
project.demo,
|
|
515
503
|
FaPlay,
|
|
516
504
|
"Demo",
|
|
517
505
|
`Live demo for ${project.title}`,
|
|
@@ -566,16 +554,18 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
566
554
|
</div>
|
|
567
555
|
|
|
568
556
|
{/* Desktop navigation button (right side) */}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
557
|
+
{projects.length > slidesToShow && (
|
|
558
|
+
<button
|
|
559
|
+
className={`${styles.carouselControl} ${styles.nextButton} ${styles.desktopOnly} ${atEnd ? styles.disabledButton : ""}`}
|
|
560
|
+
onClick={goToNext}
|
|
561
|
+
aria-label="View next projects"
|
|
562
|
+
aria-disabled={atEnd}
|
|
563
|
+
type="button"
|
|
564
|
+
disabled={atEnd}
|
|
565
|
+
>
|
|
566
|
+
<FaChevronRight />
|
|
567
|
+
</button>
|
|
568
|
+
)}
|
|
579
569
|
</div>
|
|
580
570
|
)}
|
|
581
571
|
</div>
|