@portosaur/theme 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.
Files changed (49) hide show
  1. package/package.json +12 -3
  2. package/src/plugins/theme.mjs +2 -0
  3. package/theme/DocCategoryGeneratedIndexPage/index.jsx +4 -10
  4. package/theme/MDXComponents.jsx +3 -2
  5. package/theme/Root.jsx +1 -1
  6. package/theme/components/AboutSection/index.jsx +89 -249
  7. package/theme/components/ContactSection/index.jsx +72 -153
  8. package/theme/components/ExperienceSection/index.jsx +35 -106
  9. package/theme/components/HeroSection/index.jsx +64 -186
  10. package/theme/components/Indent/index.jsx +10 -0
  11. package/theme/components/NavArrow/index.jsx +38 -55
  12. package/theme/components/NoteCards/index.jsx +110 -0
  13. package/theme/components/Preview/components/FeedbackStates.jsx +45 -190
  14. package/theme/components/Preview/components/FileTabs.jsx +17 -24
  15. package/theme/components/Preview/components/PreviewContent.jsx +37 -62
  16. package/theme/components/Preview/components/PreviewHeader.jsx +146 -380
  17. package/theme/components/Preview/components/Triggers/Pv.jsx +50 -78
  18. package/theme/components/Preview/components/Triggers/SrcPv.jsx +16 -47
  19. package/theme/components/Preview/components/Triggers/index.jsx +2 -2
  20. package/theme/components/Preview/components/ViewerWindow.jsx +160 -268
  21. package/theme/components/Preview/index.jsx +3 -3
  22. package/theme/components/Preview/renderers/CodeRenderer.jsx +81 -109
  23. package/theme/components/Preview/renderers/ImageRenderer.jsx +30 -67
  24. package/theme/components/Preview/renderers/PdfRenderer.jsx +31 -52
  25. package/theme/components/Preview/renderers/WebRenderer.jsx +18 -32
  26. package/theme/components/Preview/state/index.jsx +46 -30
  27. package/theme/components/ProjectsSection/index.jsx +278 -573
  28. package/theme/components/ScrollToTop/index.jsx +93 -0
  29. package/theme/components/ScrollToTop/styles.module.css +97 -0
  30. package/theme/components/SocialLinks/index.jsx +43 -55
  31. package/theme/components/Tooltip/index.jsx +85 -43
  32. package/theme/components/Tooltip/styles.module.css +13 -3
  33. package/theme/components/TopicList/index.jsx +31 -0
  34. package/theme/css/animations.css +61 -0
  35. package/theme/css/custom.css +16 -253
  36. package/theme/css/overrides/base.css +37 -0
  37. package/theme/css/overrides/footer.css +14 -0
  38. package/theme/css/overrides/markdown.css +62 -0
  39. package/theme/css/overrides/navbar.css +37 -0
  40. package/theme/css/overrides/pagination.css +64 -0
  41. package/theme/css/overrides/search.css +28 -0
  42. package/theme/css/{catppuccin.css → overrides/variables.css} +1 -1
  43. package/theme/pages/index.jsx +25 -87
  44. package/theme/pages/notes.jsx +27 -104
  45. package/theme/pages/tasks.jsx +293 -904
  46. package/theme/components/NoteIndex/index.jsx +0 -182
  47. package/theme/css/bootstrap.css +0 -5
  48. /package/theme/components/{NoteIndex → NoteCards}/styles.module.css +0 -0
  49. /package/theme/css/{tasks.css → tasks.module.css} +0 -0
@@ -2,11 +2,13 @@ import React, { useState, useEffect, useRef } from "react";
2
2
  import { FaChevronDown, FaChevronUp } from "react-icons/fa";
3
3
  import Tooltip from "../Tooltip";
4
4
  import styles from "./styles.module.css";
5
+
5
6
  export default function NavArrow() {
6
7
  const [direction, setDirection] = useState("down");
7
8
  const [isVisible, setIsVisible] = useState(false);
8
9
  const [isScrolling, setIsScrolling] = useState(false);
9
10
  const scrollTimeoutRef = useRef(null);
11
+
10
12
  const getSections = () => {
11
13
  const main = document.querySelector("main");
12
14
  if (!main) return [];
@@ -14,29 +16,36 @@ export default function NavArrow() {
14
16
  .map((el) => el.id)
15
17
  .filter((id) => id !== "nav-arrow");
16
18
  };
19
+
17
20
  useEffect(() => {
18
21
  const handleScroll = () => {
19
22
  const scrollTop =
20
23
  window.pageYOffset || document.documentElement.scrollTop;
21
24
  const windowHeight = window.innerHeight;
22
25
  const fullHeight = document.documentElement.scrollHeight;
26
+
23
27
  setIsVisible(scrollTop > 100);
28
+
24
29
  if (scrollTop + windowHeight >= fullHeight - 50) {
25
30
  setDirection("up");
26
31
  } else {
27
32
  setDirection("down");
28
33
  }
34
+
29
35
  setIsScrolling(true);
30
36
  if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
31
37
  scrollTimeoutRef.current = setTimeout(() => setIsScrolling(false), 800);
32
38
  };
39
+
33
40
  window.addEventListener("scroll", handleScroll, { passive: true });
34
41
  handleScroll();
42
+
35
43
  return () => {
36
44
  window.removeEventListener("scroll", handleScroll);
37
45
  if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
38
46
  };
39
47
  }, []);
48
+
40
49
  const handleClick = () => {
41
50
  if (direction === "up") {
42
51
  window.scrollTo({ top: 0, behavior: "smooth" });
@@ -45,6 +54,7 @@ export default function NavArrow() {
45
54
  const windowTop =
46
55
  window.pageYOffset || document.documentElement.scrollTop;
47
56
  let nextSectionId = null;
57
+
48
58
  for (const id of sections) {
49
59
  const element = document.getElementById(id);
50
60
  if (element) {
@@ -56,9 +66,11 @@ export default function NavArrow() {
56
66
  }
57
67
  }
58
68
  }
69
+
59
70
  if (nextSectionId) {
60
- const element = document.getElementById(nextSectionId);
61
- element.scrollIntoView({ behavior: "smooth" });
71
+ document
72
+ .getElementById(nextSectionId)
73
+ .scrollIntoView({ behavior: "smooth" });
62
74
  } else {
63
75
  window.scrollTo({
64
76
  top: document.documentElement.scrollHeight,
@@ -67,58 +79,29 @@ export default function NavArrow() {
67
79
  }
68
80
  }
69
81
  };
70
- return jsxDEV_7x81h0kn(
71
- "button",
72
- {
73
- className: `${styles.navArrow} ${isVisible ? styles.visible : ""} ${isScrolling ? styles.scrolling : ""}`,
74
- onClick: handleClick,
75
- "aria-label":
76
- direction === "down" ? "Scroll to next section" : "Scroll to top",
77
- children: jsxDEV_7x81h0kn(
78
- Tooltip,
79
- {
80
- msg: direction === "down" ? "Next Section" : "Back to Top",
81
- position: "top",
82
- gap: 25,
83
- underline: false,
84
- children: jsxDEV_7x81h0kn(
85
- "div",
86
- {
87
- className: `${styles.iconWrapper} ${styles[direction]}`,
88
- children:
89
- direction === "down"
90
- ? jsxDEV_7x81h0kn(
91
- FaChevronDown,
92
- { className: styles.chevron },
93
- undefined,
94
- false,
95
- undefined,
96
- this,
97
- )
98
- : jsxDEV_7x81h0kn(
99
- FaChevronUp,
100
- { className: styles.chevron },
101
- undefined,
102
- false,
103
- undefined,
104
- this,
105
- ),
106
- },
107
- undefined,
108
- false,
109
- undefined,
110
- this,
111
- ),
112
- },
113
- undefined,
114
- false,
115
- undefined,
116
- this,
117
- ),
118
- },
119
- undefined,
120
- false,
121
- undefined,
122
- this,
82
+
83
+ return (
84
+ <button
85
+ className={`${styles.navArrow} ${isVisible ? styles.visible : ""} ${isScrolling ? styles.scrolling : ""}`}
86
+ onClick={handleClick}
87
+ aria-label={
88
+ direction === "down" ? "Scroll to next section" : "Scroll to top"
89
+ }
90
+ >
91
+ <Tooltip
92
+ msg={direction === "down" ? "Next Section" : "Back to Top"}
93
+ position="top"
94
+ gap={25}
95
+ underline={false}
96
+ >
97
+ <div className={`${styles.iconWrapper} ${styles[direction]}`}>
98
+ {direction === "down" ? (
99
+ <FaChevronDown className={styles.chevron} />
100
+ ) : (
101
+ <FaChevronUp className={styles.chevron} />
102
+ )}
103
+ </div>
104
+ </Tooltip>
105
+ </button>
123
106
  );
124
107
  }
@@ -0,0 +1,110 @@
1
+ import useBaseUrl from "@docusaurus/useBaseUrl";
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.jsx";
6
+ import { iconMap } from "../../config/iconMappings.jsx";
7
+ import styles from "./styles.module.css";
8
+
9
+ function useNotes() {
10
+ const context = require.context(`@site/notes`, true, /index\.mdx?$|\.mdx?$/);
11
+
12
+ return context
13
+ .keys()
14
+ .filter((path) => {
15
+ if (path === "./index.md" || path === "./index.mdx") {
16
+ return false;
17
+ }
18
+ const pathParts = path.split("/");
19
+ const isTopLevelFile =
20
+ pathParts.length === 2 && !path.match(/index\.mdx?$/);
21
+ const isTopLevelDir =
22
+ pathParts.length === 3 && path.match(/index\.mdx?$/);
23
+ return isTopLevelFile || isTopLevelDir;
24
+ })
25
+ .map((path) => {
26
+ const { frontMatter } = context(path);
27
+ const pathParts = path.split("/");
28
+ const isTopLevelFile = pathParts.length === 2;
29
+ const fileSlug = isTopLevelFile
30
+ ? path.replace("./", "").replace(/\.mdx?$/, "")
31
+ : pathParts[1];
32
+ const slug = frontMatter.slug || frontMatter.id || fileSlug;
33
+ const rawTitle = frontMatter.title || frontMatter.language || fileSlug;
34
+ const title = rawTitle.charAt(0).toUpperCase() + rawTitle.slice(1);
35
+ const language = frontMatter.language
36
+ ? frontMatter.language
37
+ .toLowerCase()
38
+ .replace(/ /g, "")
39
+ .replace(/[\s-]/g, "")
40
+ : slug.toLowerCase() || title.toLowerCase();
41
+
42
+ return {
43
+ title,
44
+ language,
45
+ slug,
46
+ desc: frontMatter.desc || "",
47
+ position: frontMatter.sidebar_position || 999,
48
+ };
49
+ })
50
+ .sort((a, b) => a.position - b.position);
51
+ }
52
+
53
+ function NoteCard({ title, language, slug, desc, index, docsBasePath }) {
54
+ const noteUrl = useBaseUrl(`${docsBasePath}/${slug}`);
55
+ const { icon: Icon = FaBook, color = "var(--ifm-color-primary)" } =
56
+ iconMap[language] || iconMap[title.toLowerCase()] || {};
57
+ const tooltipContent = desc ? desc : null;
58
+
59
+ const cardInner = (
60
+ <Link
61
+ to={noteUrl}
62
+ className={styles.noteCard}
63
+ style={{ "--card-index": index, "--note-color": color }}
64
+ aria-label={`Read note: ${title}`}
65
+ >
66
+ <div className={styles.iconWrapper}>
67
+ <Icon className={styles.noteIcon} />
68
+ </div>
69
+ <div className={styles.cardContent}>
70
+ <h3 className={styles.noteTitle}>{title}</h3>
71
+ </div>
72
+ <FaChevronRight className={styles.mobileChevron} />
73
+ </Link>
74
+ );
75
+
76
+ return tooltipContent ? (
77
+ <Tooltip msg={tooltipContent} position="top" underline={false} gap={-8}>
78
+ {cardInner}
79
+ </Tooltip>
80
+ ) : (
81
+ cardInner
82
+ );
83
+ }
84
+
85
+ export default function NoteCards() {
86
+ const notes = useNotes();
87
+ const { path: docsBasePath } = usePluginData(
88
+ "docusaurus-plugin-content-docs",
89
+ );
90
+
91
+ if (!notes.length) {
92
+ return null;
93
+ }
94
+
95
+ return (
96
+ <div className={styles.notesGrid} role="list">
97
+ {notes.map((note, index) => (
98
+ <NoteCard
99
+ key={note.slug}
100
+ title={note.title}
101
+ language={note.language}
102
+ slug={note.slug}
103
+ desc={note.desc}
104
+ index={index}
105
+ docsBasePath={docsBasePath}
106
+ />
107
+ ))}
108
+ </div>
109
+ );
110
+ }
@@ -1,200 +1,55 @@
1
1
  import styles from "../styles.module.css";
2
+
2
3
  export function LoadingState() {
3
- return jsxDEV_7x81h0kn(
4
- "div",
5
- {
6
- className: styles.loading,
7
- children: [
8
- jsxDEV_7x81h0kn(
9
- "div",
10
- {
11
- className: styles.loadingIcon,
12
- children: jsxDEV_7x81h0kn(
13
- "div",
14
- { className: styles.spinner },
15
- undefined,
16
- false,
17
- undefined,
18
- this,
19
- ),
20
- },
21
- undefined,
22
- false,
23
- undefined,
24
- this,
25
- ),
26
- jsxDEV_7x81h0kn(
27
- "div",
28
- {
29
- className: styles.loadingText,
30
- children: [
31
- jsxDEV_7x81h0kn(
32
- "p",
33
- { children: "Preparing preview..." },
34
- undefined,
35
- false,
36
- undefined,
37
- this,
38
- ),
39
- jsxDEV_7x81h0kn(
40
- "span",
41
- { children: "Fetching content from source" },
42
- undefined,
43
- false,
44
- undefined,
45
- this,
46
- ),
47
- ],
48
- },
49
- undefined,
50
- true,
51
- undefined,
52
- this,
53
- ),
54
- ],
55
- },
56
- undefined,
57
- true,
58
- undefined,
59
- this,
4
+ return (
5
+ <div className={styles.loading}>
6
+ <div className={styles.loadingIcon}>
7
+ <div className={styles.spinner} />
8
+ </div>
9
+ <div className={styles.loadingText}>
10
+ <p>Preparing preview...</p>
11
+ <span>Fetching content from source</span>
12
+ </div>
13
+ </div>
60
14
  );
61
15
  }
16
+
62
17
  export function ErrorState({ path, message, fileType, fileUrl, onRetry }) {
63
- return jsxDEV_7x81h0kn(
64
- "div",
65
- {
66
- className: styles.errorState,
67
- children: [
68
- jsxDEV_7x81h0kn(
69
- "div",
70
- { className: styles.errorIcon, children: "⚠️" },
71
- undefined,
72
- false,
73
- undefined,
74
- this,
75
- ),
76
- jsxDEV_7x81h0kn(
77
- "p",
78
- {
79
- children: [
80
- "Could not load: ",
81
- jsxDEV_7x81h0kn(
82
- "code",
83
- { children: path?.split("/").pop() },
84
- undefined,
85
- false,
86
- undefined,
87
- this,
88
- ),
89
- ],
90
- },
91
- undefined,
92
- true,
93
- undefined,
94
- this,
95
- ),
96
- jsxDEV_7x81h0kn(
97
- "p",
98
- { className: styles.errorMsg, children: message },
99
- undefined,
100
- false,
101
- undefined,
102
- this,
103
- ),
104
- jsxDEV_7x81h0kn(
105
- "div",
106
- {
107
- className: styles.errorActions,
108
- children: [
109
- jsxDEV_7x81h0kn(
110
- "button",
111
- {
112
- onClick: onRetry,
113
- className: styles.retryButton,
114
- children: "Retry",
115
- },
116
- undefined,
117
- false,
118
- undefined,
119
- this,
120
- ),
121
- fileType === "web" &&
122
- jsxDEV_7x81h0kn(
123
- "a",
124
- {
125
- href: fileUrl,
126
- target: "_blank",
127
- rel: "noopener",
128
- className: styles.visitButton,
129
- children: "Visit Website",
130
- },
131
- undefined,
132
- false,
133
- undefined,
134
- this,
135
- ),
136
- ],
137
- },
138
- undefined,
139
- true,
140
- undefined,
141
- this,
142
- ),
143
- ],
144
- },
145
- undefined,
146
- true,
147
- undefined,
148
- this,
18
+ return (
19
+ <div className={styles.errorState}>
20
+ <div className={styles.errorIcon}>⚠️</div>
21
+ <p>
22
+ Could not load: <code>{path?.split("/").pop()}</code>
23
+ </p>
24
+ <p className={styles.errorMsg}>{message}</p>
25
+ <div className={styles.errorActions}>
26
+ <button onClick={onRetry} className={styles.retryButton}>
27
+ Retry
28
+ </button>
29
+ {fileType === "web" && (
30
+ <a
31
+ href={fileUrl}
32
+ target="_blank"
33
+ rel="noopener"
34
+ className={styles.visitButton}
35
+ >
36
+ Visit Website
37
+ </a>
38
+ )}
39
+ </div>
40
+ </div>
149
41
  );
150
42
  }
43
+
151
44
  export function OfflineState({ onRetry }) {
152
- return jsxDEV_7x81h0kn(
153
- "div",
154
- {
155
- className: styles.errorState,
156
- children: [
157
- jsxDEV_7x81h0kn(
158
- "div",
159
- { className: styles.errorIcon, children: "\uD83D\uDCE1" },
160
- undefined,
161
- false,
162
- undefined,
163
- this,
164
- ),
165
- jsxDEV_7x81h0kn(
166
- "h3",
167
- { children: "No Connection" },
168
- undefined,
169
- false,
170
- undefined,
171
- this,
172
- ),
173
- jsxDEV_7x81h0kn(
174
- "p",
175
- { children: "This resource requires an active internet connection." },
176
- undefined,
177
- false,
178
- undefined,
179
- this,
180
- ),
181
- jsxDEV_7x81h0kn(
182
- "button",
183
- {
184
- onClick: onRetry,
185
- className: styles.retryButton,
186
- children: "Try Again",
187
- },
188
- undefined,
189
- false,
190
- undefined,
191
- this,
192
- ),
193
- ],
194
- },
195
- undefined,
196
- true,
197
- undefined,
198
- this,
45
+ return (
46
+ <div className={styles.errorState}>
47
+ <div className={styles.errorIcon}>📡</div>
48
+ <h3>No Connection</h3>
49
+ <p>This resource requires an active internet connection.</p>
50
+ <button onClick={onRetry} className={styles.retryButton}>
51
+ Try Again
52
+ </button>
53
+ </div>
199
54
  );
200
55
  }
@@ -1,7 +1,9 @@
1
1
  import React, { useRef, useEffect } from "react";
2
2
  import styles from "../styles.module.css";
3
+
3
4
  export default function FileTabs({ sources, activeIndex, onSelect }) {
4
5
  const tabRefs = useRef([]);
6
+
5
7
  useEffect(() => {
6
8
  const el = tabRefs.current[activeIndex];
7
9
  if (el) {
@@ -12,30 +14,21 @@ export default function FileTabs({ sources, activeIndex, onSelect }) {
12
14
  });
13
15
  }
14
16
  }, [activeIndex]);
17
+
15
18
  if (!sources || sources.length <= 1) return null;
16
- return jsxDEV_7x81h0kn(
17
- "div",
18
- {
19
- className: styles.tabs,
20
- children: sources.map((src, i) =>
21
- jsxDEV_7x81h0kn(
22
- "button",
23
- {
24
- ref: (el) => (tabRefs.current[i] = el),
25
- className: `${styles.tab} ${i === activeIndex ? styles.activeTab : ""}`,
26
- onClick: () => onSelect(i),
27
- children: src.label || src.path.split("/").pop(),
28
- },
29
- i,
30
- false,
31
- undefined,
32
- this,
33
- ),
34
- ),
35
- },
36
- undefined,
37
- false,
38
- undefined,
39
- this,
19
+
20
+ return (
21
+ <div className={styles.tabs}>
22
+ {sources.map((src, i) => (
23
+ <button
24
+ key={i}
25
+ ref={(el) => (tabRefs.current[i] = el)}
26
+ className={`${styles.tab} ${i === activeIndex ? styles.activeTab : ""}`}
27
+ onClick={() => onSelect(i)}
28
+ >
29
+ {src.label || src.path.split("/").pop()}
30
+ </button>
31
+ ))}
32
+ </div>
40
33
  );
41
34
  }
@@ -5,6 +5,7 @@ import ImageRenderer from "../renderers/ImageRenderer";
5
5
  import PdfRenderer from "../renderers/PdfRenderer";
6
6
  import WebRenderer from "../renderers/WebRenderer";
7
7
  import CodeRenderer from "../renderers/CodeRenderer";
8
+
8
9
  export default function PreviewContent({
9
10
  currentFile,
10
11
  fileType,
@@ -20,84 +21,58 @@ export default function PreviewContent({
20
21
  }) {
21
22
  const path = currentFile?.path;
22
23
  const isExternal = path?.startsWith("http") || path?.startsWith("//");
24
+
23
25
  if (!isOnline && isExternal) {
24
- return jsxDEV_7x81h0kn(
25
- OfflineState,
26
- { onRetry: retryFetch },
27
- undefined,
28
- false,
29
- undefined,
30
- this,
31
- );
26
+ return <OfflineState onRetry={retryFetch} />;
32
27
  }
28
+
33
29
  const errorMsg = fetchErrors?.[path];
34
30
  if (errorMsg) {
35
- return jsxDEV_7x81h0kn(
36
- ErrorState,
37
- { path, message: errorMsg, fileType, fileUrl, onRetry: retryFetch },
38
- undefined,
39
- false,
40
- undefined,
41
- this,
31
+ return (
32
+ <ErrorState
33
+ path={path}
34
+ message={errorMsg}
35
+ fileType={fileType}
36
+ fileUrl={fileUrl}
37
+ onRetry={retryFetch}
38
+ />
42
39
  );
43
40
  }
41
+
44
42
  if (textLoading && fileType === "text") {
45
- return jsxDEV_7x81h0kn(LoadingState, {}, undefined, false, undefined, this);
43
+ return <LoadingState />;
46
44
  }
45
+
47
46
  switch (fileType) {
48
47
  case "image":
49
- return jsxDEV_7x81h0kn(
50
- ImageRenderer,
51
- {
52
- fileUrl,
53
- label: currentFile.label,
54
- zoomLevel,
55
- onError: (msg) => setError(path, msg),
56
- },
57
- fileUrl,
58
- false,
59
- undefined,
60
- this,
48
+ return (
49
+ <ImageRenderer
50
+ fileUrl={fileUrl}
51
+ label={currentFile.label}
52
+ zoomLevel={zoomLevel}
53
+ onError={(msg) => setError(path, msg)}
54
+ />
61
55
  );
62
56
  case "pdf":
63
- return jsxDEV_7x81h0kn(
64
- PdfRenderer,
65
- { fileUrl, zoomLevel, onError: (msg) => setError(path, msg) },
66
- fileUrl,
67
- false,
68
- undefined,
69
- this,
57
+ return (
58
+ <PdfRenderer
59
+ fileUrl={fileUrl}
60
+ zoomLevel={zoomLevel}
61
+ onError={(msg) => setError(path, msg)}
62
+ />
70
63
  );
71
64
  case "web":
72
- return jsxDEV_7x81h0kn(
73
- WebRenderer,
74
- {
75
- fileUrl,
76
- label: currentFile.label,
77
- onError: (msg) => setError(path, msg),
78
- },
79
- fileUrl,
80
- false,
81
- undefined,
82
- this,
65
+ return (
66
+ <WebRenderer
67
+ fileUrl={fileUrl}
68
+ label={currentFile.label}
69
+ onError={(msg) => setError(path, msg)}
70
+ />
83
71
  );
84
72
  default: {
85
- if (!textContent)
86
- return jsxDEV_7x81h0kn(
87
- LoadingState,
88
- {},
89
- undefined,
90
- false,
91
- undefined,
92
- this,
93
- );
94
- return jsxDEV_7x81h0kn(
95
- CodeRenderer,
96
- { code: textContent, language: ext, zoomLevel },
97
- undefined,
98
- false,
99
- undefined,
100
- this,
73
+ if (!textContent) return <LoadingState />;
74
+ return (
75
+ <CodeRenderer code={textContent} language={ext} zoomLevel={zoomLevel} />
101
76
  );
102
77
  }
103
78
  }