portosaurus 1.16.2 → 1.16.3

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.
@@ -19,7 +19,7 @@ import { spawn, execSync } from "child_process";
19
19
  import { createRequire } from "module";
20
20
 
21
21
  import { logger } from "../src/utils/logger.js";
22
- import { createConfig } from "../src/utils/createConfig.js";
22
+ import { createDocuConf, writeDocuConf } from "../src/utils/createDocuConf.js";
23
23
 
24
24
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
25
 
@@ -62,38 +62,50 @@ async function prepareDocusaurusRun(projectRoot) {
62
62
  // Ensure .portosaurus exists and is clean
63
63
  fs.emptyDirSync(runtimeDir);
64
64
 
65
- // 1. Copy everything from internal to .portosaurus
66
- fs.copySync(internalDir, runtimeDir);
65
+ // Ensure user has essential folders in project root
66
+ fs.ensureDirSync(path.join(projectRoot, "notes"));
67
+ fs.ensureDirSync(path.join(projectRoot, "blog"));
68
+ fs.ensureDirSync(path.join(projectRoot, "static"));
69
+
70
+ // Ensure notes/index.md exists
71
+ const userIndexPage = path.join(projectRoot, "notes/index.md");
72
+ if (!existsSync(userIndexPage)) {
73
+ const internalIndexPage = path.join(internalDir, "notes/index.md");
74
+ if (existsSync(internalIndexPage)) {
75
+ fs.copySync(internalIndexPage, userIndexPage);
76
+ }
77
+ }
78
+
79
+ // 1. Copy everything from internal to .portosaurus (skipping content folders except static)
80
+ fs.copySync(internalDir, runtimeDir, {
81
+ filter: (src) => {
82
+ const relative = path.relative(internalDir, src);
83
+ return !["notes", "blog"].includes(relative);
84
+ },
85
+ });
67
86
 
68
87
  // 2. Overwrite with user files if they exist in project root
69
- const internalFiles = readdirSync(internalDir);
88
+ // We only sync "static" and other internal-shadowing files to runtime
89
+ // Notes and Blog are served directly from projectRoot via createConfig.js
90
+ const syncItems = [...new Set([...readdirSync(internalDir), "static"])];
70
91
 
71
- for (const file of internalFiles) {
72
- const userFile = path.join(projectRoot, file);
92
+ for (const file of syncItems) {
93
+ const userFile = path.resolve(projectRoot, file);
94
+ const destFile = path.resolve(runtimeDir, file);
95
+
96
+ // Skip notes and blog folders in the sync loop - they are served from root!
97
+ if (file === "notes" || file === "blog") continue;
73
98
 
74
99
  if (existsSync(userFile)) {
75
- fs.copySync(userFile, path.join(runtimeDir, file), {
100
+ if (fs.statSync(userFile).isDirectory()) {
101
+ fs.ensureDirSync(destFile);
102
+ }
103
+ fs.copySync(userFile, destFile, {
76
104
  overwrite: true,
77
- filter: (src) => {
78
- // Ignore notes/index.md
79
- if (src.endsWith(path.join("notes", "index.md"))) {
80
- return false;
81
- }
82
- return true;
83
- },
84
105
  });
85
106
  }
86
107
  }
87
108
 
88
- // Ensure user has essential folders
89
- const userRootNotes = path.join(projectRoot, "notes");
90
- const userRootBlog = path.join(projectRoot, "blog");
91
- const userRootStatic = path.join(projectRoot, "static");
92
-
93
- fs.ensureDirSync(userRootNotes);
94
- fs.ensureDirSync(userRootBlog);
95
- fs.ensureDirSync(userRootStatic);
96
-
97
109
  // Load user config
98
110
  const require = createRequire(import.meta.url);
99
111
 
@@ -106,26 +118,10 @@ async function prepareDocusaurusRun(projectRoot) {
106
118
  }
107
119
 
108
120
  // Ensure defaults if missing
109
- if (!userConfig.usrConf) {
110
- userConfig.usrConf = {};
111
- }
112
- if (!userConfig.usrConf.site_url) {
113
- userConfig.usrConf.site_url = "auto";
114
- }
115
- if (!userConfig.usrConf.site_path) {
116
- userConfig.usrConf.site_path = "auto";
117
- }
118
-
119
121
  // Generate Docusaurus Config
120
- const docusaurusConfig = createConfig(userConfig, projectRoot);
121
-
122
122
  // Write temp config file INSIDE .portosaurus
123
123
  const tempConfigPath = path.join(runtimeDir, "docusaurus.config.js");
124
-
125
- const configContent = `// Auto-generated by Portosaurus
126
- module.exports = ${JSON.stringify(docusaurusConfig, null, 2)};`;
127
-
128
- writeFileSync(tempConfigPath, configContent);
124
+ writeDocuConf(userConfig, projectRoot, tempConfigPath);
129
125
  logger.success("Generated Docusaurus config in .portosaurus.");
130
126
 
131
127
  return { runtimeDir };
@@ -302,7 +298,59 @@ program
302
298
  }
303
299
  });
304
300
 
301
+ // --- CONFIG COMMAND ---
302
+
303
+ program
304
+ .command("config")
305
+ .description("Show the generated Docusaurus config")
306
+ .option(
307
+ "-f, --file [filePath]",
308
+ "Output to a file (default: porto.docusaurus.config.js)",
309
+ )
310
+ .option("-c, --config <path>", "Path to config file")
311
+ .action(async (options) => {
312
+ const projectRoot = process.cwd();
313
+ let configPath;
314
+
315
+ if (options.config) {
316
+ configPath = path.resolve(projectRoot, options.config);
317
+ } else {
318
+ configPath = path.join(projectRoot, "config.js");
319
+ }
320
+
321
+ if (!existsSync(configPath)) {
322
+ logger.error(`Config file not found at ${configPath}`);
323
+ process.exit(1);
324
+ }
325
+
326
+ const require = createRequire(import.meta.url);
305
327
 
328
+ let userConfig;
329
+ try {
330
+ userConfig = require(configPath);
331
+ } catch (e) {
332
+ logger.error(`Failed to load config.js: ${e.message}`);
333
+ process.exit(1);
334
+ }
335
+
336
+ try {
337
+ if (options.file) {
338
+ let filename = "porto.docusaurus.config.js";
339
+ if (typeof options.file === "string") {
340
+ filename = options.file;
341
+ }
342
+ const outputPath = path.resolve(projectRoot, filename);
343
+ writeDocuConf(userConfig, projectRoot, outputPath);
344
+ logger.success(`Config written to ${filename}`);
345
+ } else {
346
+ const docusaurusConfig = createDocuConf(userConfig, projectRoot);
347
+ console.log(JSON.stringify(docusaurusConfig, null, 2));
348
+ }
349
+ } catch (error) {
350
+ logger.error(`Failed to generate config: ${error.message}`);
351
+ process.exit(1);
352
+ }
353
+ });
306
354
 
307
355
  // --- START COMMAND ---
308
356
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portosaurus",
3
- "version": "1.16.2",
3
+ "version": "1.16.3",
4
4
  "author": "soymadip",
5
5
  "license": "GPL-3.0-only",
6
6
  "description": "Complete portfolio cum personal website solution for your digital personality.",
@@ -28,24 +28,25 @@
28
28
  "bin/",
29
29
  "src/internal/",
30
30
  "src/template/",
31
- "src/utils/"
31
+ "src/utils/",
32
+ "src/config/"
32
33
  ],
33
34
  "dependencies": {
34
35
  "@docusaurus/core": "^3.9.2",
35
36
  "@docusaurus/preset-classic": "^3.9.2",
37
+ "@easyops-cn/docusaurus-search-local": "^0.52.1",
36
38
  "chalk": "^5.6.2",
39
+ "clsx": "^2.1.1",
37
40
  "commander": "^12.1.0",
41
+ "favicons": "^7.2.0",
38
42
  "fs-extra": "^11.3.3",
43
+ "plugin-image-zoom": "github:flexanalytics/plugin-image-zoom",
44
+ "prism-react-renderer": "^2.4.1",
39
45
  "prompts": "^2.4.2",
40
46
  "react": "^18.3.1",
41
47
  "react-dom": "^18.3.1",
42
- "react-icons": "^5.4.0",
48
+ "react-icons": "^5.5.0",
43
49
  "react-slick": "^0.30.2",
44
- "slick-carousel": "^1.8.1",
45
- "clsx": "^2.1.1",
46
- "prism-react-renderer": "^2.4.1",
47
- "favicons": "^7.2.0",
48
- "plugin-image-zoom": "github:flexanalytics/plugin-image-zoom",
49
- "@easyops-cn/docusaurus-search-local": "^0.52.1"
50
+ "slick-carousel": "^1.8.1"
50
51
  }
51
52
  }
@@ -1,4 +1,4 @@
1
- import { TbBrandCSharp, TbBrandCassandra, TbBrandVscode, TbBrandOnedrive, TbBrandAzure, TbBrandBing, TbBrandGithubCopilot } from "react-icons/tb";
1
+ import { TbBrandCSharp, TbBrandVscode, TbBrandOnedrive, TbBrandAzure, TbBrandBing, TbBrandGithubCopilot } from "react-icons/tb";
2
2
  import { GrVirtualMachine } from "react-icons/gr";
3
3
  import { DiRasberryPi } from "react-icons/di";
4
4
  import { PiGithubLogoFill, PiMicrosoftExcelLogoDuotone, PiMicrosoftOutlookLogo, PiMicrosoftPowerpointLogo, PiMicrosoftWordLogo } from "react-icons/pi";
@@ -0,0 +1,239 @@
1
+ import { getCssVar } from "../internal/src/utils/cssUtils.js";
2
+
3
+ const backgroundColor = getCssVar("--ifm-background-color");
4
+
5
+ export const metaTags = [
6
+ // Theme color meta tags
7
+ {
8
+ tagName: "meta",
9
+ attributes: {
10
+ name: "msapplication-TileColor",
11
+ content: backgroundColor,
12
+ },
13
+ },
14
+ {
15
+ tagName: "meta",
16
+ attributes: {
17
+ name: "theme-color",
18
+ content: backgroundColor,
19
+ },
20
+ },
21
+
22
+ // Android Chrome icons
23
+ {
24
+ tagName: "link",
25
+ attributes: {
26
+ rel: "icon",
27
+ type: "image/png",
28
+ sizes: "36x36",
29
+ href: "/favicon/android-chrome-36x36.png",
30
+ },
31
+ },
32
+ {
33
+ tagName: "link",
34
+ attributes: {
35
+ rel: "icon",
36
+ type: "image/png",
37
+ sizes: "48x48",
38
+ href: "/favicon/android-chrome-48x48.png",
39
+ },
40
+ },
41
+ {
42
+ tagName: "link",
43
+ attributes: {
44
+ rel: "icon",
45
+ type: "image/png",
46
+ sizes: "72x72",
47
+ href: "/favicon/android-chrome-72x72.png",
48
+ },
49
+ },
50
+ {
51
+ tagName: "link",
52
+ attributes: {
53
+ rel: "icon",
54
+ type: "image/png",
55
+ sizes: "96x96",
56
+ href: "/favicon/android-chrome-96x96.png",
57
+ },
58
+ },
59
+ {
60
+ tagName: "link",
61
+ attributes: {
62
+ rel: "icon",
63
+ type: "image/png",
64
+ sizes: "144x144",
65
+ href: "/favicon/android-chrome-144x144.png",
66
+ },
67
+ },
68
+ {
69
+ tagName: "link",
70
+ attributes: {
71
+ rel: "icon",
72
+ type: "image/png",
73
+ sizes: "192x192",
74
+ href: "/favicon/android-chrome-192x192.png",
75
+ },
76
+ },
77
+ {
78
+ tagName: "link",
79
+ attributes: {
80
+ rel: "icon",
81
+ type: "image/png",
82
+ sizes: "256x256",
83
+ href: "/favicon/android-chrome-256x256.png",
84
+ },
85
+ },
86
+ {
87
+ tagName: "link",
88
+ attributes: {
89
+ rel: "icon",
90
+ type: "image/png",
91
+ sizes: "384x384",
92
+ href: "/favicon/android-chrome-384x384.png",
93
+ },
94
+ },
95
+ {
96
+ tagName: "link",
97
+ attributes: {
98
+ rel: "icon",
99
+ type: "image/png",
100
+ sizes: "512x512",
101
+ href: "/favicon/android-chrome-512x512.png",
102
+ },
103
+ },
104
+
105
+ // Apple touch icons
106
+ {
107
+ tagName: "link",
108
+ attributes: {
109
+ rel: "apple-touch-icon",
110
+ sizes: "57x57",
111
+ href: "/favicon/apple-touch-icon-57x57.png",
112
+ },
113
+ },
114
+ {
115
+ tagName: "link",
116
+ attributes: {
117
+ rel: "apple-touch-icon",
118
+ sizes: "60x60",
119
+ href: "/favicon/apple-touch-icon-60x60.png",
120
+ },
121
+ },
122
+ {
123
+ tagName: "link",
124
+ attributes: {
125
+ rel: "apple-touch-icon",
126
+ sizes: "72x72",
127
+ href: "/favicon/apple-touch-icon-72x72.png",
128
+ },
129
+ },
130
+ {
131
+ tagName: "link",
132
+ attributes: {
133
+ rel: "apple-touch-icon",
134
+ sizes: "76x76",
135
+ href: "/favicon/apple-touch-icon-76x76.png",
136
+ },
137
+ },
138
+ {
139
+ tagName: "link",
140
+ attributes: {
141
+ rel: "apple-touch-icon",
142
+ sizes: "114x114",
143
+ href: "/favicon/apple-touch-icon-114x114.png",
144
+ },
145
+ },
146
+ {
147
+ tagName: "link",
148
+ attributes: {
149
+ rel: "apple-touch-icon",
150
+ sizes: "120x120",
151
+ href: "/favicon/apple-touch-icon-120x120.png",
152
+ },
153
+ },
154
+ {
155
+ tagName: "link",
156
+ attributes: {
157
+ rel: "apple-touch-icon",
158
+ sizes: "144x144",
159
+ href: "/favicon/apple-touch-icon-144x144.png",
160
+ },
161
+ },
162
+ {
163
+ tagName: "link",
164
+ attributes: {
165
+ rel: "apple-touch-icon",
166
+ sizes: "152x152",
167
+ href: "/favicon/apple-touch-icon-152x152.png",
168
+ },
169
+ },
170
+ {
171
+ tagName: "link",
172
+ attributes: {
173
+ rel: "apple-touch-icon",
174
+ sizes: "167x167",
175
+ href: "/favicon/apple-touch-icon-167x167.png",
176
+ },
177
+ },
178
+ {
179
+ tagName: "link",
180
+ attributes: {
181
+ rel: "apple-touch-icon",
182
+ sizes: "180x180",
183
+ href: "/favicon/apple-touch-icon-180x180.png",
184
+ },
185
+ },
186
+ {
187
+ tagName: "link",
188
+ attributes: {
189
+ rel: "apple-touch-icon",
190
+ sizes: "1024x1024",
191
+ href: "/favicon/apple-touch-icon-1024x1024.png",
192
+ },
193
+ },
194
+ {
195
+ tagName: "link",
196
+ attributes: {
197
+ rel: "apple-touch-icon-precomposed",
198
+ href: "/favicon/apple-touch-icon-precomposed.png",
199
+ },
200
+ },
201
+
202
+ // Standard favicons
203
+ {
204
+ tagName: "link",
205
+ attributes: {
206
+ rel: "icon",
207
+ type: "image/png",
208
+ sizes: "16x16",
209
+ href: "/favicon/favicon-16x16.png",
210
+ },
211
+ },
212
+ {
213
+ tagName: "link",
214
+ attributes: {
215
+ rel: "icon",
216
+ type: "image/png",
217
+ sizes: "32x32",
218
+ href: "/favicon/favicon-32x32.png",
219
+ },
220
+ },
221
+ {
222
+ tagName: "link",
223
+ attributes: {
224
+ rel: "icon",
225
+ type: "image/png",
226
+ sizes: "48x48",
227
+ href: "/favicon/favicon-48x48.png",
228
+ },
229
+ },
230
+
231
+ // Web manifest
232
+ {
233
+ tagName: "link",
234
+ attributes: {
235
+ rel: "manifest",
236
+ href: "/favicon/manifest.webmanifest",
237
+ },
238
+ },
239
+ ];
@@ -1,5 +1,4 @@
1
1
  import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
2
- import { iconMap } from "@site/src/config/iconMappings";
3
2
  import { FaQuestionCircle } from "react-icons/fa";
4
3
  import styles from "./styles.module.css";
5
4
 
@@ -25,6 +24,8 @@ const sortEmail = (links) => {
25
24
  export default function ContactSection({ id, className, title, subtitle }) {
26
25
  const { siteConfig } = useDocusaurusContext();
27
26
  const { customFields } = siteConfig;
27
+ const { iconMap } = customFields.iconMap;
28
+
28
29
  let socialLinks = customFields.socialLinks.links || [];
29
30
 
30
31
  socialLinks = sortEmail(socialLinks);
@@ -1,9 +1,11 @@
1
1
  import Link from "@docusaurus/Link";
2
2
  import useBaseUrl from "@docusaurus/useBaseUrl";
3
3
  import { usePluginData } from "@docusaurus/useGlobalData";
4
- import { iconMap } from "@site/src/config/iconMappings";
4
+ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
5
5
  import DocCardList from "@theme/DocCardList";
6
6
 
7
+ const { iconMap } = useDocusaurusContext().siteConfig.customFields.iconMap;
8
+
7
9
  import { FaBook } from "react-icons/fa";
8
10
  import styles from "./styles.module.css";
9
11
 
@@ -1,75 +1,70 @@
1
- import { useState, useEffect, useMemo, useCallback } from 'react';
2
- import styles from './styles.module.css';
3
- import { FaQuestionCircle } from 'react-icons/fa';
1
+ import { useState, useEffect, useMemo, useCallback } from "react";
2
+ import styles from "./styles.module.css";
3
+ import { FaQuestionCircle } from "react-icons/fa";
4
4
 
5
- import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
6
- import useIsBrowser from '@docusaurus/useIsBrowser';
7
-
8
- import Tooltip from '@site/src/components/Tooltip';
9
- import { iconMap } from '@site/src/config/iconMappings';
5
+ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
6
+ import useIsBrowser from "@docusaurus/useIsBrowser";
10
7
 
8
+ import Tooltip from "@site/src/components/Tooltip";
11
9
 
12
10
  // Default icon & icon
13
11
  const DEFAULT_ICON = FaQuestionCircle;
14
- const DEFAULT_COLOR = 'var(--ifm-color-primary)';
15
-
12
+ const DEFAULT_COLOR = "var(--ifm-color-primary)";
16
13
 
17
14
  export default function SocialIcons({ showAll = false }) {
18
15
  const { siteConfig } = useDocusaurusContext();
19
16
  const { customFields } = siteConfig;
17
+ const { iconMap } = customFields.iconMap;
18
+
20
19
  const isBrowser = useIsBrowser();
21
20
 
22
21
  const [animationDelays, setAnimationDelays] = useState({});
23
22
 
24
23
  const allSocialLinks = customFields.socialLinks.links || [];
25
-
24
+
26
25
  // FIX: `to prevent unnecessary recalculations`
27
26
  const socialLinks = useMemo(() => {
28
- return showAll
29
- ? allSocialLinks
30
- : allSocialLinks.filter(link => link.pin);
27
+ return showAll ? allSocialLinks : allSocialLinks.filter((link) => link.pin);
31
28
  }, [allSocialLinks, showAll]);
32
-
29
+
33
30
  // Calculate delays based on screen size
34
31
  const calculateDelays = useCallback(() => {
35
32
  if (!isBrowser) return {};
36
-
33
+
37
34
  const isTablet = window.innerWidth <= 768;
38
35
  const isMobile = window.innerWidth <= 480;
39
36
  const delays = {};
40
-
41
- const baseDelay = isMobile ? 0.7 : (isTablet ? 0.9 : 1.3);
37
+
38
+ const baseDelay = isMobile ? 0.7 : isTablet ? 0.9 : 1.3;
42
39
  const incrementDelay = 0.1;
43
-
40
+
44
41
  socialLinks.forEach((_, index) => {
45
- delays[index] = `${baseDelay + (index * incrementDelay)}s`;
42
+ delays[index] = `${baseDelay + index * incrementDelay}s`;
46
43
  });
47
-
44
+
48
45
  return delays;
49
46
  }, [isBrowser, socialLinks]);
50
47
 
51
48
  useEffect(() => {
52
49
  if (!isBrowser) return;
53
-
50
+
54
51
  // Set initial delays
55
52
  setAnimationDelays(calculateDelays());
56
-
53
+
57
54
  const handleResize = () => {
58
55
  setAnimationDelays(calculateDelays());
59
56
  };
60
-
61
- window.addEventListener('resize', handleResize);
62
- return () => window.removeEventListener('resize', handleResize);
63
- }, [isBrowser, calculateDelays]);
64
57
 
58
+ window.addEventListener("resize", handleResize);
59
+ return () => window.removeEventListener("resize", handleResize);
60
+ }, [isBrowser, calculateDelays]);
65
61
 
66
62
  // Get icon component and color
67
63
  const getIconDetails = (iconName) => {
68
-
69
64
  if (!iconName) {
70
65
  return {
71
66
  icon: DEFAULT_ICON,
72
- color: DEFAULT_COLOR
67
+ color: DEFAULT_COLOR,
73
68
  };
74
69
  }
75
70
 
@@ -79,50 +74,51 @@ export default function SocialIcons({ showAll = false }) {
79
74
  if (!iconDetails) {
80
75
  return {
81
76
  icon: DEFAULT_ICON,
82
- color: DEFAULT_COLOR
77
+ color: DEFAULT_COLOR,
83
78
  };
84
79
  }
85
80
 
86
81
  return {
87
82
  icon: iconDetails.icon,
88
- color: iconDetails.color || DEFAULT_COLOR
83
+ color: iconDetails.color || DEFAULT_COLOR,
89
84
  };
90
85
  };
91
-
86
+
92
87
  if (socialLinks.length === 0) {
93
88
  return null;
94
89
  }
95
-
90
+
96
91
  return (
97
92
  <div className={styles.socialIcons}>
98
- {
99
- socialLinks.map((social, index) => {
100
- const { icon: IconComponent, color: iconColor } = getIconDetails(social.icon);
101
- const href = social.url || '#';
102
- const displayColor = social.color || iconColor;
103
-
104
- return (
105
- <Tooltip
106
- key={index}
107
- content={social.desc || social.icon || 'Link'}
108
- position="top"
109
- color={displayColor}
93
+ {socialLinks.map((social, index) => {
94
+ const { icon: IconComponent, color: iconColor } = getIconDetails(
95
+ social.icon,
96
+ );
97
+ const href = social.url || "#";
98
+ const displayColor = social.color || iconColor;
99
+
100
+ return (
101
+ <Tooltip
102
+ key={index}
103
+ content={social.desc || social.icon || "Link"}
104
+ position="top"
105
+ color={displayColor}
106
+ >
107
+ <a
108
+ href={href}
109
+ target="_blank"
110
+ rel="noopener noreferrer"
111
+ className={styles.socialLink}
112
+ style={{
113
+ "--hover-color": displayColor,
114
+ animationDelay: animationDelays[index] || "0s",
115
+ }}
116
+ aria-label={social.icon || "social link"}
110
117
  >
111
- <a
112
- href={href}
113
- target="_blank"
114
- rel="noopener noreferrer"
115
- className={styles.socialLink}
116
- style={{
117
- '--hover-color': displayColor,
118
- animationDelay: animationDelays[index] || '0s'
119
- }}
120
- aria-label={social.icon || 'social link'}
121
- >
122
- <IconComponent size={24} />
123
- </a>
124
- </Tooltip>
125
- );
118
+ <IconComponent size={24} />
119
+ </a>
120
+ </Tooltip>
121
+ );
126
122
  })}
127
123
  </div>
128
124
  );