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.
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 +305 -188
  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 +32 -17
  19. package/src/theme/components/AboutSection/styles.module.css +151 -344
  20. package/src/theme/components/ContactSection/index.js +23 -14
  21. package/src/theme/components/ContactSection/styles.module.css +19 -8
  22. package/src/theme/components/ExperienceSection/index.js +12 -5
  23. package/src/theme/components/HeroSection/index.js +4 -3
  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 +138 -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,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
- /// PART OF THIS COMPONENT IS AI GENERATED
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
- // 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
- );
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, 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,
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
- [createPlaceholders, projectDefaults],
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: Math.min(projects.length, slidesToShow),
206
+ slidesToShow: slidesToShow,
245
207
  slidesToScroll: slidesToShow,
246
- autoplay: false,
208
+ autoplay: isAutoplayEnabled,
209
+
210
+ autoplaySpeed: 5000,
211
+ pauseOnHover: true,
247
212
  adaptiveHeight: false,
248
- centerPadding: "20px",
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: (current, next) => {
238
+ beforeChange: (_, next) => {
274
239
  setAtBeginning(next === 0);
275
- setCurrentSlide(Math.floor(next / slidesToShow));
276
- setAtEnd(
277
- next + Math.min(projects.length, slidesToShow) >= projects.length,
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
- className={`${styles.projectsSection} ${className || ""}`}
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}>{title || "My Projects"}</h2>
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
- <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>
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 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
- )}
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.description}
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.github,
508
- FaGithub,
495
+ project.repo,
496
+ FaCode,
509
497
  "Source",
510
498
  `Repository with source code`,
511
499
  )}
512
500
 
513
501
  {renderProjectLink(
514
- project.Demo,
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
- <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>
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>