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.
- package/README.md +26 -126
- package/bin/portosaurus.mjs +8 -0
- package/package.json +6 -3
- package/src/assets/img/icon.png +0 -0
- package/src/assets/img/{icon.svg → svg/icon.svg} +35 -37
- package/src/assets/img/svg/project-blank.svg +140 -0
- package/src/assets/sample-resume.pdf +0 -0
- package/src/cli/build.mjs +2 -5
- package/src/cli/dev.mjs +27 -5
- package/src/cli/init.mjs +6 -12
- package/src/cli/schema.mjs +211 -0
- package/src/core/buildDocuConfig.mjs +305 -188
- package/src/core/constants.mjs +7 -1
- package/src/template/config.yml +150 -0
- package/src/template/notes/welcome.mdx +6 -0
- package/src/template/package.json +3 -3
- package/src/theme/MDXComponents.js +0 -1
- package/src/theme/components/AboutSection/index.js +32 -17
- package/src/theme/components/AboutSection/styles.module.css +151 -344
- package/src/theme/components/ContactSection/index.js +23 -14
- package/src/theme/components/ContactSection/styles.module.css +19 -8
- package/src/theme/components/ExperienceSection/index.js +12 -5
- package/src/theme/components/HeroSection/index.js +4 -3
- package/src/theme/components/HeroSection/styles.module.css +17 -16
- package/src/theme/components/NavArrow/index.js +114 -0
- package/src/theme/components/NavArrow/styles.module.css +107 -0
- package/src/theme/components/NoteIndex/index.js +66 -95
- package/src/theme/components/NoteIndex/styles.module.css +85 -89
- package/src/theme/components/Preview/components/FeedbackStates.js +3 -1
- package/src/theme/components/Preview/components/PreviewContent.js +91 -0
- package/src/theme/components/Preview/components/PreviewHeader.js +41 -33
- package/src/theme/components/Preview/components/Triggers/Pv.js +129 -72
- package/src/theme/components/Preview/components/ViewerWindow.js +198 -234
- package/src/theme/components/Preview/hooks/useAdaptiveSizing.js +115 -0
- package/src/theme/components/Preview/hooks/useDeepLinkHash.js +18 -23
- package/src/theme/components/Preview/hooks/useDockLayout.js +48 -8
- package/src/theme/components/Preview/hooks/useTouchZoom.js +118 -0
- package/src/theme/components/Preview/renderers/CodeRenderer.js +64 -25
- package/src/theme/components/Preview/state/index.js +70 -17
- package/src/theme/components/Preview/styles.module.css +181 -45
- package/src/theme/components/Preview/utils/index.js +11 -10
- package/src/theme/components/ProjectsSection/index.js +138 -148
- package/src/theme/components/ProjectsSection/styles.module.css +178 -112
- package/src/theme/components/SocialLinks/index.js +9 -7
- package/src/theme/components/Tooltip/index.js +31 -20
- package/src/theme/components/Tooltip/styles.module.css +101 -38
- package/src/theme/config/iconMappings.js +2 -0
- package/src/theme/css/custom.css +72 -0
- package/src/theme/hooks/useScrollReveal.js +30 -0
- package/src/theme/pages/index.js +7 -27
- package/src/theme/pages/notes.js +2 -2
- package/src/theme/pages/tasks.js +12 -11
- package/src/utils/cliUtils.mjs +23 -51
- package/src/utils/configUtils.mjs +95 -84
- package/src/utils/systemUtils.mjs +171 -0
- package/src/template/config.js +0 -68
- package/src/theme/components/ScrollToTop/index.js +0 -95
- package/src/theme/components/ScrollToTop/styles.module.css +0 -97
- package/src/theme/config/metaTags.js +0 -21
- /package/src/template/{.nojekyll → static/.nojekyll} +0 -0
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import { useRef, useState, useEffect } from "react";
|
|
2
|
-
import Link from "@docusaurus/Link";
|
|
3
1
|
import useBaseUrl from "@docusaurus/useBaseUrl";
|
|
4
2
|
import { usePluginData } from "@docusaurus/useGlobalData";
|
|
3
|
+
import Link from "@docusaurus/Link";
|
|
4
|
+
import { FaBook, FaChevronRight } from "react-icons/fa";
|
|
5
|
+
import Tooltip from "../Tooltip/index.js";
|
|
5
6
|
import { iconMap } from "../../config/iconMappings.js";
|
|
6
7
|
import DocCardList from "@theme/DocCardList";
|
|
7
|
-
import Tooltip from "../Tooltip/index.js";
|
|
8
|
-
|
|
9
|
-
import { FaBook } from "react-icons/fa";
|
|
10
8
|
import styles from "./styles.module.css";
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Extracts and prepares notes from the filesystem
|
|
12
|
+
*/
|
|
13
|
+
function useNotes() {
|
|
13
14
|
const context = require.context(`@site/notes`, true, /index\.mdx?$|\.mdx?$/);
|
|
14
15
|
|
|
15
16
|
return context
|
|
16
17
|
.keys()
|
|
17
18
|
.filter((path) => {
|
|
18
19
|
// Skip root index file
|
|
19
|
-
if (path === "./index.md" || path === "./index.mdx")
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
20
|
+
if (path === "./index.md" || path === "./index.mdx") return false;
|
|
22
21
|
|
|
23
22
|
const pathParts = path.split("/");
|
|
24
23
|
const isTopLevelFile =
|
|
@@ -26,136 +25,111 @@ function indexNotes() {
|
|
|
26
25
|
const isTopLevelDir =
|
|
27
26
|
pathParts.length === 3 && path.match(/index\.mdx?$/);
|
|
28
27
|
|
|
29
|
-
// Keep only top-level files & dirs
|
|
30
28
|
return isTopLevelFile || isTopLevelDir;
|
|
31
29
|
})
|
|
32
30
|
.map((path) => {
|
|
31
|
+
const { frontMatter } = context(path);
|
|
33
32
|
const pathParts = path.split("/");
|
|
34
33
|
const isTopLevelFile = pathParts.length === 2;
|
|
35
34
|
|
|
36
|
-
// Extract
|
|
37
|
-
const
|
|
35
|
+
// Extract filename-based slug as fallback
|
|
36
|
+
const fileSlug = isTopLevelFile
|
|
38
37
|
? path.replace("./", "").replace(/\.mdx?$/, "")
|
|
39
38
|
: pathParts[1];
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
// Routing logic: slug > id > filename
|
|
41
|
+
const slug = frontMatter.slug || frontMatter.id || fileSlug;
|
|
42
|
+
|
|
43
|
+
// Display logic: title > language > filename
|
|
44
|
+
const rawTitle = frontMatter.title || frontMatter.language || fileSlug;
|
|
45
|
+
const title = rawTitle.charAt(0).toUpperCase() + rawTitle.slice(1);
|
|
42
46
|
|
|
43
|
-
const title =
|
|
44
|
-
frontMatter.title || slug.charAt(0).toUpperCase() + slug.slice(1);
|
|
45
47
|
const language = frontMatter.language
|
|
46
48
|
? frontMatter.language
|
|
47
49
|
.toLowerCase()
|
|
48
50
|
.replace(/ /g, "")
|
|
49
51
|
.replace(/[\s-]/g, "")
|
|
50
52
|
: slug.toLowerCase() || title.toLowerCase();
|
|
51
|
-
const position = frontMatter.sidebar_position || 999;
|
|
52
|
-
const description = frontMatter.description || "";
|
|
53
53
|
|
|
54
54
|
return {
|
|
55
55
|
title,
|
|
56
56
|
language,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
slug,
|
|
58
|
+
desc: frontMatter.desc || "",
|
|
59
|
+
position: frontMatter.sidebar_position || 999,
|
|
60
60
|
};
|
|
61
61
|
})
|
|
62
62
|
.sort((a, b) => a.position - b.position);
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
docsBasePath,
|
|
71
|
-
buttonText,
|
|
72
|
-
}) {
|
|
73
|
-
const noteUrl = useBaseUrl(`${docsBasePath}/${link}`);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Individual Note Card Component
|
|
67
|
+
*/
|
|
68
|
+
function NoteCard({ title, language, slug, desc, index, docsBasePath }) {
|
|
69
|
+
const noteUrl = useBaseUrl(`${docsBasePath}/${slug}`);
|
|
74
70
|
const { icon: Icon = FaBook, color = "var(--ifm-color-primary)" } =
|
|
75
71
|
iconMap[language] || iconMap[title.toLowerCase()] || {};
|
|
76
72
|
|
|
77
|
-
const tooltipContent =
|
|
78
|
-
<span
|
|
79
|
-
style={{
|
|
80
|
-
textAlign: "center",
|
|
81
|
-
minWidth: "150px",
|
|
82
|
-
display: "flex",
|
|
83
|
-
flexDirection: "column",
|
|
84
|
-
gap: "6px",
|
|
85
|
-
}}
|
|
86
|
-
>
|
|
87
|
-
<strong style={{ fontSize: "0.95em" }}>{title}</strong>
|
|
88
|
-
<span
|
|
89
|
-
style={{
|
|
90
|
-
fontSize: "0.85em",
|
|
91
|
-
opacity: 0.85,
|
|
92
|
-
fontWeight: 400,
|
|
93
|
-
textAlign: "left",
|
|
94
|
-
}}
|
|
95
|
-
>
|
|
96
|
-
{description}
|
|
97
|
-
</span>
|
|
98
|
-
</span>
|
|
99
|
-
) : (
|
|
100
|
-
title
|
|
101
|
-
);
|
|
73
|
+
const tooltipContent = desc ? desc : null;
|
|
102
74
|
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
className={
|
|
107
|
-
style={{ "--card-index": index }}
|
|
108
|
-
|
|
109
|
-
id={`note-${link}`}
|
|
75
|
+
const cardInner = (
|
|
76
|
+
<Link
|
|
77
|
+
to={noteUrl}
|
|
78
|
+
className={styles.noteCard}
|
|
79
|
+
style={{ "--card-index": index, "--note-color": color }}
|
|
80
|
+
aria-label={`Read note: ${title}`}
|
|
110
81
|
>
|
|
111
|
-
<div className={styles.
|
|
112
|
-
<Icon />
|
|
82
|
+
<div className={styles.iconWrapper}>
|
|
83
|
+
<Icon className={styles.noteIcon} />
|
|
113
84
|
</div>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{buttonText}
|
|
130
|
-
</Link>
|
|
131
|
-
</div>
|
|
85
|
+
|
|
86
|
+
<div className={styles.cardContent}>
|
|
87
|
+
<h3 className={styles.noteTitle}>{title}</h3>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<FaChevronRight className={styles.mobileChevron} />
|
|
91
|
+
</Link>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return tooltipContent ? (
|
|
95
|
+
<Tooltip msg={tooltipContent} position="top" underline={false} gap={-8}>
|
|
96
|
+
{cardInner}
|
|
97
|
+
</Tooltip>
|
|
98
|
+
) : (
|
|
99
|
+
cardInner
|
|
132
100
|
);
|
|
133
101
|
}
|
|
134
102
|
|
|
135
|
-
|
|
136
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Main Notes Grid Component
|
|
105
|
+
*/
|
|
106
|
+
export default function NoteCards() {
|
|
107
|
+
const notes = useNotes();
|
|
137
108
|
const { path: docsBasePath } = usePluginData(
|
|
138
109
|
"docusaurus-plugin-content-docs",
|
|
139
110
|
);
|
|
140
111
|
|
|
112
|
+
if (!notes.length) return null;
|
|
113
|
+
|
|
141
114
|
return (
|
|
142
|
-
<div className={styles.notesGrid} role="list"
|
|
115
|
+
<div className={styles.notesGrid} role="list">
|
|
143
116
|
{notes.map((note, index) => (
|
|
144
117
|
<NoteCard
|
|
145
|
-
key={note.
|
|
118
|
+
key={note.slug}
|
|
146
119
|
{...note}
|
|
147
120
|
index={index}
|
|
148
121
|
docsBasePath={docsBasePath}
|
|
149
|
-
buttonText={buttonText}
|
|
150
122
|
/>
|
|
151
123
|
))}
|
|
152
124
|
</div>
|
|
153
125
|
);
|
|
154
126
|
}
|
|
155
127
|
|
|
156
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Topic List Helper (for inside notes)
|
|
130
|
+
*/
|
|
157
131
|
export function TopicList({
|
|
158
|
-
|
|
132
|
+
desc = "Click on the links below to explore the topics.",
|
|
159
133
|
style = {
|
|
160
134
|
marginTop: "-2.5rem",
|
|
161
135
|
marginBottom: "2.5rem",
|
|
@@ -163,12 +137,9 @@ export function TopicList({
|
|
|
163
137
|
},
|
|
164
138
|
}) {
|
|
165
139
|
return (
|
|
166
|
-
|
|
167
|
-
<p
|
|
168
|
-
style={style}
|
|
169
|
-
dangerouslySetInnerHTML={{ __html: description }} // Well we are giving it only HTML :)
|
|
170
|
-
/>
|
|
140
|
+
<div style={style}>
|
|
141
|
+
<p dangerouslySetInnerHTML={{ __html: desc }} />
|
|
171
142
|
<DocCardList />
|
|
172
|
-
|
|
143
|
+
</div>
|
|
173
144
|
);
|
|
174
145
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
@keyframes
|
|
1
|
+
@keyframes noteFadeIn {
|
|
2
2
|
from {
|
|
3
3
|
opacity: 0;
|
|
4
|
-
transform: translateY(
|
|
4
|
+
transform: translateY(15px);
|
|
5
5
|
}
|
|
6
6
|
to {
|
|
7
7
|
opacity: 1;
|
|
@@ -12,40 +12,47 @@
|
|
|
12
12
|
.notesGrid {
|
|
13
13
|
display: flex;
|
|
14
14
|
flex-wrap: wrap;
|
|
15
|
-
gap:
|
|
15
|
+
gap: 1rem;
|
|
16
16
|
justify-content: center;
|
|
17
17
|
margin: 2rem 0;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
.noteCard {
|
|
21
21
|
width: 200px;
|
|
22
|
-
|
|
23
|
-
text-align: center;
|
|
24
|
-
padding: 1.2rem;
|
|
22
|
+
min-height: 220px;
|
|
25
23
|
background-color: var(--ifm-card-background-color);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
z-index 0s;
|
|
31
|
-
animation: fadeIn 0.5s ease-out forwards;
|
|
32
|
-
animation-delay: calc(0.2s + (var(--card-index, 0) * 0.1s));
|
|
24
|
+
border-radius: var(--ifm-card-border-radius);
|
|
25
|
+
padding: 1.5rem 1rem;
|
|
26
|
+
text-decoration: none !important;
|
|
27
|
+
color: var(--ifm-font-color-base) !important;
|
|
33
28
|
display: flex;
|
|
34
29
|
flex-direction: column;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
35
32
|
position: relative;
|
|
36
|
-
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
transition: all 0.3s ease;
|
|
35
|
+
animation: noteFadeIn 0.5s ease-out forwards;
|
|
36
|
+
animation-delay: calc(0.1s + (var(--card-index, 0) * 0.05s));
|
|
37
|
+
opacity: 0;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
.noteCard:hover {
|
|
40
|
-
transform: translateY(-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
transform: translateY(-8px) scale(1.03);
|
|
42
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.iconWrapper {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
margin-bottom: 1.5rem;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
.noteIcon {
|
|
46
|
-
font-size:
|
|
47
|
-
|
|
48
|
-
transition: transform 0.
|
|
53
|
+
font-size: 3.5rem;
|
|
54
|
+
color: var(--note-color);
|
|
55
|
+
transition: transform 0.3s ease;
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
.noteCard:hover .noteIcon {
|
|
@@ -53,75 +60,62 @@
|
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
.noteTitle {
|
|
56
|
-
margin
|
|
57
|
-
|
|
58
|
-
font-
|
|
59
|
-
|
|
63
|
+
margin: 0;
|
|
64
|
+
font-size: 1.6rem;
|
|
65
|
+
font-weight: 700;
|
|
66
|
+
line-height: 1.3;
|
|
67
|
+
text-align: center;
|
|
60
68
|
display: -webkit-box;
|
|
61
|
-
line-clamp:
|
|
62
|
-
|
|
69
|
+
-webkit-line-clamp: 2;
|
|
70
|
+
line-clamp: 2;
|
|
63
71
|
-webkit-box-orient: vertical;
|
|
64
72
|
overflow: hidden;
|
|
65
73
|
text-overflow: ellipsis;
|
|
66
|
-
|
|
74
|
+
overflow-wrap: anywhere;
|
|
67
75
|
}
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@media (max-width: 768px) {
|
|
75
|
-
.noteCard {
|
|
76
|
-
width: 150px;
|
|
77
|
-
padding: 0.8rem;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.noteIcon {
|
|
81
|
-
font-size: 45px;
|
|
82
|
-
margin-bottom: -0.8rem;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.noteTitle {
|
|
86
|
-
font-size: 1.3rem;
|
|
87
|
-
margin-bottom: 1.5rem;
|
|
88
|
-
}
|
|
77
|
+
/* Mobile List Style */
|
|
78
|
+
.mobileChevron {
|
|
79
|
+
display: none;
|
|
89
80
|
}
|
|
90
81
|
|
|
91
82
|
@media (max-width: 480px) {
|
|
92
83
|
.notesGrid {
|
|
93
|
-
gap: 0.
|
|
94
|
-
margin: 0.
|
|
95
|
-
|
|
96
|
-
|
|
84
|
+
gap: 0.65rem;
|
|
85
|
+
margin: 0.2rem 0;
|
|
86
|
+
padding: 0 0.2rem;
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
align-items: stretch;
|
|
90
|
+
justify-content: flex-start;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Force the Tooltip container to be full-width */
|
|
94
|
+
.notesGrid > span {
|
|
95
|
+
width: 100%;
|
|
96
|
+
display: flex !important;
|
|
97
|
+
justify-content: flex-start;
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
.noteCard {
|
|
100
101
|
width: 100%;
|
|
101
|
-
|
|
102
|
-
padding: 1rem;
|
|
103
|
-
display: flex;
|
|
102
|
+
min-height: auto;
|
|
104
103
|
flex-direction: row;
|
|
105
104
|
align-items: center;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
padding: 0.95rem 1.2rem;
|
|
106
|
+
justify-content: flex-start;
|
|
107
|
+
background-color: var(--ifm-card-background-color);
|
|
108
|
+
border-radius: 12px;
|
|
109
109
|
box-shadow: var(--ifm-global-shadow-md);
|
|
110
|
-
|
|
110
|
+
gap: 1.2rem;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
.noteCard:hover {
|
|
114
114
|
transform: none;
|
|
115
|
-
scale: 1;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.noteCard:active {
|
|
119
|
-
transform: scale(0.98);
|
|
120
115
|
background-color: var(--ifm-hover-overlay);
|
|
121
116
|
}
|
|
122
117
|
|
|
123
|
-
.
|
|
124
|
-
font-size: 42px;
|
|
118
|
+
.iconWrapper {
|
|
125
119
|
margin: 0;
|
|
126
120
|
flex-shrink: 0;
|
|
127
121
|
display: flex;
|
|
@@ -129,43 +123,45 @@
|
|
|
129
123
|
justify-content: center;
|
|
130
124
|
}
|
|
131
125
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
margin: 0;
|
|
126
|
+
.noteIcon {
|
|
127
|
+
font-size: 2.8rem;
|
|
135
128
|
}
|
|
136
129
|
|
|
137
|
-
.
|
|
138
|
-
|
|
139
|
-
margin: 0;
|
|
130
|
+
.cardContent {
|
|
131
|
+
text-align: left;
|
|
140
132
|
flex: 1;
|
|
141
133
|
min-width: 0;
|
|
142
|
-
|
|
143
|
-
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.noteTitle {
|
|
137
|
+
font-size: 1.2rem;
|
|
138
|
+
font-weight: 600;
|
|
139
|
+
color: var(--ifm-font-color-base);
|
|
144
140
|
-webkit-line-clamp: 2;
|
|
145
141
|
line-clamp: 2;
|
|
146
|
-
-
|
|
147
|
-
|
|
148
|
-
text-overflow: ellipsis;
|
|
142
|
+
overflow-wrap: anywhere;
|
|
143
|
+
text-align: left;
|
|
149
144
|
}
|
|
150
145
|
|
|
151
|
-
.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
font-size:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
146
|
+
.mobileChevron {
|
|
147
|
+
display: block;
|
|
148
|
+
margin-left: auto;
|
|
149
|
+
font-size: 1.1rem;
|
|
150
|
+
opacity: 0.7;
|
|
151
|
+
color: var(--ifm-color-primary);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.noteCard:active {
|
|
155
|
+
transform: scale(0.98);
|
|
156
|
+
background-color: var(--ifm-hover-overlay);
|
|
158
157
|
}
|
|
159
158
|
}
|
|
160
159
|
|
|
160
|
+
/* Accessibility */
|
|
161
161
|
@media (prefers-reduced-motion: reduce) {
|
|
162
162
|
.noteCard {
|
|
163
|
-
animation: none
|
|
163
|
+
animation: none;
|
|
164
164
|
opacity: 1;
|
|
165
|
-
transition: none
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.noteIcon {
|
|
169
|
-
transition: none !important;
|
|
165
|
+
transition: none;
|
|
170
166
|
}
|
|
171
167
|
}
|
|
@@ -6,7 +6,9 @@ import styles from "../styles.module.css";
|
|
|
6
6
|
export function LoadingState() {
|
|
7
7
|
return (
|
|
8
8
|
<div className={styles.loading}>
|
|
9
|
-
<div className={styles.
|
|
9
|
+
<div className={styles.loadingIcon}>
|
|
10
|
+
<div className={styles.spinner} />
|
|
11
|
+
</div>
|
|
10
12
|
<div className={styles.loadingText}>
|
|
11
13
|
<p>Preparing preview...</p>
|
|
12
14
|
<span>Fetching content from source</span>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import styles from "../styles.module.css";
|
|
3
|
+
import { LoadingState, ErrorState, OfflineState } from "./FeedbackStates";
|
|
4
|
+
import ImageRenderer from "../renderers/ImageRenderer";
|
|
5
|
+
import PdfRenderer from "../renderers/PdfRenderer";
|
|
6
|
+
import WebRenderer from "../renderers/WebRenderer";
|
|
7
|
+
import CodeRenderer from "../renderers/CodeRenderer";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Component that routes to the appropriate renderer based on file type.
|
|
11
|
+
* Also handles shared loading, error, and offline states.
|
|
12
|
+
*/
|
|
13
|
+
export default function PreviewContent({
|
|
14
|
+
currentFile,
|
|
15
|
+
fileType,
|
|
16
|
+
fileUrl,
|
|
17
|
+
isOnline,
|
|
18
|
+
fetchErrors,
|
|
19
|
+
textLoading,
|
|
20
|
+
textContent,
|
|
21
|
+
zoomLevel,
|
|
22
|
+
ext,
|
|
23
|
+
retryFetch,
|
|
24
|
+
setError,
|
|
25
|
+
}) {
|
|
26
|
+
const path = currentFile?.path;
|
|
27
|
+
const isExternal = path?.startsWith("http") || path?.startsWith("//");
|
|
28
|
+
|
|
29
|
+
// 1. Offline guard for external resources
|
|
30
|
+
if (!isOnline && isExternal) {
|
|
31
|
+
return <OfflineState onRetry={retryFetch} />;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. Error state
|
|
35
|
+
const errorMsg = fetchErrors?.[path];
|
|
36
|
+
if (errorMsg) {
|
|
37
|
+
return (
|
|
38
|
+
<ErrorState
|
|
39
|
+
path={path}
|
|
40
|
+
message={errorMsg}
|
|
41
|
+
fileType={fileType}
|
|
42
|
+
fileUrl={fileUrl}
|
|
43
|
+
onRetry={retryFetch}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Loading state (for text files only — specialized renderers handle their own loading)
|
|
49
|
+
if (textLoading && fileType === "text") {
|
|
50
|
+
return <LoadingState />;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 4. Feature-specific renderers
|
|
54
|
+
switch (fileType) {
|
|
55
|
+
case "image":
|
|
56
|
+
return (
|
|
57
|
+
<ImageRenderer
|
|
58
|
+
key={fileUrl}
|
|
59
|
+
fileUrl={fileUrl}
|
|
60
|
+
label={currentFile.label}
|
|
61
|
+
zoomLevel={zoomLevel}
|
|
62
|
+
onError={(msg) => setError(path, msg)}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
case "pdf":
|
|
66
|
+
return (
|
|
67
|
+
<PdfRenderer
|
|
68
|
+
key={fileUrl}
|
|
69
|
+
fileUrl={fileUrl}
|
|
70
|
+
zoomLevel={zoomLevel}
|
|
71
|
+
onError={(msg) => setError(path, msg)}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
case "web":
|
|
75
|
+
return (
|
|
76
|
+
<WebRenderer
|
|
77
|
+
key={fileUrl}
|
|
78
|
+
fileUrl={fileUrl}
|
|
79
|
+
label={currentFile.label}
|
|
80
|
+
onError={(msg) => setError(path, msg)}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
default: {
|
|
84
|
+
// Text / code
|
|
85
|
+
if (!textContent) return <LoadingState />;
|
|
86
|
+
return (
|
|
87
|
+
<CodeRenderer code={textContent} language={ext} zoomLevel={zoomLevel} />
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|