veslx 0.1.3 → 0.1.4
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/dist/client/App.js +17 -0
- package/dist/client/App.js.map +1 -0
- package/dist/client/components/front-matter.js +33 -0
- package/dist/client/components/front-matter.js.map +1 -0
- package/dist/client/components/gallery/components/figure-caption.js +14 -0
- package/dist/client/components/gallery/components/figure-caption.js.map +1 -0
- package/dist/client/components/gallery/components/figure-header.js +13 -0
- package/dist/client/components/gallery/components/figure-header.js.map +1 -0
- package/dist/client/components/gallery/components/lightbox.js +96 -0
- package/dist/client/components/gallery/components/lightbox.js.map +1 -0
- package/dist/client/components/gallery/components/loading-image.js +46 -0
- package/dist/client/components/gallery/components/loading-image.js.map +1 -0
- package/dist/client/components/gallery/hooks/use-gallery-images.js +84 -0
- package/dist/client/components/gallery/hooks/use-gallery-images.js.map +1 -0
- package/dist/client/components/gallery/hooks/use-lightbox.js +37 -0
- package/dist/client/components/gallery/hooks/use-lightbox.js.map +1 -0
- package/dist/client/components/gallery/index.js +96 -0
- package/dist/client/components/gallery/index.js.map +1 -0
- package/dist/client/components/gallery/lib/render-math-in-text.js +42 -0
- package/dist/client/components/gallery/lib/render-math-in-text.js.map +1 -0
- package/dist/client/components/header.js +60 -0
- package/dist/client/components/header.js.map +1 -0
- package/dist/client/components/loading.js +15 -0
- package/dist/client/components/loading.js.map +1 -0
- package/dist/client/components/mdx-components.js +134 -0
- package/dist/client/components/mdx-components.js.map +1 -0
- package/dist/client/components/mode-toggle.js +39 -0
- package/dist/client/components/mode-toggle.js.map +1 -0
- package/dist/client/components/page-error.js +39 -0
- package/dist/client/components/page-error.js.map +1 -0
- package/dist/client/components/parameter-badge.js +48 -0
- package/dist/client/components/parameter-badge.js.map +1 -0
- package/dist/client/components/parameter-table.js +301 -0
- package/dist/client/components/parameter-table.js.map +1 -0
- package/dist/client/components/post-list.js +119 -0
- package/dist/client/components/post-list.js.map +1 -0
- package/dist/client/components/running-bar.js +15 -0
- package/dist/client/components/running-bar.js.map +1 -0
- package/dist/client/components/slides-renderer.js +75 -0
- package/dist/client/components/slides-renderer.js.map +1 -0
- package/dist/client/components/theme-provider.js +9 -0
- package/dist/client/components/theme-provider.js.map +1 -0
- package/dist/client/components/ui/button.js +49 -0
- package/dist/client/components/ui/button.js.map +1 -0
- package/dist/client/components/ui/carousel.js +195 -0
- package/dist/client/components/ui/carousel.js.map +1 -0
- package/dist/client/components/ui/dropdown-menu.js +132 -0
- package/dist/client/components/ui/dropdown-menu.js.map +1 -0
- package/dist/client/hooks/use-key-bindings.js +40 -0
- package/dist/client/hooks/use-key-bindings.js.map +1 -0
- package/dist/client/hooks/use-mdx-content.js +78 -0
- package/dist/client/hooks/use-mdx-content.js.map +1 -0
- package/dist/client/lib/constants.js +5 -0
- package/dist/client/lib/constants.js.map +1 -0
- package/dist/client/lib/format-date.js +8 -0
- package/dist/client/lib/format-date.js.map +1 -0
- package/dist/client/lib/parameter-utils.js +110 -0
- package/dist/client/lib/parameter-utils.js.map +1 -0
- package/dist/client/lib/utils.js +9 -0
- package/dist/client/lib/utils.js.map +1 -0
- package/dist/client/logo_dark.png +0 -0
- package/dist/client/logo_light.png +0 -0
- package/dist/client/main.js +9 -0
- package/dist/client/main.js.map +1 -0
- package/dist/client/pages/home.js +30 -0
- package/dist/client/pages/home.js.map +1 -0
- package/dist/client/pages/post.js +53 -0
- package/dist/client/pages/post.js.map +1 -0
- package/dist/client/pages/slides.js +137 -0
- package/dist/client/pages/slides.js.map +1 -0
- package/dist/client/plugin/src/client.js +185 -0
- package/dist/client/plugin/src/client.js.map +1 -0
- package/package.json +8 -4
- package/src/main.tsx +1 -1
- package/vite.config.ts +22 -3
- package/vite.lib.config.ts +47 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
3
|
+
import { ThemeProvider } from "./components/theme-provider.js";
|
|
4
|
+
import { Home } from "./pages/home.js";
|
|
5
|
+
import { Post } from "./pages/post.js";
|
|
6
|
+
import { SlidesPage } from "./pages/slides.js";
|
|
7
|
+
function App() {
|
|
8
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { attribute: "class", defaultTheme: "system", enableSystem: true, children: /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsxs(Routes, { children: [
|
|
9
|
+
/* @__PURE__ */ jsx(Route, { path: ":path/SLIDES.mdx", element: /* @__PURE__ */ jsx(SlidesPage, {}) }),
|
|
10
|
+
/* @__PURE__ */ jsx(Route, { path: ":path/README.mdx", element: /* @__PURE__ */ jsx(Post, {}) }),
|
|
11
|
+
/* @__PURE__ */ jsx(Route, { path: "/*", element: /* @__PURE__ */ jsx(Home, {}) })
|
|
12
|
+
] }) }) });
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
App as default
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=App.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.js","sources":["../../src/App.tsx"],"sourcesContent":["import { BrowserRouter, Routes, Route } from \"react-router-dom\"\nimport { ThemeProvider } from \"./components/theme-provider\"\nimport { Home } from \"./pages/home\"\nimport { Post } from \"./pages/post\"\nimport { SlidesPage } from \"./pages/slides\"\n\nfunction App() {\n return (\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n <BrowserRouter>\n <Routes>\n <Route path=\":path/SLIDES.mdx\" element={<SlidesPage />} />\n <Route path=\":path/README.mdx\" element={<Post />} />\n <Route path=\"/*\" element={<Home />} />\n </Routes>\n </BrowserRouter>\n </ThemeProvider>\n )\n}\n\nexport default App\n"],"names":[],"mappings":";;;;;;AAMA,SAAS,MAAM;AACb,SACE,oBAAC,eAAA,EAAc,WAAU,SAAQ,cAAa,UAAS,cAAY,MACjE,UAAA,oBAAC,eAAA,EACG,UAAA,qBAAC,QAAA,EACC,UAAA;AAAA,IAAA,oBAAC,SAAM,MAAK,oBAAmB,SAAS,oBAAC,cAAW,GAAI;AAAA,wBACvD,OAAA,EAAM,MAAK,oBAAmB,SAAS,oBAAC,QAAK,GAAI;AAAA,wBACjD,OAAA,EAAM,MAAK,MAAK,SAAS,oBAAC,QAAK,EAAA,CAAI;AAAA,EAAA,EAAA,CACtC,GACJ,GACF;AAEJ;"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { formatDate } from "../lib/format-date.js";
|
|
3
|
+
import { Presentation } from "lucide-react";
|
|
4
|
+
import { Link } from "react-router-dom";
|
|
5
|
+
function FrontMatter({
|
|
6
|
+
title,
|
|
7
|
+
date,
|
|
8
|
+
description,
|
|
9
|
+
slides
|
|
10
|
+
}) {
|
|
11
|
+
return /* @__PURE__ */ jsx("div", { children: title && /* @__PURE__ */ jsxs("header", { className: "not-prose flex flex-col gap-2 mb-8 pt-4", children: [
|
|
12
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3", children: title }),
|
|
13
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 text-muted-foreground", children: [
|
|
14
|
+
date && /* @__PURE__ */ jsx("time", { className: "font-mono text-xs bg-muted px-2 py-0.5 rounded", children: formatDate(new Date(date)) }),
|
|
15
|
+
slides && /* @__PURE__ */ jsxs(
|
|
16
|
+
Link,
|
|
17
|
+
{
|
|
18
|
+
to: `/${slides.path}`,
|
|
19
|
+
className: "font-mono text-xs px-2 py-0.5 rounded flex items-center gap-1",
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ jsx(Presentation, { className: "h-3.5 w-3.5" }),
|
|
22
|
+
/* @__PURE__ */ jsx("span", { children: "slides" })
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
] }),
|
|
27
|
+
description && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap text-sm items-center gap-3 text-muted-foreground", children: description })
|
|
28
|
+
] }) });
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
FrontMatter
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=front-matter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import { formatDate } from \"@/lib/format-date\"\nimport { Presentation } from \"lucide-react\"\nimport { FileEntry } from \"plugin/src/lib\"\nimport { Link } from \"react-router-dom\"\n\nexport function FrontMatter({\n title,\n date,\n description,\n slides,\n}: {\n title?: string\n date?: string\n description?: string\n slides?: FileEntry | null\n}){\n\n return (\n <div>\n {title && (\n <header className=\"not-prose flex flex-col gap-2 mb-8 pt-4\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3\">\n {title}\n </h1>\n\n {/* Meta line */}\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n {date && (\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(date as string))}\n </time>\n )}\n {slides && (\n <Link\n to={`/${slides.path}`}\n className=\"font-mono text-xs px-2 py-0.5 rounded flex items-center gap-1\"\n >\n <Presentation className=\"h-3.5 w-3.5\" />\n <span>slides</span>\n </Link>\n )}\n </div>\n\n {description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {description}\n </div>\n )}\n </header>\n )}\n </div>\n )\n}"],"names":[],"mappings":";;;;AAKO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKE;AAEA,6BACG,OAAA,EACE,UAAA,SACC,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,OACH;AAAA,IAGA,qBAAC,OAAA,EAAI,WAAU,2DACZ,UAAA;AAAA,MAAA,QACC,oBAAC,UAAK,WAAU,kDACb,qBAAW,IAAI,KAAK,IAAc,CAAC,EAAA,CACtC;AAAA,MAED,UACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI,IAAI,OAAO,IAAI;AAAA,UACnB,WAAU;AAAA,UAEV,UAAA;AAAA,YAAA,oBAAC,cAAA,EAAa,WAAU,cAAA,CAAc;AAAA,YACtC,oBAAC,UAAK,UAAA,SAAA,CAAM;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACd,GAEJ;AAAA,IAEC,eACC,oBAAC,OAAA,EAAI,WAAU,mEACZ,UAAA,YAAA,CACH;AAAA,EAAA,EAAA,CAEJ,EAAA,CAEJ;AAEJ;"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { renderMathInText } from "../lib/render-math-in-text.js";
|
|
3
|
+
function FigureCaption({ caption, label }) {
|
|
4
|
+
if (!caption && !label) return null;
|
|
5
|
+
return /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-md", children: /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground leading-relaxed text-left", children: [
|
|
6
|
+
label && /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: label }),
|
|
7
|
+
label && caption && /* @__PURE__ */ jsx("span", { className: "mx-1", children: "—" }),
|
|
8
|
+
caption && renderMathInText(caption)
|
|
9
|
+
] }) });
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
FigureCaption
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=figure-caption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-caption.js","sources":["../../../../../src/components/gallery/components/figure-caption.tsx"],"sourcesContent":["import { renderMathInText } from \"../lib/render-math-in-text\";\n\nexport function FigureCaption({ caption, label }: { caption?: string; label?: string }) {\n if (!caption && !label) return null;\n\n return (\n <div className=\"mx-auto max-w-md\">\n <div className=\"text-sm text-muted-foreground leading-relaxed text-left\">\n {label && <span className=\"font-medium text-foreground\">{label}</span>}\n {label && caption && <span className=\"mx-1\">—</span>}\n {caption && renderMathInText(caption)}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;AAEO,SAAS,cAAc,EAAE,SAAS,SAA+C;AACtF,MAAI,CAAC,WAAW,CAAC,MAAO,QAAO;AAE/B,6BACG,OAAA,EAAI,WAAU,oBACb,UAAA,qBAAC,OAAA,EAAI,WAAU,2DACZ,UAAA;AAAA,IAAA,SAAS,oBAAC,QAAA,EAAK,WAAU,+BAA+B,UAAA,OAAM;AAAA,IAC9D,SAAS,WAAW,oBAAC,QAAA,EAAK,WAAU,QAAO,UAAA,KAAC;AAAA,IAC5C,WAAW,iBAAiB,OAAO;AAAA,EAAA,EAAA,CACtC,EAAA,CACF;AAEJ;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { renderMathInText } from "../lib/render-math-in-text.js";
|
|
3
|
+
function FigureHeader({ title, subtitle }) {
|
|
4
|
+
if (!title && !subtitle) return null;
|
|
5
|
+
return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-md", children: [
|
|
6
|
+
title && /* @__PURE__ */ jsx("h3", { className: "text-sm md:text-base font-medium tracking-tight text-foreground text-left", children: renderMathInText(title) }),
|
|
7
|
+
subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground leading-relaxed text-left mt-1", children: renderMathInText(subtitle) })
|
|
8
|
+
] });
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
FigureHeader
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=figure-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-header.js","sources":["../../../../../src/components/gallery/components/figure-header.tsx"],"sourcesContent":["import { renderMathInText } from \"../lib/render-math-in-text\";\n\nexport function FigureHeader({ title, subtitle }: { title?: string; subtitle?: string }) {\n if (!title && !subtitle) return null;\n\n return (\n <div className=\"mx-auto max-w-md\">\n {title && (\n <h3 className=\"text-sm md:text-base font-medium tracking-tight text-foreground text-left\">\n {renderMathInText(title)}\n </h3>\n )}\n {subtitle && (\n <p className=\"text-sm text-muted-foreground leading-relaxed text-left mt-1\">\n {renderMathInText(subtitle)}\n </p>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;AAEO,SAAS,aAAa,EAAE,OAAO,YAAmD;AACvF,MAAI,CAAC,SAAS,CAAC,SAAU,QAAO;AAEhC,SACE,qBAAC,OAAA,EAAI,WAAU,oBACZ,UAAA;AAAA,IAAA,6BACE,MAAA,EAAG,WAAU,6EACX,UAAA,iBAAiB,KAAK,GACzB;AAAA,IAED,YACC,oBAAC,KAAA,EAAE,WAAU,gEACV,UAAA,iBAAiB,QAAQ,EAAA,CAC5B;AAAA,EAAA,GAEJ;AAEJ;"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
import { X, ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
+
import { FULLSCREEN_DATA_ATTR } from "../../../lib/constants.js";
|
|
5
|
+
function Lightbox({
|
|
6
|
+
images,
|
|
7
|
+
selectedIndex,
|
|
8
|
+
onClose,
|
|
9
|
+
onPrevious,
|
|
10
|
+
onNext,
|
|
11
|
+
showNavigation = true
|
|
12
|
+
}) {
|
|
13
|
+
const current = images[selectedIndex];
|
|
14
|
+
return createPortal(
|
|
15
|
+
/* @__PURE__ */ jsxs(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
className: "fixed inset-0 z-[9999] bg-background",
|
|
19
|
+
onClick: onClose,
|
|
20
|
+
...{ [FULLSCREEN_DATA_ATTR]: "true" },
|
|
21
|
+
style: { top: 0, left: 0, right: 0, bottom: 0 },
|
|
22
|
+
children: [
|
|
23
|
+
/* @__PURE__ */ jsxs(
|
|
24
|
+
"div",
|
|
25
|
+
{
|
|
26
|
+
className: "fixed top-0 left-0 right-0 z-10 flex items-center justify-between px-4 py-3 bg-background/80 backdrop-blur-sm",
|
|
27
|
+
onClick: (e) => e.stopPropagation(),
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ jsxs("div", { className: "font-mono text-xs text-muted-foreground tabular-nums", children: [
|
|
30
|
+
String(selectedIndex + 1).padStart(2, "0"),
|
|
31
|
+
" / ",
|
|
32
|
+
String(images.length).padStart(2, "0")
|
|
33
|
+
] }),
|
|
34
|
+
/* @__PURE__ */ jsx(
|
|
35
|
+
"button",
|
|
36
|
+
{
|
|
37
|
+
onClick: onClose,
|
|
38
|
+
className: "p-2 text-muted-foreground hover:text-foreground transition-colors",
|
|
39
|
+
"aria-label": "Close",
|
|
40
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
),
|
|
46
|
+
showNavigation && selectedIndex > 0 && /* @__PURE__ */ jsx(
|
|
47
|
+
"button",
|
|
48
|
+
{
|
|
49
|
+
onClick: (e) => {
|
|
50
|
+
e.stopPropagation();
|
|
51
|
+
onPrevious();
|
|
52
|
+
},
|
|
53
|
+
className: "fixed left-4 top-1/2 -translate-y-1/2 z-10 p-2 text-muted-foreground hover:text-foreground transition-colors",
|
|
54
|
+
"aria-label": "Previous image",
|
|
55
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-8 w-8" })
|
|
56
|
+
}
|
|
57
|
+
),
|
|
58
|
+
showNavigation && selectedIndex < images.length - 1 && /* @__PURE__ */ jsx(
|
|
59
|
+
"button",
|
|
60
|
+
{
|
|
61
|
+
onClick: (e) => {
|
|
62
|
+
e.stopPropagation();
|
|
63
|
+
onNext();
|
|
64
|
+
},
|
|
65
|
+
className: "fixed right-4 top-1/2 -translate-y-1/2 z-10 p-2 text-muted-foreground hover:text-foreground transition-colors",
|
|
66
|
+
"aria-label": "Next image",
|
|
67
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-8 w-8" })
|
|
68
|
+
}
|
|
69
|
+
),
|
|
70
|
+
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-16", children: /* @__PURE__ */ jsx(
|
|
71
|
+
"img",
|
|
72
|
+
{
|
|
73
|
+
src: current.src,
|
|
74
|
+
alt: current.label,
|
|
75
|
+
className: "max-w-full max-h-full object-contain",
|
|
76
|
+
onClick: (e) => e.stopPropagation()
|
|
77
|
+
}
|
|
78
|
+
) }),
|
|
79
|
+
/* @__PURE__ */ jsx(
|
|
80
|
+
"div",
|
|
81
|
+
{
|
|
82
|
+
className: "fixed bottom-0 left-0 right-0 z-10 p-4 text-center bg-background/80 backdrop-blur-sm",
|
|
83
|
+
onClick: (e) => e.stopPropagation(),
|
|
84
|
+
children: /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-muted-foreground", children: current.label })
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
),
|
|
90
|
+
document.body
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
Lightbox
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=lightbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lightbox.js","sources":["../../../../../src/components/gallery/components/lightbox.tsx"],"sourcesContent":["import { createPortal } from \"react-dom\";\nimport { X, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { FULLSCREEN_DATA_ATTR } from \"@/lib/constants\";\n\nexport interface LightboxImage {\n src: string;\n label: string;\n}\n\nexport interface LightboxProps {\n images: LightboxImage[];\n selectedIndex: number;\n onClose: () => void;\n onPrevious: () => void;\n onNext: () => void;\n showNavigation?: boolean;\n}\n\n/**\n * Fullscreen lightbox component for viewing images\n */\nexport function Lightbox({\n images,\n selectedIndex,\n onClose,\n onPrevious,\n onNext,\n showNavigation = true,\n}: LightboxProps) {\n const current = images[selectedIndex];\n\n return createPortal(\n <div\n className=\"fixed inset-0 z-[9999] bg-background\"\n onClick={onClose}\n {...{ [FULLSCREEN_DATA_ATTR]: \"true\" }}\n style={{ top: 0, left: 0, right: 0, bottom: 0 }}\n >\n {/* Top bar */}\n <div\n className=\"fixed top-0 left-0 right-0 z-10 flex items-center justify-between px-4 py-3 bg-background/80 backdrop-blur-sm\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"font-mono text-xs text-muted-foreground tabular-nums\">\n {String(selectedIndex + 1).padStart(2, '0')} / {String(images.length).padStart(2, '0')}\n </div>\n <button\n onClick={onClose}\n className=\"p-2 text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Close\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Navigation: Previous */}\n {showNavigation && selectedIndex > 0 && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n onPrevious();\n }}\n className=\"fixed left-4 top-1/2 -translate-y-1/2 z-10 p-2 text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Previous image\"\n >\n <ChevronLeft className=\"h-8 w-8\" />\n </button>\n )}\n\n {/* Navigation: Next */}\n {showNavigation && selectedIndex < images.length - 1 && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n onNext();\n }}\n className=\"fixed right-4 top-1/2 -translate-y-1/2 z-10 p-2 text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Next image\"\n >\n <ChevronRight className=\"h-8 w-8\" />\n </button>\n )}\n\n {/* Main image */}\n <div className=\"fixed inset-0 flex items-center justify-center p-16\">\n <img\n src={current.src}\n alt={current.label}\n className=\"max-w-full max-h-full object-contain\"\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n\n {/* Caption */}\n <div\n className=\"fixed bottom-0 left-0 right-0 z-10 p-4 text-center bg-background/80 backdrop-blur-sm\"\n onClick={(e) => e.stopPropagation()}\n >\n <span className=\"font-mono text-xs text-muted-foreground\">\n {current.label}\n </span>\n </div>\n </div>,\n document.body\n );\n}\n"],"names":[],"mappings":";;;;AAqBO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AACnB,GAAkB;AAChB,QAAM,UAAU,OAAO,aAAa;AAEpC,SAAO;AAAA,IACL;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,SAAS;AAAA,QACR,GAAG,EAAE,CAAC,oBAAoB,GAAG,OAAA;AAAA,QAC9B,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,EAAA;AAAA,QAG5C,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,cAElB,UAAA;AAAA,gBAAA,qBAAC,OAAA,EAAI,WAAU,wDACZ,UAAA;AAAA,kBAAA,OAAO,gBAAgB,CAAC,EAAE,SAAS,GAAG,GAAG;AAAA,kBAAE;AAAA,kBAAI,OAAO,OAAO,MAAM,EAAE,SAAS,GAAG,GAAG;AAAA,gBAAA,GACvF;AAAA,gBACA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,SAAS;AAAA,oBACT,WAAU;AAAA,oBACV,cAAW;AAAA,oBAEX,UAAA,oBAAC,GAAA,EAAE,WAAU,UAAA,CAAU;AAAA,kBAAA;AAAA,gBAAA;AAAA,cACzB;AAAA,YAAA;AAAA,UAAA;AAAA,UAID,kBAAkB,gBAAgB,KACjC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAS,CAAC,MAAM;AACd,kBAAE,gBAAA;AACF,2BAAA;AAAA,cACF;AAAA,cACA,WAAU;AAAA,cACV,cAAW;AAAA,cAEX,UAAA,oBAAC,aAAA,EAAY,WAAU,UAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAKpC,kBAAkB,gBAAgB,OAAO,SAAS,KACjD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAS,CAAC,MAAM;AACd,kBAAE,gBAAA;AACF,uBAAA;AAAA,cACF;AAAA,cACA,WAAU;AAAA,cACV,cAAW;AAAA,cAEX,UAAA,oBAAC,cAAA,EAAa,WAAU,UAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAKtC,oBAAC,OAAA,EAAI,WAAU,uDACb,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAK,QAAQ;AAAA,cACb,KAAK,QAAQ;AAAA,cACb,WAAU;AAAA,cACV,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,YAAgB;AAAA,UAAA,GAEtC;AAAA,UAGA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,cAElB,UAAA,oBAAC,QAAA,EAAK,WAAU,2CACb,kBAAQ,MAAA,CACX;AAAA,YAAA;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,SAAS;AAAA,EAAA;AAEb;"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Image } from "lucide-react";
|
|
4
|
+
import { cn } from "../../../lib/utils.js";
|
|
5
|
+
function LoadingImage({
|
|
6
|
+
className,
|
|
7
|
+
wrapperClassName,
|
|
8
|
+
...props
|
|
9
|
+
}) {
|
|
10
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
11
|
+
const [hasError, setHasError] = useState(false);
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative", wrapperClassName), children: [
|
|
13
|
+
isLoading && !hasError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-muted/30 animate-pulse flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-8 h-8 border border-border/50 rounded-sm" }) }),
|
|
14
|
+
hasError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-muted/20 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
15
|
+
/* @__PURE__ */ jsx(Image, { className: "h-5 w-5 text-muted-foreground/40 mx-auto" }),
|
|
16
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground/40 mt-1.5 block font-mono", children: "failed" })
|
|
17
|
+
] }) }),
|
|
18
|
+
/* @__PURE__ */ jsx(
|
|
19
|
+
"img",
|
|
20
|
+
{
|
|
21
|
+
...props,
|
|
22
|
+
className: cn(
|
|
23
|
+
className,
|
|
24
|
+
"transition-opacity duration-500 ease-out-expo",
|
|
25
|
+
isLoading && "opacity-0",
|
|
26
|
+
hasError && "opacity-0"
|
|
27
|
+
),
|
|
28
|
+
onLoad: (e) => {
|
|
29
|
+
var _a;
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
(_a = props.onLoad) == null ? void 0 : _a.call(props, e);
|
|
32
|
+
},
|
|
33
|
+
onError: (e) => {
|
|
34
|
+
var _a;
|
|
35
|
+
setIsLoading(false);
|
|
36
|
+
setHasError(true);
|
|
37
|
+
(_a = props.onError) == null ? void 0 : _a.call(props, e);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
] });
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
LoadingImage
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=loading-image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loading-image.js","sources":["../../../../../src/components/gallery/components/loading-image.tsx"],"sourcesContent":["import { useState, ImgHTMLAttributes } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport function LoadingImage({\n className,\n wrapperClassName,\n ...props\n}: ImgHTMLAttributes<HTMLImageElement> & { wrapperClassName?: string }) {\n const [isLoading, setIsLoading] = useState(true);\n const [hasError, setHasError] = useState(false);\n\n return (\n <div className={cn(\"relative\", wrapperClassName)}>\n {isLoading && !hasError && (\n <div className=\"absolute inset-0 bg-muted/30 animate-pulse flex items-center justify-center\">\n <div className=\"w-8 h-8 border border-border/50 rounded-sm\" />\n </div>\n )}\n {hasError && (\n <div className=\"absolute inset-0 bg-muted/20 flex items-center justify-center\">\n <div className=\"text-center\">\n <Image className=\"h-5 w-5 text-muted-foreground/40 mx-auto\" />\n <span className=\"text-xs text-muted-foreground/40 mt-1.5 block font-mono\">failed</span>\n </div>\n </div>\n )}\n <img\n {...props}\n className={cn(\n className,\n \"transition-opacity duration-500 ease-out-expo\",\n isLoading && \"opacity-0\",\n hasError && \"opacity-0\"\n )}\n onLoad={(e) => {\n setIsLoading(false);\n props.onLoad?.(e);\n }}\n onError={(e) => {\n setIsLoading(false);\n setHasError(true);\n props.onError?.(e);\n }}\n />\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AAIO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAwE;AACtE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,8BACG,OAAA,EAAI,WAAW,GAAG,YAAY,gBAAgB,GAC5C,UAAA;AAAA,IAAA,aAAa,CAAC,YACb,oBAAC,OAAA,EAAI,WAAU,+EACb,UAAA,oBAAC,OAAA,EAAI,WAAU,6CAAA,CAA6C,EAAA,CAC9D;AAAA,IAED,gCACE,OAAA,EAAI,WAAU,iEACb,UAAA,qBAAC,OAAA,EAAI,WAAU,eACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAM,WAAU,2CAAA,CAA2C;AAAA,MAC5D,oBAAC,QAAA,EAAK,WAAU,2DAA0D,UAAA,SAAA,CAAM;AAAA,IAAA,EAAA,CAClF,EAAA,CACF;AAAA,IAEF;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb,YAAY;AAAA,QAAA;AAAA,QAEd,QAAQ,CAAC,MAAM;;AACb,uBAAa,KAAK;AAClB,sBAAM,WAAN,+BAAe;AAAA,QACjB;AAAA,QACA,SAAS,CAAC,MAAM;;AACd,uBAAa,KAAK;AAClB,sBAAY,IAAI;AAChB,sBAAM,YAAN,+BAAgB;AAAA,QAClB;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useTheme } from "next-themes";
|
|
3
|
+
import { useDirectory } from "../../../plugin/src/client.js";
|
|
4
|
+
import { minimatch } from "minimatch";
|
|
5
|
+
function sortPathsNumerically(paths) {
|
|
6
|
+
paths.sort((a, b) => {
|
|
7
|
+
const nums = (s) => (s.match(/\d+/g) || []).map(Number);
|
|
8
|
+
const na = nums(a);
|
|
9
|
+
const nb = nums(b);
|
|
10
|
+
const len = Math.max(na.length, nb.length);
|
|
11
|
+
for (let i = 0; i < len; i++) {
|
|
12
|
+
const diff = (na[i] ?? 0) - (nb[i] ?? 0);
|
|
13
|
+
if (diff !== 0) return diff;
|
|
14
|
+
}
|
|
15
|
+
return a.localeCompare(b);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function filterPathsByTheme(paths, theme) {
|
|
19
|
+
const pathGroups = /* @__PURE__ */ new Map();
|
|
20
|
+
paths.forEach((path) => {
|
|
21
|
+
if (path.endsWith("_light.png")) {
|
|
22
|
+
const baseName = path.replace("_light.png", "");
|
|
23
|
+
const group = pathGroups.get(baseName) || {};
|
|
24
|
+
group.light = path;
|
|
25
|
+
pathGroups.set(baseName, group);
|
|
26
|
+
} else if (path.endsWith("_dark.png")) {
|
|
27
|
+
const baseName = path.replace("_dark.png", "");
|
|
28
|
+
const group = pathGroups.get(baseName) || {};
|
|
29
|
+
group.dark = path;
|
|
30
|
+
pathGroups.set(baseName, group);
|
|
31
|
+
} else {
|
|
32
|
+
pathGroups.set(path, { original: path });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const filtered = [];
|
|
36
|
+
pathGroups.forEach((group, baseName) => {
|
|
37
|
+
if (group.original) {
|
|
38
|
+
filtered.push(group.original);
|
|
39
|
+
} else {
|
|
40
|
+
const isDark = theme === "dark";
|
|
41
|
+
const preferredPath = isDark ? group.dark : group.light;
|
|
42
|
+
const fallbackPath = isDark ? group.light : group.dark;
|
|
43
|
+
filtered.push(preferredPath || fallbackPath || baseName);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return filtered;
|
|
47
|
+
}
|
|
48
|
+
function useGalleryImages({
|
|
49
|
+
path,
|
|
50
|
+
globs = null,
|
|
51
|
+
limit,
|
|
52
|
+
page = 0
|
|
53
|
+
}) {
|
|
54
|
+
const { resolvedTheme } = useTheme();
|
|
55
|
+
let resolvedPath = path;
|
|
56
|
+
const { directory } = useDirectory(resolvedPath);
|
|
57
|
+
const paths = useMemo(() => {
|
|
58
|
+
if (!directory) return [];
|
|
59
|
+
const imageChildren = directory.children.filter((child) => {
|
|
60
|
+
return !!child.name.match(/\.(png|jpeg|gif|svg|webp)$/i) && child.type === "file";
|
|
61
|
+
});
|
|
62
|
+
let imagePaths = imageChildren.map((child) => child.path);
|
|
63
|
+
if (globs && globs.length > 0) {
|
|
64
|
+
imagePaths = imagePaths.filter((p) => {
|
|
65
|
+
return globs.some((glob) => minimatch(p.split("/").pop() || "", glob));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
sortPathsNumerically(imagePaths);
|
|
69
|
+
let filtered = filterPathsByTheme(imagePaths, resolvedTheme);
|
|
70
|
+
if (limit) {
|
|
71
|
+
filtered = filtered.slice(page * limit, (page + 1) * limit);
|
|
72
|
+
}
|
|
73
|
+
return filtered;
|
|
74
|
+
}, [directory, globs, resolvedTheme, limit, page]);
|
|
75
|
+
return {
|
|
76
|
+
paths,
|
|
77
|
+
isLoading: !directory,
|
|
78
|
+
isEmpty: directory !== void 0 && paths.length === 0
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
useGalleryImages
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=use-gallery-images.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-gallery-images.js","sources":["../../../../../src/components/gallery/hooks/use-gallery-images.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useTheme } from \"next-themes\";\nimport { useDirectory } from \"../../../../plugin/src/client\";\nimport { FileEntry } from \"../../../../plugin/src/lib\";\nimport { minimatch } from \"minimatch\";\n\nfunction sortPathsNumerically(paths: string[]): void {\n paths.sort((a, b) => {\n const nums = (s: string) => (s.match(/\\d+/g) || []).map(Number);\n const na = nums(a);\n const nb = nums(b);\n const len = Math.max(na.length, nb.length);\n for (let i = 0; i < len; i++) {\n const diff = (na[i] ?? 0) - (nb[i] ?? 0);\n if (diff !== 0) return diff;\n }\n return a.localeCompare(b);\n });\n}\n\nfunction filterPathsByTheme(paths: string[], theme: string | undefined): string[] {\n const pathGroups = new Map<string, { light?: string; dark?: string; original?: string }>();\n\n paths.forEach(path => {\n if (path.endsWith('_light.png')) {\n const baseName = path.replace('_light.png', '');\n const group = pathGroups.get(baseName) || {};\n group.light = path;\n pathGroups.set(baseName, group);\n } else if (path.endsWith('_dark.png')) {\n const baseName = path.replace('_dark.png', '');\n const group = pathGroups.get(baseName) || {};\n group.dark = path;\n pathGroups.set(baseName, group);\n } else {\n pathGroups.set(path, { original: path });\n }\n });\n\n const filtered: string[] = [];\n pathGroups.forEach((group, baseName) => {\n if (group.original) {\n filtered.push(group.original);\n } else {\n const isDark = theme === 'dark';\n const preferredPath = isDark ? group.dark : group.light;\n const fallbackPath = isDark ? group.light : group.dark;\n filtered.push(preferredPath || fallbackPath || baseName);\n }\n });\n\n return filtered;\n}\n\n\nexport function useGalleryImages({\n path,\n globs = null,\n limit,\n page = 0,\n}: {\n path?: string;\n globs?: string[] | null;\n limit?: number | null;\n page?: number;\n}) {\n const { resolvedTheme } = useTheme();\n\n let resolvedPath = path;\n\n const { directory } = useDirectory(resolvedPath);\n\n const paths = useMemo(() => {\n if (!directory) return [];\n\n const imageChildren = directory.children.filter((child): child is FileEntry => {\n return !!child.name.match(/\\.(png|jpeg|gif|svg|webp)$/i) && child.type === \"file\";\n });\n\n let imagePaths = imageChildren.map(child => child.path);\n\n if (globs && globs.length > 0) {\n imagePaths = imagePaths.filter(p => {\n return globs.some(glob => minimatch(p.split('/').pop() || '', glob));\n });\n }\n\n sortPathsNumerically(imagePaths);\n let filtered = filterPathsByTheme(imagePaths, resolvedTheme);\n\n if (limit) {\n filtered = filtered.slice(page * limit, (page + 1) * limit);\n }\n\n return filtered;\n }, [directory, globs, resolvedTheme, limit, page]);\n\n return {\n paths,\n isLoading: !directory,\n isEmpty: directory !== undefined && paths.length === 0,\n };\n}\n"],"names":[],"mappings":";;;;AAMA,SAAS,qBAAqB,OAAuB;AACnD,QAAM,KAAK,CAAC,GAAG,MAAM;AACnB,UAAM,OAAO,CAAC,OAAe,EAAE,MAAM,MAAM,KAAK,CAAA,GAAI,IAAI,MAAM;AAC9D,UAAM,KAAK,KAAK,CAAC;AACjB,UAAM,KAAK,KAAK,CAAC;AACjB,UAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,GAAG,MAAM;AACzC,aAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,YAAM,QAAQ,GAAG,CAAC,KAAK,MAAM,GAAG,CAAC,KAAK;AACtC,UAAI,SAAS,EAAG,QAAO;AAAA,IACzB;AACA,WAAO,EAAE,cAAc,CAAC;AAAA,EAC1B,CAAC;AACH;AAEA,SAAS,mBAAmB,OAAiB,OAAqC;AAChF,QAAM,iCAAiB,IAAA;AAEvB,QAAM,QAAQ,CAAA,SAAQ;AACpB,QAAI,KAAK,SAAS,YAAY,GAAG;AAC/B,YAAM,WAAW,KAAK,QAAQ,cAAc,EAAE;AAC9C,YAAM,QAAQ,WAAW,IAAI,QAAQ,KAAK,CAAA;AAC1C,YAAM,QAAQ;AACd,iBAAW,IAAI,UAAU,KAAK;AAAA,IAChC,WAAW,KAAK,SAAS,WAAW,GAAG;AACrC,YAAM,WAAW,KAAK,QAAQ,aAAa,EAAE;AAC7C,YAAM,QAAQ,WAAW,IAAI,QAAQ,KAAK,CAAA;AAC1C,YAAM,OAAO;AACb,iBAAW,IAAI,UAAU,KAAK;AAAA,IAChC,OAAO;AACL,iBAAW,IAAI,MAAM,EAAE,UAAU,MAAM;AAAA,IACzC;AAAA,EACF,CAAC;AAED,QAAM,WAAqB,CAAA;AAC3B,aAAW,QAAQ,CAAC,OAAO,aAAa;AACtC,QAAI,MAAM,UAAU;AAClB,eAAS,KAAK,MAAM,QAAQ;AAAA,IAC9B,OAAO;AACL,YAAM,SAAS,UAAU;AACzB,YAAM,gBAAgB,SAAS,MAAM,OAAO,MAAM;AAClD,YAAM,eAAe,SAAS,MAAM,QAAQ,MAAM;AAClD,eAAS,KAAK,iBAAiB,gBAAgB,QAAQ;AAAA,IACzD;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAGO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,OAAO;AACT,GAKG;AACD,QAAM,EAAE,cAAA,IAAkB,SAAA;AAE1B,MAAI,eAAe;AAEnB,QAAM,EAAE,UAAA,IAAc,aAAa,YAAY;AAE/C,QAAM,QAAQ,QAAQ,MAAM;AAC1B,QAAI,CAAC,UAAW,QAAO,CAAA;AAEvB,UAAM,gBAAgB,UAAU,SAAS,OAAO,CAAC,UAA8B;AAC7E,aAAO,CAAC,CAAC,MAAM,KAAK,MAAM,6BAA6B,KAAK,MAAM,SAAS;AAAA,IAC7E,CAAC;AAED,QAAI,aAAa,cAAc,IAAI,CAAA,UAAS,MAAM,IAAI;AAEtD,QAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,mBAAa,WAAW,OAAO,CAAA,MAAK;AAClC,eAAO,MAAM,KAAK,CAAA,SAAQ,UAAU,EAAE,MAAM,GAAG,EAAE,IAAA,KAAS,IAAI,IAAI,CAAC;AAAA,MACrE,CAAC;AAAA,IACH;AAEA,yBAAqB,UAAU;AAC/B,QAAI,WAAW,mBAAmB,YAAY,aAAa;AAE3D,QAAI,OAAO;AACT,iBAAW,SAAS,MAAM,OAAO,QAAQ,OAAO,KAAK,KAAK;AAAA,IAC5D;AAEA,WAAO;AAAA,EACT,GAAG,CAAC,WAAW,OAAO,eAAe,OAAO,IAAI,CAAC;AAEjD,SAAO;AAAA,IACL;AAAA,IACA,WAAW,CAAC;AAAA,IACZ,SAAS,cAAc,UAAa,MAAM,WAAW;AAAA,EAAA;AAEzD;"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useKeyBindings } from "../../../hooks/use-key-bindings.js";
|
|
2
|
+
import { useState, useCallback } from "react";
|
|
3
|
+
function useLightbox(totalImages) {
|
|
4
|
+
const [selectedIndex, setSelectedIndex] = useState(null);
|
|
5
|
+
const open = useCallback((index) => {
|
|
6
|
+
setSelectedIndex(index);
|
|
7
|
+
}, []);
|
|
8
|
+
const close = useCallback(() => {
|
|
9
|
+
setSelectedIndex(null);
|
|
10
|
+
}, []);
|
|
11
|
+
const goToPrevious = useCallback(() => {
|
|
12
|
+
setSelectedIndex((prev) => prev !== null && prev > 0 ? prev - 1 : prev);
|
|
13
|
+
}, []);
|
|
14
|
+
const goToNext = useCallback(() => {
|
|
15
|
+
setSelectedIndex((prev) => prev !== null && prev < totalImages - 1 ? prev + 1 : prev);
|
|
16
|
+
}, [totalImages]);
|
|
17
|
+
useKeyBindings(
|
|
18
|
+
[
|
|
19
|
+
{ key: "Escape", action: close },
|
|
20
|
+
{ key: "ArrowLeft", action: goToPrevious },
|
|
21
|
+
{ key: "ArrowRight", action: goToNext }
|
|
22
|
+
],
|
|
23
|
+
{ enabled: () => selectedIndex !== null }
|
|
24
|
+
);
|
|
25
|
+
return {
|
|
26
|
+
selectedIndex,
|
|
27
|
+
open,
|
|
28
|
+
close,
|
|
29
|
+
goToPrevious,
|
|
30
|
+
goToNext,
|
|
31
|
+
isOpen: selectedIndex !== null
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
useLightbox
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=use-lightbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-lightbox.js","sources":["../../../../../src/components/gallery/hooks/use-lightbox.ts"],"sourcesContent":["import { useKeyBindings } from \"@/hooks/use-key-bindings\"; \nimport { useCallback, useState } from \"react\";\n\nexport function useLightbox(totalImages: number) {\n const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n\n const open = useCallback((index: number) => {\n setSelectedIndex(index);\n }, []);\n\n const close = useCallback(() => {\n setSelectedIndex(null);\n }, []);\n\n const goToPrevious = useCallback(() => {\n setSelectedIndex(prev => prev !== null && prev > 0 ? prev - 1 : prev);\n }, []);\n\n const goToNext = useCallback(() => {\n setSelectedIndex(prev => prev !== null && prev < totalImages - 1 ? prev + 1 : prev);\n }, [totalImages]);\n\n useKeyBindings(\n [\n { key: \"Escape\", action: close },\n { key: \"ArrowLeft\", action: goToPrevious },\n { key: \"ArrowRight\", action: goToNext },\n ],\n { enabled: () => selectedIndex !== null }\n );\n\n return {\n selectedIndex,\n open,\n close,\n goToPrevious,\n goToNext,\n isOpen: selectedIndex !== null,\n };\n}\n"],"names":[],"mappings":";;AAGO,SAAS,YAAY,aAAqB;AAC/C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAwB,IAAI;AAEtE,QAAM,OAAO,YAAY,CAAC,UAAkB;AAC1C,qBAAiB,KAAK;AAAA,EACxB,GAAG,CAAA,CAAE;AAEL,QAAM,QAAQ,YAAY,MAAM;AAC9B,qBAAiB,IAAI;AAAA,EACvB,GAAG,CAAA,CAAE;AAEL,QAAM,eAAe,YAAY,MAAM;AACrC,qBAAiB,UAAQ,SAAS,QAAQ,OAAO,IAAI,OAAO,IAAI,IAAI;AAAA,EACtE,GAAG,CAAA,CAAE;AAEL,QAAM,WAAW,YAAY,MAAM;AACjC,qBAAiB,CAAA,SAAQ,SAAS,QAAQ,OAAO,cAAc,IAAI,OAAO,IAAI,IAAI;AAAA,EACpF,GAAG,CAAC,WAAW,CAAC;AAEhB;AAAA,IACE;AAAA,MACE,EAAE,KAAK,UAAU,QAAQ,MAAA;AAAA,MACzB,EAAE,KAAK,aAAa,QAAQ,aAAA;AAAA,MAC5B,EAAE,KAAK,cAAc,QAAQ,SAAA;AAAA,IAAS;AAAA,IAExC,EAAE,SAAS,MAAM,kBAAkB,KAAA;AAAA,EAAK;AAG1C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,kBAAkB;AAAA,EAAA;AAE9B;"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { Image } from "lucide-react";
|
|
4
|
+
import { Lightbox } from "./components/lightbox.js";
|
|
5
|
+
import { useGalleryImages } from "./hooks/use-gallery-images.js";
|
|
6
|
+
import { useLightbox } from "./hooks/use-lightbox.js";
|
|
7
|
+
import { LoadingImage } from "./components/loading-image.js";
|
|
8
|
+
import { FigureHeader } from "./components/figure-header.js";
|
|
9
|
+
import { FigureCaption } from "./components/figure-caption.js";
|
|
10
|
+
import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from "../ui/carousel.js";
|
|
11
|
+
function getImageLabel(path) {
|
|
12
|
+
const filename = path.split("/").pop() || path;
|
|
13
|
+
return filename.replace(/\.(png|jpg|jpeg|gif|svg|webp)$/i, "").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim();
|
|
14
|
+
}
|
|
15
|
+
function getImageUrl(path) {
|
|
16
|
+
return `/raw/${path}`;
|
|
17
|
+
}
|
|
18
|
+
function Gallery({
|
|
19
|
+
path,
|
|
20
|
+
globs = null,
|
|
21
|
+
caption,
|
|
22
|
+
captionLabel,
|
|
23
|
+
title,
|
|
24
|
+
subtitle,
|
|
25
|
+
limit = null,
|
|
26
|
+
page = 0
|
|
27
|
+
}) {
|
|
28
|
+
const { paths, isLoading, isEmpty } = useGalleryImages({
|
|
29
|
+
path,
|
|
30
|
+
globs,
|
|
31
|
+
limit,
|
|
32
|
+
page
|
|
33
|
+
});
|
|
34
|
+
const lightbox = useLightbox(paths.length);
|
|
35
|
+
const images = useMemo(
|
|
36
|
+
() => paths.map((p) => ({ src: getImageUrl(p), label: getImageLabel(p) })),
|
|
37
|
+
[paths]
|
|
38
|
+
);
|
|
39
|
+
if (isLoading) {
|
|
40
|
+
return /* @__PURE__ */ jsx("div", { className: "not-prose py-4 md:py-6", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2 sm:gap-4 max-w-[var(--gallery-width)] mx-auto", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
className: "aspect-[4/3] bg-muted/30 animate-pulse",
|
|
44
|
+
style: { animationDelay: `${i * 100}ms` }
|
|
45
|
+
},
|
|
46
|
+
i
|
|
47
|
+
)) }) });
|
|
48
|
+
}
|
|
49
|
+
if (isEmpty) {
|
|
50
|
+
return /* @__PURE__ */ jsx("div", { className: "not-prose py-16 text-center", children: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-3 text-muted-foreground/60", children: [
|
|
51
|
+
/* @__PURE__ */ jsx(Image, { className: "h-4 w-4" }),
|
|
52
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-sm tracking-wide", children: "no image(s) found" })
|
|
53
|
+
] }) });
|
|
54
|
+
}
|
|
55
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
56
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-lg not-prose flex flex-col gap-0 relative p-4 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]", children: [
|
|
57
|
+
/* @__PURE__ */ jsx(FigureHeader, { title, subtitle }),
|
|
58
|
+
/* @__PURE__ */ jsxs(Carousel, { children: [
|
|
59
|
+
/* @__PURE__ */ jsx(CarouselContent, { children: images.map((img, index) => /* @__PURE__ */ jsx(
|
|
60
|
+
CarouselItem,
|
|
61
|
+
{
|
|
62
|
+
className: "mx-auto md:basis-1/2 lg:basis-1/3 cursor-pointer transition-transform duration-700 ease-out-expo hover:scale-[1.02]",
|
|
63
|
+
onClick: () => lightbox.open(index),
|
|
64
|
+
children: /* @__PURE__ */ jsx(
|
|
65
|
+
LoadingImage,
|
|
66
|
+
{
|
|
67
|
+
src: img.src,
|
|
68
|
+
alt: img.label
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
},
|
|
72
|
+
index
|
|
73
|
+
)) }),
|
|
74
|
+
images.length > 3 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
75
|
+
/* @__PURE__ */ jsx(CarouselPrevious, {}),
|
|
76
|
+
/* @__PURE__ */ jsx(CarouselNext, {})
|
|
77
|
+
] })
|
|
78
|
+
] }),
|
|
79
|
+
/* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
|
|
80
|
+
] }),
|
|
81
|
+
lightbox.isOpen && lightbox.selectedIndex !== null && /* @__PURE__ */ jsx(
|
|
82
|
+
Lightbox,
|
|
83
|
+
{
|
|
84
|
+
images,
|
|
85
|
+
selectedIndex: lightbox.selectedIndex,
|
|
86
|
+
onClose: lightbox.close,
|
|
87
|
+
onPrevious: lightbox.goToPrevious,
|
|
88
|
+
onNext: lightbox.goToNext
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
] });
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
Gallery as default
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/components/gallery/index.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { Lightbox, LightboxImage } from \"@/components/gallery/components/lightbox\";\nimport { useGalleryImages } from \"./hooks/use-gallery-images\";\nimport { useLightbox } from \"./hooks/use-lightbox\";\nimport { LoadingImage } from \"./components/loading-image\";\nimport { FigureHeader } from \"./components/figure-header\";\nimport { FigureCaption } from \"./components/figure-caption\";\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n} from \"@/components/ui/carousel\"\n\nfunction getImageLabel(path: string): string {\n const filename = path.split('/').pop() || path;\n return filename\n .replace(/\\.(png|jpg|jpeg|gif|svg|webp)$/i, '')\n .replace(/[_-]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction getImageUrl(path: string): string {\n return `/raw/${path}`;\n}\n\nexport default function Gallery({\n path,\n globs = null, \n caption,\n captionLabel,\n title,\n subtitle,\n limit = null,\n page = 0,\n}: {\n path?: string;\n globs?: string[] | null;\n caption?: string;\n captionLabel?: string;\n title?: string;\n subtitle?: string;\n limit?: number | null;\n page?: number;\n}) {\n const { paths, isLoading, isEmpty } = useGalleryImages({\n path,\n globs,\n limit,\n page: page,\n });\n\n const lightbox = useLightbox(paths.length);\n\n const images: LightboxImage[] = useMemo(() =>\n paths.map(p => ({ src: getImageUrl(p), label: getImageLabel(p) })),\n [paths]\n );\n\n if (isLoading) {\n return (\n <div className=\"not-prose py-4 md:py-6\">\n <div className=\"grid grid-cols-3 gap-2 sm:gap-4 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-[4/3] bg-muted/30 animate-pulse\"\n style={{ animationDelay: `${i * 100}ms` }}\n />\n ))}\n </div>\n </div>\n );\n }\n\n if (isEmpty) {\n return (\n <div className=\"not-prose py-16 text-center\">\n <div className=\"inline-flex items-center gap-3 text-muted-foreground/60\">\n <Image className=\"h-4 w-4\" />\n <span className=\"font-mono text-sm tracking-wide\">no image(s) found</span>\n </div>\n </div>\n );\n }\n\n return (\n <>\n <div className=\"rounded-lg not-prose flex flex-col gap-0 relative p-4 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]\">\n <FigureHeader title={title} subtitle={subtitle} />\n\n <Carousel>\n <CarouselContent>\n {images.map((img, index) => (\n <CarouselItem \n key={index} \n className=\"mx-auto md:basis-1/2 lg:basis-1/3 cursor-pointer transition-transform duration-700 ease-out-expo hover:scale-[1.02]\"\n // wrapperClassName=\"h-full\"\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n />\n </CarouselItem>\n ))}\n </CarouselContent>\n\n {images.length > 3 && (\n <>\n <CarouselPrevious />\n <CarouselNext />\n </>\n )} \n </Carousel>\n\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n\n {lightbox.isOpen && lightbox.selectedIndex !== null && (\n <Lightbox\n images={images}\n selectedIndex={lightbox.selectedIndex}\n onClose={lightbox.close}\n onPrevious={lightbox.goToPrevious}\n onNext={lightbox.goToNext}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAgBA,SAAS,cAAc,MAAsB;AAC3C,QAAM,WAAW,KAAK,MAAM,GAAG,EAAE,SAAS;AAC1C,SAAO,SACJ,QAAQ,mCAAmC,EAAE,EAC7C,QAAQ,SAAS,GAAG,EACpB,QAAQ,QAAQ,GAAG,EACnB,KAAA;AACL;AAEA,SAAS,YAAY,MAAsB;AACzC,SAAO,QAAQ,IAAI;AACrB;AAEA,SAAwB,QAAQ;AAAA,EAC9B;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,OAAO;AACT,GASG;AACD,QAAM,EAAE,OAAO,WAAW,QAAA,IAAY,iBAAiB;AAAA,IACrD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AAED,QAAM,WAAW,YAAY,MAAM,MAAM;AAEzC,QAAM,SAA0B;AAAA,IAAQ,MACtC,MAAM,IAAI,CAAA,OAAM,EAAE,KAAK,YAAY,CAAC,GAAG,OAAO,cAAc,CAAC,IAAI;AAAA,IACjE,CAAC,KAAK;AAAA,EAAA;AAGR,MAAI,WAAW;AACb,+BACG,OAAA,EAAI,WAAU,0BACb,UAAA,oBAAC,SAAI,WAAU,wEACZ,UAAA,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,MACrB;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QACV,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,KAAA;AAAA,MAAK;AAAA,MAFnC;AAAA,IAAA,CAIR,GACH,EAAA,CACF;AAAA,EAEJ;AAEA,MAAI,SAAS;AACX,+BACG,OAAA,EAAI,WAAU,+BACb,UAAA,qBAAC,OAAA,EAAI,WAAU,2DACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAM,WAAU,UAAA,CAAU;AAAA,MAC3B,oBAAC,QAAA,EAAK,WAAU,mCAAkC,UAAA,oBAAA,CAAiB;AAAA,IAAA,EAAA,CACrE,EAAA,CACF;AAAA,EAEJ;AAEA,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,uIACb,UAAA;AAAA,MAAA,oBAAC,cAAA,EAAa,OAAc,SAAA,CAAoB;AAAA,2BAE/C,UAAA,EACC,UAAA;AAAA,QAAA,oBAAC,iBAAA,EACE,UAAA,OAAO,IAAI,CAAC,KAAK,UAChB;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,WAAU;AAAA,YAEV,SAAS,MAAM,SAAS,KAAK,KAAK;AAAA,YAElC,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,KAAK,IAAI;AAAA,gBACT,KAAK,IAAI;AAAA,cAAA;AAAA,YAAA;AAAA,UACX;AAAA,UARK;AAAA,QAAA,CAUR,GACH;AAAA,QAEC,OAAO,SAAS,KACf,qBAAA,UAAA,EACE,UAAA;AAAA,UAAA,oBAAC,kBAAA,EAAiB;AAAA,8BACjB,cAAA,CAAA,CAAa;AAAA,QAAA,EAAA,CAChB;AAAA,MAAA,GAEJ;AAAA,MAEA,oBAAC,eAAA,EAAc,SAAkB,OAAO,aAAA,CAAc;AAAA,IAAA,GACxD;AAAA,IAEC,SAAS,UAAU,SAAS,kBAAkB,QAC7C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAe,SAAS;AAAA,QACxB,SAAS,SAAS;AAAA,QAClB,YAAY,SAAS;AAAA,QACrB,QAAQ,SAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EACnB,GAEJ;AAEJ;"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import katex from "katex";
|
|
3
|
+
function renderMathInText(text) {
|
|
4
|
+
const parts = [];
|
|
5
|
+
let lastIndex = 0;
|
|
6
|
+
const regex = /\$\$([^$]+)\$\$|\$([^$]+)\$/g;
|
|
7
|
+
let match;
|
|
8
|
+
let key = 0;
|
|
9
|
+
while ((match = regex.exec(text)) !== null) {
|
|
10
|
+
if (match.index > lastIndex) {
|
|
11
|
+
parts.push(text.slice(lastIndex, match.index));
|
|
12
|
+
}
|
|
13
|
+
const isDisplay = match[1] !== void 0;
|
|
14
|
+
const mathContent = match[1] || match[2];
|
|
15
|
+
try {
|
|
16
|
+
const html = katex.renderToString(mathContent, {
|
|
17
|
+
displayMode: isDisplay,
|
|
18
|
+
throwOnError: false
|
|
19
|
+
});
|
|
20
|
+
parts.push(
|
|
21
|
+
/* @__PURE__ */ jsx(
|
|
22
|
+
"span",
|
|
23
|
+
{
|
|
24
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
25
|
+
},
|
|
26
|
+
key++
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
} catch {
|
|
30
|
+
parts.push(match[0]);
|
|
31
|
+
}
|
|
32
|
+
lastIndex = match.index + match[0].length;
|
|
33
|
+
}
|
|
34
|
+
if (lastIndex < text.length) {
|
|
35
|
+
parts.push(text.slice(lastIndex));
|
|
36
|
+
}
|
|
37
|
+
return parts.length === 1 && typeof parts[0] === "string" ? parts[0] : /* @__PURE__ */ jsx(Fragment, { children: parts });
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
renderMathInText
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=render-math-in-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-math-in-text.js","sources":["../../../../../src/components/gallery/lib/render-math-in-text.tsx"],"sourcesContent":["import { ReactNode } from \"react\";\nimport katex from \"katex\";\n\nexport function renderMathInText(text: string): ReactNode {\n // Match $...$ for inline math and $$...$$ for display math\n const parts: ReactNode[] = [];\n let lastIndex = 0;\n // Match display math ($$...$$) first, then inline math ($...$)\n const regex = /\\$\\$([^$]+)\\$\\$|\\$([^$]+)\\$/g;\n let match;\n let key = 0;\n\n while ((match = regex.exec(text)) !== null) {\n // Add text before this match\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n const isDisplay = match[1] !== undefined;\n const mathContent = match[1] || match[2];\n\n try {\n const html = katex.renderToString(mathContent, {\n displayMode: isDisplay,\n throwOnError: false,\n });\n parts.push(\n <span\n key={key++}\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n } catch {\n // If KaTeX fails, just show the original text\n parts.push(match[0]);\n }\n\n lastIndex = match.index + match[0].length;\n }\n\n // Add remaining text\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts.length === 1 && typeof parts[0] === 'string' ? parts[0] : <>{parts}</>;\n}\n"],"names":[],"mappings":";;AAGO,SAAS,iBAAiB,MAAyB;AAExD,QAAM,QAAqB,CAAA;AAC3B,MAAI,YAAY;AAEhB,QAAM,QAAQ;AACd,MAAI;AACJ,MAAI,MAAM;AAEV,UAAQ,QAAQ,MAAM,KAAK,IAAI,OAAO,MAAM;AAE1C,QAAI,MAAM,QAAQ,WAAW;AAC3B,YAAM,KAAK,KAAK,MAAM,WAAW,MAAM,KAAK,CAAC;AAAA,IAC/C;AAEA,UAAM,YAAY,MAAM,CAAC,MAAM;AAC/B,UAAM,cAAc,MAAM,CAAC,KAAK,MAAM,CAAC;AAEvC,QAAI;AACF,YAAM,OAAO,MAAM,eAAe,aAAa;AAAA,QAC7C,aAAa;AAAA,QACb,cAAc;AAAA,MAAA,CACf;AACD,YAAM;AAAA,QACJ;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,yBAAyB,EAAE,QAAQ,KAAA;AAAA,UAAK;AAAA,UADnC;AAAA,QAAA;AAAA,MAEP;AAAA,IAEJ,QAAQ;AAEN,YAAM,KAAK,MAAM,CAAC,CAAC;AAAA,IACrB;AAEA,gBAAY,MAAM,QAAQ,MAAM,CAAC,EAAE;AAAA,EACrC;AAGA,MAAI,YAAY,KAAK,QAAQ;AAC3B,UAAM,KAAK,KAAK,MAAM,SAAS,CAAC;AAAA,EAClC;AAEA,SAAO,MAAM,WAAW,KAAK,OAAO,MAAM,CAAC,MAAM,WAAW,MAAM,CAAC,IAAI,oBAAA,UAAA,EAAG,UAAA,OAAM;AAClF;"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
3
|
+
import { ModeToggle } from "./mode-toggle.js";
|
|
4
|
+
import { SiGithub } from "@icons-pack/react-simple-icons";
|
|
5
|
+
import { ChevronUp, ChevronDown } from "lucide-react";
|
|
6
|
+
function Header({ slideControls } = {}) {
|
|
7
|
+
return /* @__PURE__ */ jsx("header", { className: "print:hidden fixed top-0 left-0 right-0 z-40", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4", children: [
|
|
8
|
+
/* @__PURE__ */ jsx("nav", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(
|
|
9
|
+
Link,
|
|
10
|
+
{
|
|
11
|
+
to: "/",
|
|
12
|
+
className: "rounded-lg font-mono py-1.5 text-sm font-medium text-muted-foreground hover:underline",
|
|
13
|
+
children: "pl"
|
|
14
|
+
}
|
|
15
|
+
) }),
|
|
16
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
17
|
+
slideControls && /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-1", children: [
|
|
18
|
+
/* @__PURE__ */ jsx(
|
|
19
|
+
"button",
|
|
20
|
+
{
|
|
21
|
+
onClick: slideControls.onPrevious,
|
|
22
|
+
className: "p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200",
|
|
23
|
+
title: "Previous slide (↑)",
|
|
24
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4" })
|
|
25
|
+
}
|
|
26
|
+
),
|
|
27
|
+
/* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground/70 tabular-nums min-w-[3ch] text-center", children: [
|
|
28
|
+
slideControls.current + 1,
|
|
29
|
+
"/",
|
|
30
|
+
slideControls.total
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsx(
|
|
33
|
+
"button",
|
|
34
|
+
{
|
|
35
|
+
onClick: slideControls.onNext,
|
|
36
|
+
className: "p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200",
|
|
37
|
+
title: "Next slide (↓)",
|
|
38
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
] }),
|
|
42
|
+
/* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-2", children: [
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
Link,
|
|
45
|
+
{
|
|
46
|
+
to: "https://github.com/eoinmurray/pinglab",
|
|
47
|
+
target: "_blank",
|
|
48
|
+
className: "text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
|
|
49
|
+
"aria-label": "GitHub",
|
|
50
|
+
children: /* @__PURE__ */ jsx(SiGithub, { className: "h-4 w-4" })
|
|
51
|
+
}
|
|
52
|
+
),
|
|
53
|
+
/* @__PURE__ */ jsx(ModeToggle, {})
|
|
54
|
+
] })
|
|
55
|
+
] }) });
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
Header
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=header.js.map
|