portosaurus 2.0.2 → 2.1.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/bin/portosaurus.mjs +14 -327
- package/package.json +16 -11
- package/src/cli/build.mjs +43 -0
- package/src/cli/dev.mjs +31 -0
- package/src/cli/init.mjs +135 -0
- package/src/cli/serve.mjs +30 -0
- package/src/core/buildDocuConfig.mjs +664 -0
- package/src/core/{themePlugin.mjs → plugins/themePlugin.mjs} +1 -1
- package/src/template/.github/workflows/deploy.yml +52 -0
- package/src/template/.nojekyll +0 -0
- package/src/template/README.md +58 -0
- package/src/template/blog/authors.yml +1 -1
- package/src/template/blog/welcome.md +1 -1
- package/src/template/config.js +40 -23
- package/src/template/package.json +20 -0
- package/src/template/static/img/svg/icon-blog.svg +2 -0
- package/src/template/static/img/svg/icon-note.svg +2 -0
- package/src/{components → theme/components}/AboutSection/index.js +22 -13
- package/src/{components → theme/components}/AboutSection/styles.module.css +59 -48
- package/src/{components → theme/components}/ContactSection/index.js +31 -24
- package/src/{components → theme/components}/ContactSection/styles.module.css +31 -26
- package/src/{components → theme/components}/ExperienceSection/index.js +12 -7
- package/src/{components → theme/components}/ExperienceSection/styles.module.css +23 -20
- package/src/{components → theme/components}/HeroSection/index.js +9 -11
- package/src/{components → theme/components}/HeroSection/styles.module.css +44 -32
- package/src/{components → theme/components}/NoteIndex/index.js +10 -3
- package/src/{components → theme/components}/Preview/components/PreviewHeader.js +14 -8
- package/src/{components → theme/components}/Preview/components/Triggers/Pv.js +32 -7
- package/src/{components → theme/components}/Preview/components/Triggers/SrcPv.js +1 -5
- package/src/theme/components/Preview/index.js +3 -0
- package/src/{components → theme/components}/ProjectsSection/index.js +279 -224
- package/src/{components → theme/components}/ProjectsSection/styles.module.css +21 -17
- package/src/{components → theme/components}/ScrollToTop/index.js +18 -21
- package/src/{components → theme/components}/ScrollToTop/styles.module.css +10 -9
- package/src/theme/components/SocialLinks/index.js +125 -0
- package/src/{components → theme/components}/SocialLinks/styles.module.css +9 -7
- package/src/{components → theme/components}/Tooltip/index.js +4 -1
- package/src/theme/config/iconMappings.js +465 -0
- package/src/theme/config/metaTags.js +239 -0
- package/src/theme/config/prism.js +179 -0
- package/src/theme/config/sidebar.js +17 -0
- package/src/{css → theme/css}/bootstrap.css +0 -1
- package/src/theme/css/catppuccin.css +618 -0
- package/src/{css → theme/css}/custom.css +3 -9
- package/src/{css → theme/css}/tasks.css +43 -37
- package/src/theme/{MDXComponents.js → overrides/MDXComponents.js} +3 -3
- package/src/theme/{Root.js → overrides/Root.js} +2 -4
- package/src/{pages → theme/pages}/index.js +23 -39
- package/src/theme/pages/notes.js +83 -0
- package/src/{pages → theme/pages}/tasks.js +115 -56
- package/src/{core/client-utils → theme/utils}/HashNavigation.js +60 -49
- package/src/{core/client-utils → theme/utils}/updateTitle.js +21 -25
- package/src/{core/build-utils → utils/build}/cssUtils.mjs +5 -3
- package/src/{core/build-utils → utils/build}/generateFavicon.mjs +44 -12
- package/src/{core/build-utils → utils/build}/generateRobotsTxt.mjs +4 -3
- package/src/{core/build-utils → utils/build}/iconExtractor.mjs +7 -3
- package/src/utils/build/imageDownloader.mjs +159 -0
- package/src/{core/build-utils → utils/build}/imageProcessor.mjs +5 -6
- package/src/utils/helpers.mjs +153 -0
- package/src/utils/logger.mjs +53 -0
- package/src/utils/packageManager.mjs +88 -0
- package/src/components/Preview/index.js +0 -3
- package/src/components/SocialLinks/index.js +0 -130
- package/src/config/iconMappings.js +0 -329
- package/src/config/metaTags.js +0 -240
- package/src/config/prism.js +0 -179
- package/src/config/sidebar.js +0 -20
- package/src/core/build-utils/imageDownloader.mjs +0 -98
- package/src/core/createDocuConf.mjs +0 -490
- package/src/core/defaults.mjs +0 -67
- package/src/core/logger.mjs +0 -17
- package/src/core/packageManager.mjs +0 -72
- package/src/css/catppuccin.css +0 -632
- package/src/pages/notes.js +0 -87
- /package/src/template/notes/{welcome.md → welcome.mdx} +0 -0
- /package/src/{components → theme/components}/NoteIndex/styles.module.css +0 -0
- /package/src/{components → theme/components}/Preview/components/FeedbackStates.js +0 -0
- /package/src/{components → theme/components}/Preview/components/FileTabs.js +0 -0
- /package/src/{components → theme/components}/Preview/components/Triggers/index.js +0 -0
- /package/src/{components → theme/components}/Preview/components/ViewerWindow.js +0 -0
- /package/src/{components → theme/components}/Preview/hooks/useDeepLinkHash.js +0 -0
- /package/src/{components → theme/components}/Preview/hooks/useDockLayout.js +0 -0
- /package/src/{components → theme/components}/Preview/hooks/useFileFetch.js +0 -0
- /package/src/{components → theme/components}/Preview/renderers/CodeRenderer.js +0 -0
- /package/src/{components → theme/components}/Preview/renderers/ImageRenderer.js +0 -0
- /package/src/{components → theme/components}/Preview/renderers/PdfRenderer.js +0 -0
- /package/src/{components → theme/components}/Preview/renderers/WebRenderer.js +0 -0
- /package/src/{components → theme/components}/Preview/state/index.js +0 -0
- /package/src/{components → theme/components}/Preview/styles.module.css +0 -0
- /package/src/{components → theme/components}/Preview/utils/index.js +0 -0
- /package/src/{components → theme/components}/Tooltip/styles.module.css +0 -0
|
@@ -8,23 +8,22 @@
|
|
|
8
8
|
This Page is completely Vibe coded. No code except small tweaks is written by me.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import Head from '@docusaurus/Head';
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import Layout from "@theme/Layout";
|
|
13
|
+
import Head from "@docusaurus/Head";
|
|
15
14
|
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
FaClipboardList,
|
|
19
|
-
FaSyncAlt,
|
|
20
|
-
FaClock,
|
|
15
|
+
import "../css/tasks.css";
|
|
16
|
+
import {
|
|
17
|
+
FaClipboardList,
|
|
18
|
+
FaSyncAlt,
|
|
19
|
+
FaClock,
|
|
21
20
|
FaCheckCircle,
|
|
22
21
|
FaFire,
|
|
23
22
|
FaThermometerHalf,
|
|
24
23
|
FaSnowflake,
|
|
25
24
|
FaTasks,
|
|
26
|
-
FaExclamationTriangle
|
|
27
|
-
} from
|
|
25
|
+
FaExclamationTriangle,
|
|
26
|
+
} from "react-icons/fa";
|
|
28
27
|
|
|
29
28
|
function TaskList({ filterStatus, taskList }) {
|
|
30
29
|
if (!taskList || !Array.isArray(taskList)) {
|
|
@@ -35,9 +34,9 @@ function TaskList({ filterStatus, taskList }) {
|
|
|
35
34
|
</div>
|
|
36
35
|
);
|
|
37
36
|
}
|
|
38
|
-
|
|
39
|
-
const filteredTasks = taskList.filter(task =>
|
|
40
|
-
filterStatus ? task.status === filterStatus : true
|
|
37
|
+
|
|
38
|
+
const filteredTasks = taskList.filter((task) =>
|
|
39
|
+
filterStatus ? task.status === filterStatus : true,
|
|
41
40
|
);
|
|
42
41
|
|
|
43
42
|
if (filteredTasks.length === 0) {
|
|
@@ -53,14 +52,14 @@ function TaskList({ filterStatus, taskList }) {
|
|
|
53
52
|
const sortedTasks = [...filteredTasks].sort((a, b) => {
|
|
54
53
|
const statusOrder = { active: 1, pending: 2, completed: 3 };
|
|
55
54
|
const statusDiff = statusOrder[a.status] - statusOrder[b.status];
|
|
56
|
-
|
|
55
|
+
|
|
57
56
|
if (statusDiff !== 0) return statusDiff;
|
|
58
|
-
|
|
57
|
+
|
|
59
58
|
// Priority order: high, medium, low
|
|
60
59
|
const priorityOrder = { high: 1, medium: 2, low: 3 };
|
|
61
60
|
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
62
61
|
});
|
|
63
|
-
|
|
62
|
+
|
|
64
63
|
return (
|
|
65
64
|
<div className="task-list-container">
|
|
66
65
|
<div className="task-list-table">
|
|
@@ -72,15 +71,27 @@ function TaskList({ filterStatus, taskList }) {
|
|
|
72
71
|
|
|
73
72
|
<div className="task-rows">
|
|
74
73
|
{sortedTasks.map((task, index) => (
|
|
75
|
-
<div
|
|
76
|
-
key={index}
|
|
77
|
-
className={`task-row ${task.status ===
|
|
74
|
+
<div
|
|
75
|
+
key={index}
|
|
76
|
+
className={`task-row ${task.status === "completed" ? "task-row-completed" : ""} ${index % 2 === 1 ? "task-row-striped" : ""}`}
|
|
78
77
|
>
|
|
79
78
|
<div className="task-cell task-cell-status">
|
|
80
79
|
<span className={`badge badge-status-${task.status}`}>
|
|
81
|
-
{task.status ===
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
{task.status === "completed" && (
|
|
81
|
+
<>
|
|
82
|
+
<FaCheckCircle className="badge-icon" /> Done
|
|
83
|
+
</>
|
|
84
|
+
)}
|
|
85
|
+
{task.status === "active" && (
|
|
86
|
+
<>
|
|
87
|
+
<FaSyncAlt className="badge-icon spin" /> In Progress
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
90
|
+
{task.status === "pending" && (
|
|
91
|
+
<>
|
|
92
|
+
<FaClock className="badge-icon" /> Planned
|
|
93
|
+
</>
|
|
94
|
+
)}
|
|
84
95
|
</span>
|
|
85
96
|
</div>
|
|
86
97
|
|
|
@@ -93,9 +104,21 @@ function TaskList({ filterStatus, taskList }) {
|
|
|
93
104
|
|
|
94
105
|
<div className="task-cell task-cell-priority">
|
|
95
106
|
<span className={`badge badge-priority-${task.priority}`}>
|
|
96
|
-
{task.priority ===
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
{task.priority === "high" && (
|
|
108
|
+
<>
|
|
109
|
+
<FaFire className="badge-icon" /> High
|
|
110
|
+
</>
|
|
111
|
+
)}
|
|
112
|
+
{task.priority === "medium" && (
|
|
113
|
+
<>
|
|
114
|
+
<FaThermometerHalf className="badge-icon" /> Medium
|
|
115
|
+
</>
|
|
116
|
+
)}
|
|
117
|
+
{task.priority === "low" && (
|
|
118
|
+
<>
|
|
119
|
+
<FaSnowflake className="badge-icon" /> Low
|
|
120
|
+
</>
|
|
121
|
+
)}
|
|
99
122
|
</span>
|
|
100
123
|
</div>
|
|
101
124
|
</div>
|
|
@@ -106,16 +129,17 @@ function TaskList({ filterStatus, taskList }) {
|
|
|
106
129
|
);
|
|
107
130
|
}
|
|
108
131
|
|
|
109
|
-
|
|
110
132
|
function TaskStats({ taskList }) {
|
|
111
133
|
if (!taskList || !Array.isArray(taskList)) {
|
|
112
134
|
return null;
|
|
113
135
|
}
|
|
114
|
-
|
|
136
|
+
|
|
115
137
|
const total = taskList.length;
|
|
116
|
-
const completed = taskList.filter(
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
const completed = taskList.filter(
|
|
139
|
+
(task) => task.status === "completed",
|
|
140
|
+
).length;
|
|
141
|
+
const active = taskList.filter((task) => task.status === "active").length;
|
|
142
|
+
const pending = taskList.filter((task) => task.status === "pending").length;
|
|
119
143
|
const percentComplete = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
120
144
|
|
|
121
145
|
return (
|
|
@@ -140,7 +164,10 @@ function TaskStats({ taskList }) {
|
|
|
140
164
|
<div className="stat-label">Progress</div>
|
|
141
165
|
<div className="stat-value">{percentComplete}%</div>
|
|
142
166
|
<div className="progress-bar-container">
|
|
143
|
-
<div
|
|
167
|
+
<div
|
|
168
|
+
className="progress-bar"
|
|
169
|
+
style={{ width: `${percentComplete}%` }}
|
|
170
|
+
></div>
|
|
144
171
|
</div>
|
|
145
172
|
</div>
|
|
146
173
|
</div>
|
|
@@ -148,55 +175,82 @@ function TaskStats({ taskList }) {
|
|
|
148
175
|
}
|
|
149
176
|
|
|
150
177
|
function TaskTabs({ taskList }) {
|
|
151
|
-
const [activeTab, setActiveTab] = useState(
|
|
152
|
-
|
|
178
|
+
const [activeTab, setActiveTab] = useState("all");
|
|
179
|
+
|
|
153
180
|
if (!taskList || !Array.isArray(taskList)) {
|
|
154
181
|
return null;
|
|
155
182
|
}
|
|
156
|
-
|
|
183
|
+
|
|
157
184
|
const tabData = [
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
185
|
+
{
|
|
186
|
+
id: "all",
|
|
187
|
+
label: "All Tasks",
|
|
188
|
+
icon: <FaClipboardList />,
|
|
189
|
+
count: taskList.length,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "active",
|
|
193
|
+
label: "In Progress",
|
|
194
|
+
icon: <FaSyncAlt className="spin" />,
|
|
195
|
+
count: taskList.filter((t) => t.status === "active").length,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "pending",
|
|
199
|
+
label: "Planned",
|
|
200
|
+
icon: <FaClock />,
|
|
201
|
+
count: taskList.filter((t) => t.status === "pending").length,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "completed",
|
|
205
|
+
label: "Completed",
|
|
206
|
+
icon: <FaCheckCircle />,
|
|
207
|
+
count: taskList.filter((t) => t.status === "completed").length,
|
|
208
|
+
},
|
|
162
209
|
];
|
|
163
|
-
|
|
210
|
+
|
|
164
211
|
return (
|
|
165
212
|
<div className="task-tabs-container">
|
|
166
213
|
<div className="task-tabs" role="tablist" aria-label="Task categories">
|
|
167
|
-
{tabData.map(tab => (
|
|
168
|
-
<button
|
|
214
|
+
{tabData.map((tab) => (
|
|
215
|
+
<button
|
|
169
216
|
key={tab.id}
|
|
170
|
-
className={`task-tab ${activeTab === tab.id ?
|
|
217
|
+
className={`task-tab ${activeTab === tab.id ? "task-tab-active" : ""}`}
|
|
171
218
|
onClick={() => setActiveTab(tab.id)}
|
|
172
219
|
role="tab"
|
|
173
220
|
aria-selected={activeTab === tab.id}
|
|
174
221
|
aria-controls={`tab-content-${tab.id}`}
|
|
175
222
|
id={`tab-${tab.id}`}
|
|
176
223
|
>
|
|
177
|
-
<span className="task-tab-icon" aria-hidden="true">
|
|
224
|
+
<span className="task-tab-icon" aria-hidden="true">
|
|
225
|
+
{tab.icon}
|
|
226
|
+
</span>
|
|
178
227
|
<span className="task-tab-label">{tab.label}</span>
|
|
179
228
|
<span className="task-tab-count">{tab.count}</span>
|
|
180
229
|
</button>
|
|
181
230
|
))}
|
|
182
231
|
</div>
|
|
183
|
-
|
|
184
|
-
<div
|
|
185
|
-
className="task-tab-content"
|
|
186
|
-
role="tabpanel"
|
|
232
|
+
|
|
233
|
+
<div
|
|
234
|
+
className="task-tab-content"
|
|
235
|
+
role="tabpanel"
|
|
187
236
|
id={`tab-content-${activeTab}`}
|
|
188
237
|
aria-labelledby={`tab-${activeTab}`}
|
|
189
238
|
>
|
|
190
|
-
{activeTab ===
|
|
191
|
-
{activeTab ===
|
|
192
|
-
|
|
193
|
-
|
|
239
|
+
{activeTab === "all" && <TaskList taskList={taskList} />}
|
|
240
|
+
{activeTab === "active" && (
|
|
241
|
+
<TaskList taskList={taskList} filterStatus="active" />
|
|
242
|
+
)}
|
|
243
|
+
{activeTab === "pending" && (
|
|
244
|
+
<TaskList taskList={taskList} filterStatus="pending" />
|
|
245
|
+
)}
|
|
246
|
+
{activeTab === "completed" && (
|
|
247
|
+
<TaskList taskList={taskList} filterStatus="completed" />
|
|
248
|
+
)}
|
|
194
249
|
</div>
|
|
195
250
|
</div>
|
|
196
251
|
);
|
|
197
252
|
}
|
|
198
253
|
|
|
199
|
-
|
|
200
254
|
export default function TasksPage() {
|
|
201
255
|
const { siteConfig } = useDocusaurusContext();
|
|
202
256
|
const { customFields } = siteConfig || {};
|
|
@@ -204,12 +258,16 @@ export default function TasksPage() {
|
|
|
204
258
|
const tasksPage = customFields?.tasksPage;
|
|
205
259
|
const title = tasksPage.title;
|
|
206
260
|
const description = tasksPage.description;
|
|
207
|
-
const taskList =
|
|
261
|
+
const taskList =
|
|
262
|
+
tasksPage.enable && tasksPage.taskList ? tasksPage.taskList : [];
|
|
208
263
|
|
|
209
264
|
// If tasks are disabled, show a notice box instead
|
|
210
265
|
if (!tasksPage || !tasksPage.enable) {
|
|
211
266
|
return (
|
|
212
|
-
<Layout
|
|
267
|
+
<Layout
|
|
268
|
+
title="Tasks are Disabled"
|
|
269
|
+
description="Tasks are currently disabled"
|
|
270
|
+
>
|
|
213
271
|
<div className="tasks-container">
|
|
214
272
|
<div className="tasks-content">
|
|
215
273
|
<div className="tasks-disabled-notice">
|
|
@@ -218,7 +276,8 @@ export default function TasksPage() {
|
|
|
218
276
|
</div>
|
|
219
277
|
<h2 className="disabled-title">Tasks are currently disabled</h2>
|
|
220
278
|
<p className="disabled-help">
|
|
221
|
-
To enable tasks, set <code>tasks_page.enable</code> to
|
|
279
|
+
To enable tasks, set <code>tasks_page.enable</code> to{" "}
|
|
280
|
+
<code>true</code>
|
|
222
281
|
</p>
|
|
223
282
|
</div>
|
|
224
283
|
</div>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { useEffect, useRef } from
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
2
|
|
|
3
3
|
// AI Generated (partially)
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* <HashNavigation/> Component to handle hash-based navigation with visual effects
|
|
7
7
|
* Should be added at the bottom of the page
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* @param {Object} props Component props
|
|
10
10
|
* @param {string} props.elementPrefix Prefix for element IDs (default: 'card-')
|
|
11
11
|
* @param {string} props.elementSelector Selector for all elements in the group (default: '.content-card')
|
|
@@ -21,42 +21,41 @@ import { useEffect, useRef } from 'react';
|
|
|
21
21
|
* @param {string} props.styles.blurAmount Blur amount for non-highlighted elements (default: '4px')
|
|
22
22
|
* @param {string} props.styles.blurOpacity Opacity for blurred elements (default: '0.3')
|
|
23
23
|
*/
|
|
24
|
-
export default function HashNavigation({
|
|
25
|
-
elementPrefix =
|
|
26
|
-
elementSelector =
|
|
27
|
-
containerSelector =
|
|
24
|
+
export default function HashNavigation({
|
|
25
|
+
elementPrefix = "card-",
|
|
26
|
+
elementSelector = ".content-card",
|
|
27
|
+
containerSelector = ".container",
|
|
28
28
|
effectDuration = 6000,
|
|
29
29
|
scrollDelay = 300,
|
|
30
|
-
scrollOptions = { behavior:
|
|
30
|
+
scrollOptions = { behavior: "smooth", block: "center" },
|
|
31
31
|
enabled = true,
|
|
32
|
-
styles = {}
|
|
32
|
+
styles = {},
|
|
33
33
|
}) {
|
|
34
|
-
const styleId =
|
|
35
|
-
const highlightClass =
|
|
36
|
-
const blurClass =
|
|
37
|
-
const containerActiveClass =
|
|
38
|
-
|
|
34
|
+
const styleId = "hash-navigation-styles";
|
|
35
|
+
const highlightClass = "hash-nav-highlight";
|
|
36
|
+
const blurClass = "hash-nav-blur";
|
|
37
|
+
const containerActiveClass = "hash-nav-active";
|
|
38
|
+
|
|
39
39
|
// Default styles
|
|
40
40
|
const {
|
|
41
|
-
overlayColor =
|
|
42
|
-
highlightShadow =
|
|
43
|
-
highlightScale =
|
|
44
|
-
blurAmount =
|
|
45
|
-
blurOpacity =
|
|
41
|
+
overlayColor = "rgba(var(--ifm-color-emphasis-200-rgb), 0.5)",
|
|
42
|
+
highlightShadow = "0 0 30px 10px var(--ifm-color-primary)",
|
|
43
|
+
highlightScale = "1.05",
|
|
44
|
+
blurAmount = "4px",
|
|
45
|
+
blurOpacity = "0.3",
|
|
46
46
|
} = styles;
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
// Reference to track if styles have been injected
|
|
49
49
|
const stylesInjected = useRef(false);
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
// Inject the component styles
|
|
52
52
|
useEffect(() => {
|
|
53
|
-
|
|
54
53
|
// Don't inject styles if already present
|
|
55
54
|
if (document.getElementById(styleId) || stylesInjected.current) {
|
|
56
55
|
return;
|
|
57
56
|
}
|
|
58
|
-
|
|
59
|
-
const styleElement = document.createElement(
|
|
57
|
+
|
|
58
|
+
const styleElement = document.createElement("style");
|
|
60
59
|
|
|
61
60
|
styleElement.id = styleId;
|
|
62
61
|
|
|
@@ -122,10 +121,10 @@ export default function HashNavigation({
|
|
|
122
121
|
}
|
|
123
122
|
}
|
|
124
123
|
`;
|
|
125
|
-
|
|
124
|
+
|
|
126
125
|
document.head.appendChild(styleElement);
|
|
127
126
|
stylesInjected.current = true;
|
|
128
|
-
|
|
127
|
+
|
|
129
128
|
// Clean up on unmount
|
|
130
129
|
return () => {
|
|
131
130
|
const existingStyle = document.getElementById(styleId);
|
|
@@ -134,8 +133,14 @@ export default function HashNavigation({
|
|
|
134
133
|
}
|
|
135
134
|
stylesInjected.current = false;
|
|
136
135
|
};
|
|
137
|
-
}, [
|
|
138
|
-
|
|
136
|
+
}, [
|
|
137
|
+
containerSelector,
|
|
138
|
+
overlayColor,
|
|
139
|
+
highlightShadow,
|
|
140
|
+
highlightScale,
|
|
141
|
+
blurAmount,
|
|
142
|
+
blurOpacity,
|
|
143
|
+
]);
|
|
139
144
|
|
|
140
145
|
// Main hash navigation logic
|
|
141
146
|
useEffect(() => {
|
|
@@ -143,16 +148,16 @@ export default function HashNavigation({
|
|
|
143
148
|
|
|
144
149
|
if (window.location.hash) {
|
|
145
150
|
const hashValue = window.location.hash.substring(1);
|
|
146
|
-
const targetElement = document.getElementById(
|
|
151
|
+
const targetElement = document.getElementById(
|
|
152
|
+
`${elementPrefix}${hashValue}`,
|
|
153
|
+
);
|
|
147
154
|
|
|
148
155
|
if (targetElement) {
|
|
149
|
-
|
|
150
156
|
// Wait a moment for the page to fully render
|
|
151
157
|
setTimeout(() => {
|
|
152
|
-
|
|
153
158
|
// Scroll to the element
|
|
154
159
|
targetElement.scrollIntoView(scrollOptions);
|
|
155
|
-
|
|
160
|
+
|
|
156
161
|
// Get the container to add the overlay effect
|
|
157
162
|
const container = document.querySelector(containerSelector);
|
|
158
163
|
if (container) {
|
|
@@ -166,15 +171,15 @@ export default function HashNavigation({
|
|
|
166
171
|
targetElement.classList.add(highlightClass);
|
|
167
172
|
|
|
168
173
|
// Add blur to all other elements
|
|
169
|
-
allElements.forEach(element => {
|
|
174
|
+
allElements.forEach((element) => {
|
|
170
175
|
if (element !== targetElement) {
|
|
171
176
|
element.classList.add(blurClass);
|
|
172
177
|
}
|
|
173
178
|
});
|
|
174
179
|
|
|
175
180
|
// Create clickable overlay for dismissing effects
|
|
176
|
-
const overlay = document.createElement(
|
|
177
|
-
overlay.className =
|
|
181
|
+
const overlay = document.createElement("div");
|
|
182
|
+
overlay.className = "hash-nav-overlay";
|
|
178
183
|
|
|
179
184
|
document.body.appendChild(overlay);
|
|
180
185
|
|
|
@@ -182,50 +187,48 @@ export default function HashNavigation({
|
|
|
182
187
|
|
|
183
188
|
// remove effects
|
|
184
189
|
const removeEffects = () => {
|
|
185
|
-
|
|
186
190
|
if (effectTimeoutId) {
|
|
187
191
|
clearTimeout(effectTimeoutId);
|
|
188
192
|
effectTimeoutId = null;
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
// Remove effects
|
|
192
|
-
allElements.forEach(element => {
|
|
196
|
+
allElements.forEach((element) => {
|
|
193
197
|
element.classList.remove(blurClass);
|
|
194
198
|
});
|
|
195
199
|
|
|
196
200
|
targetElement.classList.remove(highlightClass);
|
|
197
|
-
|
|
201
|
+
|
|
198
202
|
// Remove the container overlay
|
|
199
203
|
if (container) {
|
|
200
204
|
container.classList.remove(containerActiveClass);
|
|
201
205
|
}
|
|
202
|
-
|
|
206
|
+
|
|
203
207
|
// Remove clickable overlay
|
|
204
208
|
if (overlay && overlay.parentNode) {
|
|
205
209
|
overlay.parentNode.removeChild(overlay);
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
// Remove event listeners after effects are cleared
|
|
209
|
-
overlay.removeEventListener(
|
|
210
|
-
overlay.removeEventListener(
|
|
211
|
-
document.removeEventListener(
|
|
213
|
+
overlay.removeEventListener("click", removeEffects);
|
|
214
|
+
overlay.removeEventListener("touchstart", removeEffects);
|
|
215
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
212
216
|
};
|
|
213
|
-
|
|
217
|
+
|
|
214
218
|
// Add keyboard escape handler
|
|
215
219
|
const handleKeyDown = (e) => {
|
|
216
|
-
if (e.key ===
|
|
220
|
+
if (e.key === "Escape") {
|
|
217
221
|
removeEffects();
|
|
218
222
|
}
|
|
219
223
|
};
|
|
220
224
|
|
|
221
225
|
// Add event listeners to dismiss effects
|
|
222
|
-
overlay.addEventListener(
|
|
223
|
-
overlay.addEventListener(
|
|
224
|
-
document.addEventListener(
|
|
226
|
+
overlay.addEventListener("click", removeEffects);
|
|
227
|
+
overlay.addEventListener("touchstart", removeEffects);
|
|
228
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
225
229
|
|
|
226
230
|
// Set timeout to automatically remove effects after duration
|
|
227
231
|
effectTimeoutId = setTimeout(removeEffects, effectDuration);
|
|
228
|
-
|
|
229
232
|
}, scrollDelay);
|
|
230
233
|
}
|
|
231
234
|
}
|
|
@@ -233,7 +236,7 @@ export default function HashNavigation({
|
|
|
233
236
|
// Cleanup
|
|
234
237
|
return () => {
|
|
235
238
|
const container = document.querySelector(containerSelector);
|
|
236
|
-
const overlay = document.querySelector(
|
|
239
|
+
const overlay = document.querySelector(".hash-nav-overlay");
|
|
237
240
|
|
|
238
241
|
if (container) {
|
|
239
242
|
container.classList.remove(containerActiveClass);
|
|
@@ -244,7 +247,15 @@ export default function HashNavigation({
|
|
|
244
247
|
overlay.parentNode.removeChild(overlay);
|
|
245
248
|
}
|
|
246
249
|
};
|
|
247
|
-
}, [
|
|
250
|
+
}, [
|
|
251
|
+
enabled,
|
|
252
|
+
elementPrefix,
|
|
253
|
+
elementSelector,
|
|
254
|
+
containerSelector,
|
|
255
|
+
effectDuration,
|
|
256
|
+
scrollDelay,
|
|
257
|
+
scrollOptions,
|
|
258
|
+
]);
|
|
248
259
|
|
|
249
260
|
return null;
|
|
250
261
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useEffect, useState } from
|
|
2
|
-
import { useLocation } from
|
|
3
|
-
import useDocusaurusContext from
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useLocation } from "@docusaurus/router";
|
|
3
|
+
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
4
4
|
|
|
5
5
|
// AI Generated
|
|
6
6
|
|
|
@@ -13,11 +13,10 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
|
13
13
|
* @returns {null} This component doesn't render anything visible
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
sections = {},
|
|
16
|
+
export default function UpdateTitle({
|
|
17
|
+
sections = {},
|
|
19
18
|
defaultTitle = null,
|
|
20
|
-
enabled = true
|
|
19
|
+
enabled = true,
|
|
21
20
|
}) {
|
|
22
21
|
const location = useLocation();
|
|
23
22
|
const { siteConfig } = useDocusaurusContext();
|
|
@@ -27,44 +26,41 @@ export default function UpdateTitle({
|
|
|
27
26
|
const effectiveDefaultTitle = defaultTitle || siteConfig.title;
|
|
28
27
|
|
|
29
28
|
useEffect(() => {
|
|
30
|
-
|
|
31
29
|
// Only run if enabled
|
|
32
30
|
if (!enabled) return;
|
|
33
31
|
|
|
34
32
|
// Use provided sections or default to empty object
|
|
35
|
-
const sectionTitles = Object.keys(sections).length > 0
|
|
36
|
-
? sections
|
|
37
|
-
: {};
|
|
33
|
+
const sectionTitles = Object.keys(sections).length > 0 ? sections : {};
|
|
38
34
|
|
|
39
35
|
const updateTitle = () => {
|
|
40
36
|
// Get all sections we want to track
|
|
41
37
|
const sectionsToTrack = Object.keys(sectionTitles)
|
|
42
|
-
.map(id => document.getElementById(id))
|
|
38
|
+
.map((id) => document.getElementById(id))
|
|
43
39
|
.filter(Boolean);
|
|
44
|
-
|
|
40
|
+
|
|
45
41
|
if (sectionsToTrack.length === 0) {
|
|
46
42
|
// No sections found, use default title
|
|
47
43
|
setCurrentTitle(effectiveDefaultTitle);
|
|
48
44
|
return;
|
|
49
45
|
}
|
|
50
|
-
|
|
46
|
+
|
|
51
47
|
// Calculate which section is most visible
|
|
52
48
|
const viewportHeight = window.innerHeight;
|
|
53
49
|
let maxVisibleSection = null;
|
|
54
50
|
let maxVisibleArea = 0;
|
|
55
|
-
|
|
56
|
-
sectionsToTrack.forEach(section => {
|
|
51
|
+
|
|
52
|
+
sectionsToTrack.forEach((section) => {
|
|
57
53
|
const rect = section.getBoundingClientRect();
|
|
58
54
|
const visibleTop = Math.max(0, rect.top);
|
|
59
55
|
const visibleBottom = Math.min(viewportHeight, rect.bottom);
|
|
60
56
|
const visibleArea = Math.max(0, visibleBottom - visibleTop);
|
|
61
|
-
|
|
57
|
+
|
|
62
58
|
if (visibleArea > maxVisibleArea) {
|
|
63
59
|
maxVisibleArea = visibleArea;
|
|
64
60
|
maxVisibleSection = section.id;
|
|
65
61
|
}
|
|
66
62
|
});
|
|
67
|
-
|
|
63
|
+
|
|
68
64
|
// Update title state based on visible section
|
|
69
65
|
if (maxVisibleSection && sectionTitles[maxVisibleSection]) {
|
|
70
66
|
setCurrentTitle(sectionTitles[maxVisibleSection]);
|
|
@@ -72,7 +68,7 @@ export default function UpdateTitle({
|
|
|
72
68
|
setCurrentTitle(effectiveDefaultTitle);
|
|
73
69
|
}
|
|
74
70
|
};
|
|
75
|
-
|
|
71
|
+
|
|
76
72
|
// Add scroll event listener with throttling
|
|
77
73
|
let isScrolling = false;
|
|
78
74
|
const handleScroll = () => {
|
|
@@ -84,15 +80,15 @@ export default function UpdateTitle({
|
|
|
84
80
|
isScrolling = true;
|
|
85
81
|
}
|
|
86
82
|
};
|
|
87
|
-
|
|
88
|
-
window.addEventListener(
|
|
89
|
-
|
|
83
|
+
|
|
84
|
+
window.addEventListener("scroll", handleScroll);
|
|
85
|
+
|
|
90
86
|
// Initial call
|
|
91
87
|
updateTitle();
|
|
92
|
-
|
|
88
|
+
|
|
93
89
|
// Clean up
|
|
94
90
|
return () => {
|
|
95
|
-
window.removeEventListener(
|
|
91
|
+
window.removeEventListener("scroll", handleScroll);
|
|
96
92
|
};
|
|
97
93
|
}, [location.pathname, sections, effectiveDefaultTitle, enabled]);
|
|
98
94
|
|
|
@@ -104,4 +100,4 @@ export default function UpdateTitle({
|
|
|
104
100
|
|
|
105
101
|
// Component doesn't render anything visible
|
|
106
102
|
return null;
|
|
107
|
-
}
|
|
103
|
+
}
|
|
@@ -15,10 +15,12 @@ export function getCssVar(name) {
|
|
|
15
15
|
const cssContent = fs.readFileSync(cssPath, "utf8");
|
|
16
16
|
|
|
17
17
|
if (name === "--ifm-color-primary") {
|
|
18
|
-
const match = cssContent.match(
|
|
18
|
+
const match = cssContent.match(
|
|
19
|
+
/--ifm-color-primary:\s*(#[a-fA-F0-9]{3,6})/,
|
|
20
|
+
);
|
|
19
21
|
return match ? match[1] : "#2e8555";
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
|
|
22
24
|
if (name === "--ifm-background-color") {
|
|
23
25
|
return "#ffffff";
|
|
24
26
|
}
|
|
@@ -27,4 +29,4 @@ export function getCssVar(name) {
|
|
|
27
29
|
} catch {
|
|
28
30
|
return "#2e8555"; // Safe default
|
|
29
31
|
}
|
|
30
|
-
}
|
|
32
|
+
}
|