@portosaur/core 0.1.5 → 0.3.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/package.json +2 -2
- package/src/app.mjs +2 -2
- package/src/generators/docusaurusConfig.mjs +164 -18
- package/src/generators/generateFavicons.mjs +48 -4
- package/src/index.d.ts +1 -1
- package/src/index.mjs +3 -1
- package/src/utils/config.mjs +11 -7
- package/src/utils/cssExtractor.mjs +72 -0
- package/src/utils/docusaurus.mjs +32 -6
- package/src/utils/iconExtractor.mjs +17 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portosaur/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The engine of portosaur that translates YAML configuration into Docusaurus structures.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"author": "soymadip",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"types": "./src/index.d.ts",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@portosaur/logger": "^0.
|
|
30
|
+
"@portosaur/logger": "^0.3.0",
|
|
31
31
|
"favicons": "^7.2.0",
|
|
32
32
|
"js-yaml": "^4.1.1",
|
|
33
33
|
"sharp": "^0.34.5"
|
package/src/app.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import {
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Portosaur application metadata and configuration
|
|
6
6
|
*/
|
|
7
7
|
export const porto = (() => {
|
|
8
8
|
try {
|
|
9
|
-
const pkgPath =
|
|
9
|
+
const pkgPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
10
10
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
11
11
|
|
|
12
12
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { createRequire } from "module";
|
|
3
4
|
import { getGitDate } from "../utils/system.mjs";
|
|
4
5
|
import { porto } from "../app.mjs";
|
|
5
6
|
import { resolveVars, getNestedValue } from "../utils/config.mjs";
|
|
@@ -15,19 +16,10 @@ import {
|
|
|
15
16
|
/**
|
|
16
17
|
* Generates a Docusaurus configuration object from raw user config
|
|
17
18
|
*/
|
|
18
|
-
export function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
) {
|
|
23
|
-
const {
|
|
24
|
-
portoPkg = {},
|
|
25
|
-
portoPaths = {},
|
|
26
|
-
gitDate = null,
|
|
27
|
-
env = process.env,
|
|
28
|
-
} = context;
|
|
29
|
-
|
|
30
|
-
const portoVersion = portoPkg.version ?? "0.0.0";
|
|
19
|
+
export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
20
|
+
const { portoPaths = {}, gitDate = null, env = process.env } = context;
|
|
21
|
+
|
|
22
|
+
const portoVersion = porto.version ?? "0.0.0";
|
|
31
23
|
const lastUpdated = gitDate ?? getGitDate(projectDir);
|
|
32
24
|
|
|
33
25
|
const staticDir = path.resolve(projectDir, "static");
|
|
@@ -65,6 +57,16 @@ export function generateDocusaurusConfig(
|
|
|
65
57
|
|
|
66
58
|
// ------- Configuration Setup -------
|
|
67
59
|
|
|
60
|
+
// Collect static directories: local site static/, theme assets/, and portosaur dot-dir.
|
|
61
|
+
const staticDirectories = [
|
|
62
|
+
"static",
|
|
63
|
+
assetsDir,
|
|
64
|
+
path.join(projectDir, ".docusaurus", "portosaur"),
|
|
65
|
+
].filter((dir) => dir && fs.existsSync(dir));
|
|
66
|
+
|
|
67
|
+
const isDarkMode = get("theme.appearance.dark_mode", true);
|
|
68
|
+
const disableSwitch = get("theme.appearance.disable_switch", false);
|
|
69
|
+
|
|
68
70
|
return {
|
|
69
71
|
projectName: siteName,
|
|
70
72
|
title: siteName,
|
|
@@ -83,6 +85,104 @@ export function generateDocusaurusConfig(
|
|
|
83
85
|
onBrokenLinks: get("site.on_broken_links", "throw"),
|
|
84
86
|
i18n: { defaultLocale: "en", locales: ["en"] },
|
|
85
87
|
|
|
88
|
+
staticDirectories,
|
|
89
|
+
|
|
90
|
+
themeConfig: {
|
|
91
|
+
colorMode: {
|
|
92
|
+
defaultMode: isDarkMode ? "dark" : "light",
|
|
93
|
+
disableSwitch,
|
|
94
|
+
respectPrefersColorScheme: false,
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
navbar: {
|
|
98
|
+
title: siteName,
|
|
99
|
+
logo: {
|
|
100
|
+
alt: `${siteName} logo`,
|
|
101
|
+
src: resolveAsset(get("site.favicon", ""), "img/icon.png"),
|
|
102
|
+
},
|
|
103
|
+
hideOnScroll: get("theme.navigation.hide_navbar_on_scroll", true),
|
|
104
|
+
items: [
|
|
105
|
+
{
|
|
106
|
+
type: "search",
|
|
107
|
+
position: "right",
|
|
108
|
+
className: "navbar-search-bar",
|
|
109
|
+
},
|
|
110
|
+
...(get("home_page.about.enable", true)
|
|
111
|
+
? [
|
|
112
|
+
{
|
|
113
|
+
label: "About Me",
|
|
114
|
+
to: "/#about",
|
|
115
|
+
position: "right",
|
|
116
|
+
activeBasePath: "/never-match",
|
|
117
|
+
},
|
|
118
|
+
]
|
|
119
|
+
: []),
|
|
120
|
+
...(get("home_page.project_shelf.enable", true)
|
|
121
|
+
? [
|
|
122
|
+
{
|
|
123
|
+
label: "Projects",
|
|
124
|
+
to: "/#projects",
|
|
125
|
+
position: "right",
|
|
126
|
+
activeBasePath: "/never-match",
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
: []),
|
|
130
|
+
...(get("home_page.experience.enable", false)
|
|
131
|
+
? [
|
|
132
|
+
{
|
|
133
|
+
label: "Experience",
|
|
134
|
+
to: "/#experience",
|
|
135
|
+
position: "right",
|
|
136
|
+
activeBasePath: "/never-match",
|
|
137
|
+
},
|
|
138
|
+
]
|
|
139
|
+
: []),
|
|
140
|
+
...(get("home_page.social.enable", true)
|
|
141
|
+
? [
|
|
142
|
+
{
|
|
143
|
+
label: "Contact",
|
|
144
|
+
to: "/#contact",
|
|
145
|
+
position: "right",
|
|
146
|
+
activeBasePath: "/never-match",
|
|
147
|
+
},
|
|
148
|
+
]
|
|
149
|
+
: []),
|
|
150
|
+
{
|
|
151
|
+
type: "dropdown",
|
|
152
|
+
label: "More",
|
|
153
|
+
position: "right",
|
|
154
|
+
className: "_navbar-more-items",
|
|
155
|
+
items: [
|
|
156
|
+
{ label: "Notes", to: "/notes" },
|
|
157
|
+
{ label: "Blog", to: "/blog" },
|
|
158
|
+
...(get("tasks.enable", false)
|
|
159
|
+
? [{ label: "Tasks", to: "/tasks" }]
|
|
160
|
+
: []),
|
|
161
|
+
...(!get("theme.appearance.disable_branding", false)
|
|
162
|
+
? [
|
|
163
|
+
{
|
|
164
|
+
label: `Portosaur v${portoVersion}`,
|
|
165
|
+
className: "_nav-portosaur-version",
|
|
166
|
+
href:
|
|
167
|
+
porto?.homepage ||
|
|
168
|
+
"https://github.com/soymadip/portosaur",
|
|
169
|
+
},
|
|
170
|
+
]
|
|
171
|
+
: []),
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
footer: {
|
|
178
|
+
style: "dark",
|
|
179
|
+
copyright: get(
|
|
180
|
+
"site.footer_text",
|
|
181
|
+
`© ${new Date().getFullYear()} ${siteName}. Built with Portosaur.`,
|
|
182
|
+
),
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
|
|
86
186
|
headTags: buildHeadTags([
|
|
87
187
|
{ meta: { name: "generator", content: `Portosaur v${porto.version}` } },
|
|
88
188
|
{ meta: { name: "theme-color", content: "var(--ifm-background-color)" } },
|
|
@@ -132,11 +232,17 @@ export function generateDocusaurusConfig(
|
|
|
132
232
|
subtitle: get("home_page.hero.subtitle", "I am a"),
|
|
133
233
|
profession: get("home_page.hero.profession", "Your Profession"),
|
|
134
234
|
desc: get("home_page.hero.desc", "Welcome to my portfolio."),
|
|
235
|
+
social: get("home_page.hero.social", []),
|
|
236
|
+
learnMoreButtonTxt: get(
|
|
237
|
+
"home_page.hero.learn_more_button_txt",
|
|
238
|
+
"Learn More",
|
|
239
|
+
),
|
|
135
240
|
},
|
|
136
241
|
|
|
137
242
|
aboutSection: {
|
|
138
243
|
enable: get("home_page.about.enable", true),
|
|
139
244
|
heading: get("home_page.about.heading", "About Me"),
|
|
245
|
+
name: get("site.title", "Your Name"),
|
|
140
246
|
image: resolveAsset(get("home_page.about.image", "")),
|
|
141
247
|
bio: get("home_page.about.bio", []),
|
|
142
248
|
skills: get("home_page.about.skills", []),
|
|
@@ -194,20 +300,47 @@ export function generateDocusaurusConfig(
|
|
|
194
300
|
path: "notes",
|
|
195
301
|
sidebarPath: path.resolve(
|
|
196
302
|
portoPaths.theme ?? context.portoRoot ?? "",
|
|
197
|
-
"
|
|
303
|
+
"config/sidebar.jsx",
|
|
198
304
|
),
|
|
199
305
|
},
|
|
200
306
|
blog: { path: "blog", showReadingTime: false },
|
|
201
307
|
theme: {
|
|
202
308
|
customCss: path.resolve(
|
|
203
309
|
portoPaths.theme ?? context.portoRoot ?? "",
|
|
204
|
-
"
|
|
310
|
+
"css/custom.css",
|
|
205
311
|
),
|
|
206
312
|
},
|
|
207
313
|
},
|
|
208
314
|
],
|
|
209
315
|
],
|
|
210
316
|
|
|
317
|
+
// ------- Themes -------
|
|
318
|
+
|
|
319
|
+
themes: [
|
|
320
|
+
[
|
|
321
|
+
(() => {
|
|
322
|
+
const require = createRequire(import.meta.url);
|
|
323
|
+
return require.resolve("@easyops-cn/docusaurus-search-local", {
|
|
324
|
+
paths: [portoPaths.theme ?? context.portoRoot ?? ""],
|
|
325
|
+
});
|
|
326
|
+
})(),
|
|
327
|
+
{
|
|
328
|
+
hashed: true,
|
|
329
|
+
indexDocs: true,
|
|
330
|
+
indexBlog: true,
|
|
331
|
+
indexPages: true,
|
|
332
|
+
docsDir: "notes",
|
|
333
|
+
docsRouteBasePath: "notes",
|
|
334
|
+
searchContextByPaths: ["notes", "blog"],
|
|
335
|
+
highlightSearchTermsOnTargetPage: true,
|
|
336
|
+
explicitSearchResultPath: true,
|
|
337
|
+
hideSearchBarWithNoSearchContext: true,
|
|
338
|
+
searchBarShortcutHint: false,
|
|
339
|
+
language: ["en"],
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
],
|
|
343
|
+
|
|
211
344
|
// ------- Plugins -------
|
|
212
345
|
|
|
213
346
|
plugins: [
|
|
@@ -217,12 +350,25 @@ export function generateDocusaurusConfig(
|
|
|
217
350
|
debug: !env.NODE_ENV || env.NODE_ENV === "development",
|
|
218
351
|
offlineModeActivationStrategies: [
|
|
219
352
|
"always",
|
|
220
|
-
"
|
|
221
|
-
"
|
|
222
|
-
"
|
|
353
|
+
"appInstalled",
|
|
354
|
+
"queryString",
|
|
355
|
+
"standalone",
|
|
223
356
|
],
|
|
224
357
|
},
|
|
225
358
|
],
|
|
359
|
+
|
|
360
|
+
// Serve the theme's pages/ directory as page routes.
|
|
361
|
+
// This registers pages/index.jsx → / and pages/tasks.jsx → /tasks etc.
|
|
362
|
+
[
|
|
363
|
+
"@docusaurus/plugin-content-pages",
|
|
364
|
+
{
|
|
365
|
+
id: "portosaur-pages",
|
|
366
|
+
path: path.resolve(
|
|
367
|
+
portoPaths.theme ?? context.portoRoot ?? "",
|
|
368
|
+
"pages",
|
|
369
|
+
),
|
|
370
|
+
},
|
|
371
|
+
],
|
|
226
372
|
],
|
|
227
373
|
};
|
|
228
374
|
}
|
|
@@ -45,6 +45,36 @@ function processManifest(manifestFile, outputDir, appVersion) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Converts a raw HTML tag string from the favicons library into a Docusaurus
|
|
50
|
+
* headTag object: { tagName, attributes }.
|
|
51
|
+
*
|
|
52
|
+
* Example input: '<link rel="icon" href="/favicon/favicon.ico">'
|
|
53
|
+
* Example output: { tagName: 'link', attributes: { rel: 'icon', href: '/favicon/favicon.ico' } }
|
|
54
|
+
*/
|
|
55
|
+
function parseHtmlTagString(htmlString) {
|
|
56
|
+
const tagMatch = htmlString.match(/^<(\w+)/);
|
|
57
|
+
if (!tagMatch) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const tagName = tagMatch[1];
|
|
62
|
+
const attributes = {};
|
|
63
|
+
const attrRegex = /([\w-]+)(?:=["']([^"']*)["'])?/g;
|
|
64
|
+
|
|
65
|
+
// Skip past the opening tag name to only match attributes
|
|
66
|
+
attrRegex.lastIndex = tagName.length + 1;
|
|
67
|
+
let match;
|
|
68
|
+
while ((match = attrRegex.exec(htmlString)) !== null) {
|
|
69
|
+
if (match[0] === tagName) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
attributes[match[1]] = match[2] ?? "";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { tagName, attributes };
|
|
76
|
+
}
|
|
77
|
+
|
|
48
78
|
// Main Generation Function
|
|
49
79
|
|
|
50
80
|
export async function generateFavicons(siteDir, options = {}) {
|
|
@@ -77,9 +107,16 @@ export async function generateFavicons(siteDir, options = {}) {
|
|
|
77
107
|
if (fs.existsSync(hashFilePath)) {
|
|
78
108
|
const existingHash = fs.readFileSync(hashFilePath, "utf-8");
|
|
79
109
|
if (existingHash === configHash) {
|
|
110
|
+
const htmlFilePath = path.join(outputDir, ".favicon.html");
|
|
80
111
|
if (fs.existsSync(path.join(outputDir, "favicon.ico"))) {
|
|
81
112
|
logger.info("Favicons are up to date, skipping generation.");
|
|
82
|
-
|
|
113
|
+
try {
|
|
114
|
+
const cachedHtml = JSON.parse(fs.readFileSync(htmlFilePath, "utf-8"));
|
|
115
|
+
const headTags = cachedHtml.map(parseHtmlTagString).filter(Boolean);
|
|
116
|
+
return { success: true, html: headTags };
|
|
117
|
+
} catch (e) {
|
|
118
|
+
return { success: true, html: [] };
|
|
119
|
+
}
|
|
83
120
|
}
|
|
84
121
|
}
|
|
85
122
|
}
|
|
@@ -97,7 +134,10 @@ export async function generateFavicons(siteDir, options = {}) {
|
|
|
97
134
|
const iconsToGenerate = ["note", "blog"];
|
|
98
135
|
for (const icon of iconsToGenerate) {
|
|
99
136
|
try {
|
|
100
|
-
await extractSvg(icon, imgDir,
|
|
137
|
+
await extractSvg(icon, imgDir, {
|
|
138
|
+
...iconColor,
|
|
139
|
+
assetsDir: options.portoAssetsDir,
|
|
140
|
+
});
|
|
101
141
|
} catch (e) {}
|
|
102
142
|
}
|
|
103
143
|
|
|
@@ -212,7 +252,10 @@ export async function generateFavicons(siteDir, options = {}) {
|
|
|
212
252
|
const htmlFilePath = path.join(outputDir, ".favicon.html");
|
|
213
253
|
fs.writeFileSync(htmlFilePath, JSON.stringify(response.html), "utf-8");
|
|
214
254
|
tempFiles.forEach(cleanupFile);
|
|
215
|
-
|
|
255
|
+
const headTags = (response.html || [])
|
|
256
|
+
.map(parseHtmlTagString)
|
|
257
|
+
.filter(Boolean);
|
|
258
|
+
return { success: true, html: headTags };
|
|
216
259
|
} catch (error) {
|
|
217
260
|
logger.warn(`Favicon generation skipped: ${error.message}`);
|
|
218
261
|
tempFiles.forEach(cleanupFile);
|
|
@@ -220,7 +263,8 @@ export async function generateFavicons(siteDir, options = {}) {
|
|
|
220
263
|
if (fs.existsSync(htmlFilePath)) {
|
|
221
264
|
try {
|
|
222
265
|
const cachedHtml = JSON.parse(fs.readFileSync(htmlFilePath, "utf-8"));
|
|
223
|
-
|
|
266
|
+
const headTags = cachedHtml.map(parseHtmlTagString).filter(Boolean);
|
|
267
|
+
return { success: false, html: headTags };
|
|
224
268
|
} catch (e) {}
|
|
225
269
|
}
|
|
226
270
|
return { success: false, html: [] };
|
package/src/index.d.ts
CHANGED
|
@@ -31,10 +31,10 @@ export {
|
|
|
31
31
|
openInBrowser,
|
|
32
32
|
} from "./utils/system.mjs";
|
|
33
33
|
export { porto, git, text, limits } from "./app.mjs";
|
|
34
|
+
export { getCssVar } from "./utils/cssExtractor.mjs";
|
|
34
35
|
export { generateFavicons } from "./generators/generateFavicons.mjs";
|
|
35
36
|
export { generateRobotsTxt } from "./generators/generateRobots.mjs";
|
|
36
37
|
export {
|
|
37
|
-
generateDocusaurusConfig,
|
|
38
38
|
buildDocuConfig,
|
|
39
39
|
resolveSiteUrl,
|
|
40
40
|
resolveBasePath,
|
package/src/index.mjs
CHANGED
|
@@ -14,9 +14,11 @@ export {
|
|
|
14
14
|
|
|
15
15
|
export * from "./app.mjs";
|
|
16
16
|
|
|
17
|
+
export { getCssVar } from "./utils/cssExtractor.mjs";
|
|
18
|
+
|
|
17
19
|
export { generateFavicons } from "./generators/generateFavicons.mjs";
|
|
18
20
|
export { generateRobotsTxt } from "./generators/generateRobots.mjs";
|
|
19
|
-
export {
|
|
21
|
+
export { buildDocuConfig } from "./generators/docusaurusConfig.mjs";
|
|
20
22
|
|
|
21
23
|
export {
|
|
22
24
|
resolveSiteUrl,
|
package/src/utils/config.mjs
CHANGED
|
@@ -18,22 +18,26 @@ export function getNestedValue(obj, pathStr, ...fallbacks) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Return if value found at requested path
|
|
21
|
-
if (current !== undefined
|
|
21
|
+
if (current !== undefined && current !== null) {
|
|
22
|
+
return current;
|
|
23
|
+
}
|
|
22
24
|
|
|
23
25
|
// ------- Try fallback paths ----------
|
|
24
|
-
|
|
25
|
-
if (fallback.includes(".")) {
|
|
26
|
-
const val = getNestedValue(obj, fallback);
|
|
26
|
+
if (fallbacks.length === 0) return undefined;
|
|
27
27
|
|
|
28
|
+
const defaultVal = fallbacks[fallbacks.length - 1];
|
|
29
|
+
const altPaths = fallbacks.slice(0, fallbacks.length - 1);
|
|
30
|
+
|
|
31
|
+
for (const fallback of altPaths) {
|
|
32
|
+
if (typeof fallback === "string") {
|
|
33
|
+
const val = getNestedValue(obj, fallback);
|
|
28
34
|
if (val !== undefined) {
|
|
29
35
|
return val;
|
|
30
36
|
}
|
|
31
|
-
} else if (obj[fallback] !== undefined) {
|
|
32
|
-
return obj[fallback];
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
return;
|
|
40
|
+
return defaultVal;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
/**
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { logger } from "@portosaur/logger";
|
|
3
|
+
|
|
4
|
+
// Cache for CSS content and parsed variables to avoid redundant reads
|
|
5
|
+
const cssCache = new Map();
|
|
6
|
+
const varCache = new Map();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extracts a CSS variable's value from an array of CSS files.
|
|
10
|
+
* Supports resolving nested variables (e.g., var(--other-var)).
|
|
11
|
+
*
|
|
12
|
+
* @param {string} varName - The CSS variable name to find (e.g., '--ifm-color-primary').
|
|
13
|
+
* @param {string[]} cssFiles - Array of absolute paths to CSS files to search in.
|
|
14
|
+
* @returns {string|null} The resolved CSS variable value, or null if not found.
|
|
15
|
+
*/
|
|
16
|
+
export function getCssVar(varName, cssFiles = []) {
|
|
17
|
+
// Return cached value if exists
|
|
18
|
+
if (varCache.has(varName)) {
|
|
19
|
+
return varCache.get(varName);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const cssPath of cssFiles) {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(cssPath)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let cssContent = cssCache.get(cssPath);
|
|
29
|
+
if (!cssContent) {
|
|
30
|
+
cssContent = fs.readFileSync(cssPath, "utf8");
|
|
31
|
+
cssCache.set(cssPath, cssContent);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Find all occurrences of the variable (e.g., --var-name: value;)
|
|
35
|
+
// Using a regex that captures everything up to a semicolon or closing brace
|
|
36
|
+
const regex = new RegExp(`${varName}:\\s*([^;}]+)`, "g");
|
|
37
|
+
let lastValue = null;
|
|
38
|
+
let match;
|
|
39
|
+
|
|
40
|
+
// Find all matches and keep the last one (CSS cascade logic)
|
|
41
|
+
while ((match = regex.exec(cssContent)) !== null) {
|
|
42
|
+
lastValue = match[1].replace(/!important/g, "").trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Process nested variables: if the value is var(--something)
|
|
46
|
+
if (lastValue && lastValue.includes("var(")) {
|
|
47
|
+
const nestedMatch = lastValue.match(/var\((--[^)]+)\)/);
|
|
48
|
+
if (nestedMatch) {
|
|
49
|
+
const nestedVar = nestedMatch[1];
|
|
50
|
+
try {
|
|
51
|
+
const resolvedValue = getCssVar(nestedVar, cssFiles);
|
|
52
|
+
if (resolvedValue) {
|
|
53
|
+
varCache.set(varName, resolvedValue);
|
|
54
|
+
return resolvedValue;
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
logger.warn(`Failed to resolve nested variable ${nestedVar}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (lastValue) {
|
|
63
|
+
varCache.set(varName, lastValue);
|
|
64
|
+
return lastValue;
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
logger.warn(`Error processing CSS file ${cssPath}: ${err.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null;
|
|
72
|
+
}
|
package/src/utils/docusaurus.mjs
CHANGED
|
@@ -31,16 +31,42 @@ export function resolveBasePath(configValue, env = process.env) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Creates a function to resolve static asset paths
|
|
34
|
+
* Creates a function to resolve static asset paths.
|
|
35
|
+
* Handles portoRoot-prefixed absolute paths by extracting the bare relative
|
|
36
|
+
* subpath after any "assets/" segment, so they resolve correctly from
|
|
37
|
+
* the registered staticDirectories.
|
|
35
38
|
*/
|
|
36
39
|
export function createStaticAssetResolver(_projectDir, staticDir, assetsDir) {
|
|
37
40
|
return function (primaryPath, fallbackPath = "") {
|
|
38
|
-
if (!primaryPath)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
41
|
+
if (!primaryPath) {
|
|
42
|
+
return fallbackPath;
|
|
43
|
+
}
|
|
44
|
+
if (/^https?:\/\//.test(primaryPath)) {
|
|
45
|
+
return primaryPath;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Strip known prefix segments that arise from {{portoRoot}}/src/assets/...
|
|
49
|
+
// or {{portoRoot}}/assets/... template paths so we get a bare relative path
|
|
50
|
+
// that Docusaurus can serve from its staticDirectories.
|
|
51
|
+
const match = primaryPath.match(/(?:src\/)?assets\/(.+)$/);
|
|
52
|
+
const normalizedPath = match ? match[1] : primaryPath;
|
|
53
|
+
|
|
54
|
+
if (fs.existsSync(path.resolve(staticDir, normalizedPath))) {
|
|
55
|
+
return normalizedPath;
|
|
56
|
+
}
|
|
57
|
+
if (assetsDir && fs.existsSync(path.resolve(assetsDir, normalizedPath))) {
|
|
58
|
+
return normalizedPath;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Fallback: try the original path unchanged
|
|
62
|
+
if (fs.existsSync(path.resolve(staticDir, primaryPath))) {
|
|
63
|
+
return primaryPath;
|
|
64
|
+
}
|
|
65
|
+
if (assetsDir && fs.existsSync(path.resolve(assetsDir, primaryPath))) {
|
|
42
66
|
return primaryPath;
|
|
43
|
-
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return fallbackPath || normalizedPath || primaryPath;
|
|
44
70
|
};
|
|
45
71
|
}
|
|
46
72
|
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
3
|
import { logger } from "@portosaur/logger";
|
|
5
4
|
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
|
|
8
5
|
/**
|
|
9
|
-
* Extracts specific SVGs from
|
|
6
|
+
* Extracts specific SVGs from a given assets directory and saves them to the project.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} iconName - Icon name without the "icon-" prefix or extension.
|
|
9
|
+
* @param {string} destDir - Destination directory to copy the SVG into.
|
|
10
|
+
* @param {Object} options - Optional settings.
|
|
11
|
+
* @param {string} options.assetsDir - Base assets directory containing img/svg/. Required.
|
|
12
|
+
* @param {string} options.color - Optional fill color to inject into the SVG.
|
|
10
13
|
*/
|
|
11
14
|
export async function extractSvg(iconName, destDir, options = {}) {
|
|
15
|
+
if (!options.assetsDir) {
|
|
16
|
+
logger.warn(
|
|
17
|
+
`extractSvg: assetsDir not provided, skipping icon-${iconName}.svg`,
|
|
18
|
+
);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
const srcPath = path.resolve(
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
options.assetsDir,
|
|
24
|
+
`img/svg/icon-${iconName}.svg`,
|
|
15
25
|
);
|
|
16
|
-
|
|
17
26
|
const destPath = path.join(destDir, `icon-${iconName}.svg`);
|
|
18
27
|
|
|
19
28
|
if (!fs.existsSync(srcPath)) {
|
|
@@ -28,6 +37,7 @@ export async function extractSvg(iconName, destDir, options = {}) {
|
|
|
28
37
|
if (options.color) {
|
|
29
38
|
content = content.replace(/fill="[^"]*"/g, `fill="${options.color}"`);
|
|
30
39
|
}
|
|
40
|
+
|
|
31
41
|
fs.writeFileSync(destPath, content);
|
|
32
42
|
|
|
33
43
|
logger.info(`Generated SVG icon: ${destPath}`);
|