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.
Files changed (60) hide show
  1. package/README.md +26 -126
  2. package/bin/portosaurus.mjs +8 -0
  3. package/package.json +6 -3
  4. package/src/assets/img/icon.png +0 -0
  5. package/src/assets/img/{icon.svg → svg/icon.svg} +35 -37
  6. package/src/assets/img/svg/project-blank.svg +140 -0
  7. package/src/assets/sample-resume.pdf +0 -0
  8. package/src/cli/build.mjs +2 -5
  9. package/src/cli/dev.mjs +27 -5
  10. package/src/cli/init.mjs +6 -12
  11. package/src/cli/schema.mjs +211 -0
  12. package/src/core/buildDocuConfig.mjs +306 -189
  13. package/src/core/constants.mjs +7 -1
  14. package/src/template/config.yml +150 -0
  15. package/src/template/notes/welcome.mdx +6 -0
  16. package/src/template/package.json +3 -3
  17. package/src/theme/MDXComponents.js +0 -1
  18. package/src/theme/components/AboutSection/index.js +39 -17
  19. package/src/theme/components/AboutSection/styles.module.css +151 -344
  20. package/src/theme/components/ContactSection/index.js +29 -14
  21. package/src/theme/components/ContactSection/styles.module.css +19 -8
  22. package/src/theme/components/ExperienceSection/index.js +19 -5
  23. package/src/theme/components/HeroSection/index.js +11 -4
  24. package/src/theme/components/HeroSection/styles.module.css +17 -16
  25. package/src/theme/components/NavArrow/index.js +114 -0
  26. package/src/theme/components/NavArrow/styles.module.css +107 -0
  27. package/src/theme/components/NoteIndex/index.js +66 -95
  28. package/src/theme/components/NoteIndex/styles.module.css +85 -89
  29. package/src/theme/components/Preview/components/FeedbackStates.js +3 -1
  30. package/src/theme/components/Preview/components/PreviewContent.js +91 -0
  31. package/src/theme/components/Preview/components/PreviewHeader.js +41 -33
  32. package/src/theme/components/Preview/components/Triggers/Pv.js +129 -72
  33. package/src/theme/components/Preview/components/ViewerWindow.js +198 -234
  34. package/src/theme/components/Preview/hooks/useAdaptiveSizing.js +115 -0
  35. package/src/theme/components/Preview/hooks/useDeepLinkHash.js +18 -23
  36. package/src/theme/components/Preview/hooks/useDockLayout.js +48 -8
  37. package/src/theme/components/Preview/hooks/useTouchZoom.js +118 -0
  38. package/src/theme/components/Preview/renderers/CodeRenderer.js +64 -25
  39. package/src/theme/components/Preview/state/index.js +70 -17
  40. package/src/theme/components/Preview/styles.module.css +181 -45
  41. package/src/theme/components/Preview/utils/index.js +11 -10
  42. package/src/theme/components/ProjectsSection/index.js +145 -148
  43. package/src/theme/components/ProjectsSection/styles.module.css +178 -112
  44. package/src/theme/components/SocialLinks/index.js +9 -7
  45. package/src/theme/components/Tooltip/index.js +31 -20
  46. package/src/theme/components/Tooltip/styles.module.css +101 -38
  47. package/src/theme/config/iconMappings.js +2 -0
  48. package/src/theme/css/custom.css +72 -0
  49. package/src/theme/hooks/useScrollReveal.js +30 -0
  50. package/src/theme/pages/index.js +7 -27
  51. package/src/theme/pages/notes.js +2 -2
  52. package/src/theme/pages/tasks.js +12 -11
  53. package/src/utils/cliUtils.mjs +23 -51
  54. package/src/utils/configUtils.mjs +95 -84
  55. package/src/utils/systemUtils.mjs +171 -0
  56. package/src/template/config.js +0 -68
  57. package/src/theme/components/ScrollToTop/index.js +0 -95
  58. package/src/theme/components/ScrollToTop/styles.module.css +0 -97
  59. package/src/theme/config/metaTags.js +0 -21
  60. /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
- FaGithub,
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
- /// PART OF THIS COMPONENT IS AI GENERATED
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
- // Default Settings
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, slides) => {
76
- if (!projectList?.length) return { projects: [], totalPages: 0 };
77
-
78
- // Sort featured first
79
- const sortedProjects = [...projectList]
80
- .sort((a, b) => (a.featured ? -1 : 0) - (b.featured ? -1 : 0))
81
- .map((project) => {
82
- // Apply defaults if value not null
83
- const processedProject = {
84
- ...project,
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
- [createPlaceholders, projectDefaults],
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: Math.min(projects.length, slidesToShow),
213
+ slidesToShow: slidesToShow,
245
214
  slidesToScroll: slidesToShow,
246
- autoplay: false,
215
+ autoplay: isAutoplayEnabled,
216
+
217
+ autoplaySpeed: 5000,
218
+ pauseOnHover: true,
247
219
  adaptiveHeight: false,
248
- centerPadding: "20px",
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: (current, next) => {
245
+ beforeChange: (_, next) => {
274
246
  setAtBeginning(next === 0);
275
- setCurrentSlide(Math.floor(next / slidesToShow));
276
- setAtEnd(
277
- next + Math.min(projects.length, slidesToShow) >= projects.length,
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
- className={`${styles.projectsSection} ${className || ""}`}
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}>{title || "My Projects"}</h2>
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
- <button
417
- className={`${styles.carouselControl} ${styles.prevButton} ${styles.desktopOnly} ${atBeginning ? styles.disabledButton : ""}`}
418
- onClick={goToPrev}
419
- aria-label="View previous projects"
420
- aria-disabled={atBeginning}
421
- type="button"
422
- disabled={atBeginning}
423
- >
424
- <FaChevronLeft aria-hidden="true" />
425
- </button>
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 className={styles.projectImageContainer}>
459
- {project.image && (
460
- <img
461
- src={project.image}
462
- alt={project.title}
463
- className={styles.projectImage}
464
- loading="lazy"
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.description}
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.github,
508
- FaGithub,
502
+ project.repo,
503
+ FaCode,
509
504
  "Source",
510
505
  `Repository with source code`,
511
506
  )}
512
507
 
513
508
  {renderProjectLink(
514
- project.Demo,
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
- <button
570
- className={`${styles.carouselControl} ${styles.nextButton} ${styles.desktopOnly} ${atEnd ? styles.disabledButton : ""}`}
571
- onClick={goToNext}
572
- aria-label="View next projects"
573
- aria-disabled={atEnd}
574
- type="button"
575
- disabled={atEnd}
576
- >
577
- <FaChevronRight />
578
- </button>
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>