portosaurus 3.0.2 → 4.0.1
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 +306 -189
- 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 +39 -17
- package/src/theme/components/AboutSection/styles.module.css +151 -344
- package/src/theme/components/ContactSection/index.js +29 -14
- package/src/theme/components/ContactSection/styles.module.css +19 -8
- package/src/theme/components/ExperienceSection/index.js +19 -5
- package/src/theme/components/HeroSection/index.js +11 -4
- 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 +145 -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,31 @@ 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";
|
|
14
|
+
import useBrokenLinks from "@docusaurus/useBrokenLinks";
|
|
12
15
|
import styles from "./styles.module.css";
|
|
13
16
|
|
|
14
17
|
// Import slick carousel css
|
|
15
18
|
import "slick-carousel/slick/slick.css";
|
|
16
19
|
import "slick-carousel/slick/slick-theme.css";
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
21
|
+
export default function ProjectsSection({ id, className }) {
|
|
21
22
|
const { siteConfig } = useDocusaurusContext();
|
|
23
|
+
const brokenLinks = useBrokenLinks();
|
|
24
|
+
|
|
25
|
+
if (id) {
|
|
26
|
+
brokenLinks.collectAnchor(id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const projectShelf = siteConfig.customFields?.projects || {};
|
|
30
|
+
|
|
31
|
+
if (projectShelf.enable === false) return null;
|
|
32
|
+
|
|
33
|
+
const isAutoplayEnabled = projectShelf.autoplay ?? true;
|
|
34
|
+
|
|
35
|
+
const displayHeading = projectShelf.heading;
|
|
36
|
+
const displaySubheading = projectShelf.subheading;
|
|
22
37
|
|
|
23
38
|
const [projects, setProjects] = useState([]);
|
|
24
39
|
const sliderRef = useRef(null);
|
|
@@ -30,36 +45,7 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
30
45
|
const activeDotRef = useRef(null);
|
|
31
46
|
const dotsContainerRef = useRef(null);
|
|
32
47
|
|
|
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
|
-
);
|
|
48
|
+
const [sectionRef, isVisible] = useScrollReveal();
|
|
63
49
|
|
|
64
50
|
// Get current slidesToShow based on screen width
|
|
65
51
|
const getVisibleSlidesPerView = useCallback(() => {
|
|
@@ -71,62 +57,45 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
71
57
|
return 3;
|
|
72
58
|
}, []);
|
|
73
59
|
|
|
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,
|
|
60
|
+
const prepareProjects = useCallback((projectList, slides) => {
|
|
61
|
+
if (!projectList?.length) return { projects: [], totalPages: 0 };
|
|
62
|
+
|
|
63
|
+
const processedProjects = projectList.map((project, index) => {
|
|
64
|
+
const processed = {
|
|
65
|
+
...project,
|
|
66
|
+
desc: project.desc || "N/A",
|
|
67
|
+
icon: project.icon || "img/project-blank.png",
|
|
68
|
+
bg: project.bg || null,
|
|
69
|
+
tags: project.tags || [],
|
|
70
|
+
state: project.state || "completed",
|
|
126
71
|
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
72
|
+
|
|
73
|
+
if (!processed.id) {
|
|
74
|
+
processed.id =
|
|
75
|
+
(processed.title || "project")
|
|
76
|
+
.toLowerCase()
|
|
77
|
+
.replace(/[^\w\s-]/g, "")
|
|
78
|
+
.replace(/\s+/g, "-")
|
|
79
|
+
.replace(/-+/g, "-") + `-${index}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return processed;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const totalPages = Math.ceil(processedProjects.length / slides);
|
|
86
|
+
|
|
87
|
+
// Sort: Featured first
|
|
88
|
+
processedProjects.sort((a, b) => {
|
|
89
|
+
if (a.featured && !b.featured) return -1;
|
|
90
|
+
if (!a.featured && b.featured) return 1;
|
|
91
|
+
return 0;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
projects: processedProjects,
|
|
96
|
+
totalPages,
|
|
97
|
+
};
|
|
98
|
+
}, []);
|
|
130
99
|
|
|
131
100
|
// Load and set up projects on initial load and on resize
|
|
132
101
|
useEffect(() => {
|
|
@@ -241,15 +210,19 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
241
210
|
dots: false,
|
|
242
211
|
infinite: false,
|
|
243
212
|
speed: 600,
|
|
244
|
-
slidesToShow:
|
|
213
|
+
slidesToShow: slidesToShow,
|
|
245
214
|
slidesToScroll: slidesToShow,
|
|
246
|
-
autoplay:
|
|
215
|
+
autoplay: isAutoplayEnabled,
|
|
216
|
+
|
|
217
|
+
autoplaySpeed: 5000,
|
|
218
|
+
pauseOnHover: true,
|
|
247
219
|
adaptiveHeight: false,
|
|
248
|
-
centerPadding: "
|
|
220
|
+
centerPadding: "0px",
|
|
249
221
|
centerMode: false,
|
|
250
222
|
variableWidth: false,
|
|
251
223
|
swipeToSlide: false,
|
|
252
224
|
focusOnSelect: false,
|
|
225
|
+
arrows: false,
|
|
253
226
|
responsive: [
|
|
254
227
|
{
|
|
255
228
|
breakpoint: 1024,
|
|
@@ -265,17 +238,15 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
265
238
|
slidesToShow: 1,
|
|
266
239
|
slidesToScroll: 1,
|
|
267
240
|
dots: false,
|
|
268
|
-
arrows: false,
|
|
269
241
|
},
|
|
270
242
|
},
|
|
271
243
|
],
|
|
272
244
|
className: styles.projectsCarousel,
|
|
273
|
-
beforeChange: (
|
|
245
|
+
beforeChange: (_, next) => {
|
|
274
246
|
setAtBeginning(next === 0);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
);
|
|
247
|
+
const nextSlideIndex = Math.floor(next / slidesToShow);
|
|
248
|
+
setCurrentSlide(nextSlideIndex);
|
|
249
|
+
setAtEnd(next + slidesToShow >= projects.length);
|
|
279
250
|
},
|
|
280
251
|
}),
|
|
281
252
|
[projects, slidesToShow],
|
|
@@ -296,7 +267,7 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
296
267
|
|
|
297
268
|
// Project link renderer
|
|
298
269
|
const renderProjectLink = useCallback((url, Icon, label, ariaLabel) => {
|
|
299
|
-
if (!url) return null;
|
|
270
|
+
if (!url || url === "#" || url === "") return null;
|
|
300
271
|
|
|
301
272
|
return (
|
|
302
273
|
<a
|
|
@@ -393,17 +364,15 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
393
364
|
return (
|
|
394
365
|
<div
|
|
395
366
|
id={id}
|
|
396
|
-
|
|
367
|
+
ref={sectionRef}
|
|
368
|
+
className={`${styles.projectsSection} ${isVisible ? "is-visible" : ""} ${className || ""}`}
|
|
397
369
|
role="region"
|
|
398
370
|
aria-label="Projects section"
|
|
399
371
|
>
|
|
400
372
|
<div className={styles.projectsContainer}>
|
|
401
373
|
<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>
|
|
374
|
+
<h2 className={styles.projectsTitle}>{displayHeading}</h2>
|
|
375
|
+
<p className={styles.projectsSubtitle}>{displaySubheading}</p>
|
|
407
376
|
</div>
|
|
408
377
|
|
|
409
378
|
{projects.length === 0 ? (
|
|
@@ -413,16 +382,18 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
413
382
|
) : (
|
|
414
383
|
<div className={styles.carouselContainer}>
|
|
415
384
|
{/* Desktop navigation buttons (sides) */}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
385
|
+
{projects.length > slidesToShow && (
|
|
386
|
+
<button
|
|
387
|
+
className={`${styles.carouselControl} ${styles.prevButton} ${styles.desktopOnly} ${atBeginning ? styles.disabledButton : ""}`}
|
|
388
|
+
onClick={goToPrev}
|
|
389
|
+
aria-label="View previous projects"
|
|
390
|
+
aria-disabled={atBeginning}
|
|
391
|
+
type="button"
|
|
392
|
+
disabled={atBeginning}
|
|
393
|
+
>
|
|
394
|
+
<FaChevronLeft aria-hidden="true" />
|
|
395
|
+
</button>
|
|
396
|
+
)}
|
|
426
397
|
|
|
427
398
|
<div
|
|
428
399
|
className={styles.carouselWrapper}
|
|
@@ -437,6 +408,7 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
437
408
|
data-project-id={project.id}
|
|
438
409
|
aria-roledescription="slide"
|
|
439
410
|
aria-label={`Project ${index + 1} of ${projects.length}: ${project.title}`}
|
|
411
|
+
style={{ "--card-index": index }}
|
|
440
412
|
>
|
|
441
413
|
<div
|
|
442
414
|
className={`${styles.carouselCard} ${project.featured ? styles.featuredCard : ""}`}
|
|
@@ -455,15 +427,48 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
455
427
|
</div>
|
|
456
428
|
)}
|
|
457
429
|
|
|
458
|
-
<div
|
|
459
|
-
{
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
430
|
+
<div
|
|
431
|
+
className={styles.projectImageContainer}
|
|
432
|
+
style={{
|
|
433
|
+
backgroundColor:
|
|
434
|
+
project.bg ||
|
|
435
|
+
"rgba(var(--ifm-color-primary-rgb), 0.05)",
|
|
436
|
+
}}
|
|
437
|
+
>
|
|
438
|
+
<img
|
|
439
|
+
src={project.icon}
|
|
440
|
+
alt={project.title}
|
|
441
|
+
className={styles.projectImage}
|
|
442
|
+
loading="lazy"
|
|
443
|
+
/>
|
|
444
|
+
|
|
445
|
+
{/* Project tags */}
|
|
446
|
+
{project.tags?.length > 0 &&
|
|
447
|
+
(() => {
|
|
448
|
+
const extraCount = project.tags.length - 3;
|
|
449
|
+
return (
|
|
450
|
+
<div className={styles.projectTags}>
|
|
451
|
+
{project.tags.slice(0, 3).map((tag) => (
|
|
452
|
+
<span key={tag} className={styles.projectTag}>
|
|
453
|
+
{tag}
|
|
454
|
+
</span>
|
|
455
|
+
))}
|
|
456
|
+
{extraCount > 0 && (
|
|
457
|
+
<Tooltip
|
|
458
|
+
msg={project.tags.slice(3).join(", ")}
|
|
459
|
+
underline={false}
|
|
460
|
+
gap={13}
|
|
461
|
+
>
|
|
462
|
+
<span
|
|
463
|
+
className={`${styles.projectTag} ${styles.extraTagBtn}`}
|
|
464
|
+
>
|
|
465
|
+
+{extraCount}
|
|
466
|
+
</span>
|
|
467
|
+
</Tooltip>
|
|
468
|
+
)}
|
|
469
|
+
</div>
|
|
470
|
+
);
|
|
471
|
+
})()}
|
|
467
472
|
|
|
468
473
|
{/* Featured badge */}
|
|
469
474
|
{project.featured && (
|
|
@@ -480,18 +485,8 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
480
485
|
<div className={styles.projectContent}>
|
|
481
486
|
<h3 className={styles.projectTitle}>{project.title}</h3>
|
|
482
487
|
|
|
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
488
|
<p className={styles.projectDescription}>
|
|
494
|
-
{project.
|
|
489
|
+
{project.desc}
|
|
495
490
|
</p>
|
|
496
491
|
</div>
|
|
497
492
|
|
|
@@ -504,14 +499,14 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
504
499
|
)}
|
|
505
500
|
|
|
506
501
|
{renderProjectLink(
|
|
507
|
-
project.
|
|
508
|
-
|
|
502
|
+
project.repo,
|
|
503
|
+
FaCode,
|
|
509
504
|
"Source",
|
|
510
505
|
`Repository with source code`,
|
|
511
506
|
)}
|
|
512
507
|
|
|
513
508
|
{renderProjectLink(
|
|
514
|
-
project.
|
|
509
|
+
project.demo,
|
|
515
510
|
FaPlay,
|
|
516
511
|
"Demo",
|
|
517
512
|
`Live demo for ${project.title}`,
|
|
@@ -566,16 +561,18 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
566
561
|
</div>
|
|
567
562
|
|
|
568
563
|
{/* Desktop navigation button (right side) */}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
564
|
+
{projects.length > slidesToShow && (
|
|
565
|
+
<button
|
|
566
|
+
className={`${styles.carouselControl} ${styles.nextButton} ${styles.desktopOnly} ${atEnd ? styles.disabledButton : ""}`}
|
|
567
|
+
onClick={goToNext}
|
|
568
|
+
aria-label="View next projects"
|
|
569
|
+
aria-disabled={atEnd}
|
|
570
|
+
type="button"
|
|
571
|
+
disabled={atEnd}
|
|
572
|
+
>
|
|
573
|
+
<FaChevronRight />
|
|
574
|
+
</button>
|
|
575
|
+
)}
|
|
579
576
|
</div>
|
|
580
577
|
)}
|
|
581
578
|
</div>
|