veslx 0.1.14 → 0.1.16
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 +262 -55
- package/bin/lib/build.ts +65 -13
- package/bin/lib/import-config.ts +10 -9
- package/bin/lib/init.ts +21 -22
- package/bin/lib/serve.ts +66 -12
- package/bin/veslx.ts +2 -2
- package/dist/client/App.js +3 -9
- package/dist/client/App.js.map +1 -1
- package/dist/client/components/front-matter.js +11 -25
- package/dist/client/components/front-matter.js.map +1 -1
- package/dist/client/components/gallery/components/figure-caption.js +6 -4
- package/dist/client/components/gallery/components/figure-caption.js.map +1 -1
- package/dist/client/components/gallery/components/figure-header.js +3 -3
- package/dist/client/components/gallery/components/figure-header.js.map +1 -1
- package/dist/client/components/gallery/components/lightbox.js +13 -13
- package/dist/client/components/gallery/components/lightbox.js.map +1 -1
- package/dist/client/components/gallery/components/loading-image.js +11 -10
- package/dist/client/components/gallery/components/loading-image.js.map +1 -1
- package/dist/client/components/gallery/hooks/use-gallery-images.js +31 -7
- package/dist/client/components/gallery/hooks/use-gallery-images.js.map +1 -1
- package/dist/client/components/gallery/index.js +22 -15
- package/dist/client/components/gallery/index.js.map +1 -1
- package/dist/client/components/header.js +5 -3
- package/dist/client/components/header.js.map +1 -1
- package/dist/client/components/mdx-components.js +42 -8
- package/dist/client/components/mdx-components.js.map +1 -1
- package/dist/client/components/post-list.js +97 -90
- package/dist/client/components/post-list.js.map +1 -1
- package/dist/client/components/running-bar.js +1 -1
- package/dist/client/components/running-bar.js.map +1 -1
- package/dist/client/components/slide.js +18 -0
- package/dist/client/components/slide.js.map +1 -0
- package/dist/client/components/slides-renderer.js +7 -71
- package/dist/client/components/slides-renderer.js.map +1 -1
- package/dist/client/hooks/use-mdx-content.js +55 -9
- package/dist/client/hooks/use-mdx-content.js.map +1 -1
- package/dist/client/main.js +1 -0
- package/dist/client/main.js.map +1 -1
- package/dist/client/pages/content-router.js +19 -0
- package/dist/client/pages/content-router.js.map +1 -0
- package/dist/client/pages/home.js +11 -7
- package/dist/client/pages/home.js.map +1 -1
- package/dist/client/pages/post.js +8 -20
- package/dist/client/pages/post.js.map +1 -1
- package/dist/client/pages/slides.js +62 -86
- package/dist/client/pages/slides.js.map +1 -1
- package/dist/client/plugin/src/client.js +58 -96
- package/dist/client/plugin/src/client.js.map +1 -1
- package/dist/client/plugin/src/directory-tree.js +111 -0
- package/dist/client/plugin/src/directory-tree.js.map +1 -0
- package/index.html +1 -1
- package/package.json +27 -15
- package/plugin/src/client.tsx +64 -116
- package/plugin/src/directory-tree.ts +171 -0
- package/plugin/src/lib.ts +6 -249
- package/plugin/src/plugin.ts +93 -50
- package/plugin/src/remark-slides.ts +100 -0
- package/plugin/src/types.ts +22 -0
- package/src/App.tsx +3 -6
- package/src/components/front-matter.tsx +14 -29
- package/src/components/gallery/components/figure-caption.tsx +15 -7
- package/src/components/gallery/components/figure-header.tsx +3 -3
- package/src/components/gallery/components/lightbox.tsx +15 -13
- package/src/components/gallery/components/loading-image.tsx +15 -12
- package/src/components/gallery/hooks/use-gallery-images.ts +51 -10
- package/src/components/gallery/index.tsx +32 -26
- package/src/components/header.tsx +14 -9
- package/src/components/mdx-components.tsx +61 -8
- package/src/components/post-list.tsx +149 -115
- package/src/components/running-bar.tsx +1 -1
- package/src/components/slide.tsx +22 -5
- package/src/components/slides-renderer.tsx +7 -115
- package/src/components/welcome.tsx +11 -14
- package/src/hooks/use-mdx-content.ts +94 -9
- package/src/index.css +159 -0
- package/src/main.tsx +1 -0
- package/src/pages/content-router.tsx +27 -0
- package/src/pages/home.tsx +16 -2
- package/src/pages/post.tsx +10 -13
- package/src/pages/slides.tsx +75 -88
- package/src/vite-env.d.ts +7 -17
- package/vite.config.ts +31 -6
package/bin/veslx.ts
CHANGED
|
@@ -21,7 +21,7 @@ cli
|
|
|
21
21
|
.action(init)
|
|
22
22
|
|
|
23
23
|
cli
|
|
24
|
-
.command("serve", "Start the veslx server")
|
|
24
|
+
.command("serve [dir]", "Start the veslx server")
|
|
25
25
|
.action(serve);
|
|
26
26
|
|
|
27
27
|
cli
|
|
@@ -33,7 +33,7 @@ cli
|
|
|
33
33
|
.action(stop);
|
|
34
34
|
|
|
35
35
|
cli
|
|
36
|
-
.command("build", "Build the veslx app")
|
|
36
|
+
.command("build [dir]", "Build the veslx app")
|
|
37
37
|
.action(build)
|
|
38
38
|
|
|
39
39
|
cli.help();
|
package/dist/client/App.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { jsx
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
3
3
|
import { ThemeProvider } from "./components/theme-provider.js";
|
|
4
|
-
import {
|
|
5
|
-
import { Post } from "./pages/post.js";
|
|
6
|
-
import { SlidesPage } from "./pages/slides.js";
|
|
4
|
+
import { ContentRouter } from "./pages/content-router.js";
|
|
7
5
|
function App() {
|
|
8
|
-
return /* @__PURE__ */ jsx(ThemeProvider, { attribute: "class", defaultTheme: "system", enableSystem: true, children: /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */
|
|
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
|
-
] }) }) });
|
|
6
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { attribute: "class", defaultTheme: "system", enableSystem: true, children: /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsx(Routes, { children: /* @__PURE__ */ jsx(Route, { path: "/*", element: /* @__PURE__ */ jsx(ContentRouter, {}) }) }) }) });
|
|
13
7
|
}
|
|
14
8
|
export {
|
|
15
9
|
App as default
|
package/dist/client/App.js.map
CHANGED
|
@@ -1 +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 {
|
|
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 { ContentRouter } from \"./pages/content-router\"\n\nfunction App() {\n return (\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n <BrowserRouter>\n <Routes>\n {/* Single catch-all route - ContentRouter determines page type */}\n <Route path=\"/*\" element={<ContentRouter />} />\n </Routes>\n </BrowserRouter>\n </ThemeProvider>\n )\n}\n\nexport default App\n"],"names":[],"mappings":";;;;AAIA,SAAS,MAAM;AACb,SACE,oBAAC,iBAAc,WAAU,SAAQ,cAAa,UAAS,cAAY,MACjE,UAAA,oBAAC,eAAA,EACG,UAAA,oBAAC,UAEC,UAAA,oBAAC,OAAA,EAAM,MAAK,MAAK,6BAAU,eAAA,CAAA,CAAc,EAAA,CAAI,EAAA,CAC/C,EAAA,CACJ,EAAA,CACF;AAEJ;"}
|
|
@@ -1,30 +1,16 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMDXContent, useMDXSlides } from "../hooks/use-mdx-content.js";
|
|
2
3
|
import { formatDate } from "../lib/format-date.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
/* @__PURE__ */ jsx("
|
|
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 })
|
|
4
|
+
import { useParams } from "react-router-dom";
|
|
5
|
+
function FrontMatter() {
|
|
6
|
+
const { "path": path = "." } = useParams();
|
|
7
|
+
const { frontmatter: readmeFm } = useMDXContent(path);
|
|
8
|
+
const { frontmatter: slidesFm } = useMDXSlides(path);
|
|
9
|
+
let frontmatter = readmeFm || slidesFm;
|
|
10
|
+
return /* @__PURE__ */ jsx("div", { children: (frontmatter == null ? void 0 : frontmatter.title) && /* @__PURE__ */ jsxs("header", { className: "not-prose flex flex-col gap-2 mb-8 pt-4", children: [
|
|
11
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3", children: frontmatter == null ? void 0 : frontmatter.title }),
|
|
12
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-3 text-muted-foreground", children: (frontmatter == null ? void 0 : frontmatter.date) && /* @__PURE__ */ jsx("time", { className: "font-mono text-xs bg-muted px-2 py-0.5 rounded", children: formatDate(new Date(frontmatter.date)) }) }),
|
|
13
|
+
(frontmatter == null ? void 0 : frontmatter.description) && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap text-sm items-center gap-3 text-muted-foreground", children: frontmatter == null ? void 0 : frontmatter.description })
|
|
28
14
|
] }) });
|
|
29
15
|
}
|
|
30
16
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import { useMDXContent, useMDXSlides } from \"@/hooks/use-mdx-content\";\nimport { formatDate } from \"@/lib/format-date\"\nimport { useParams } from \"react-router-dom\"\n\nexport function FrontMatter(){\n const { \"path\": path = \".\" } = useParams();\n const { frontmatter: readmeFm } = useMDXContent(path);\n const { frontmatter: slidesFm } = useMDXSlides(path);\n\n let frontmatter = readmeFm || slidesFm;\n\n return (\n <div>\n {frontmatter?.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 {frontmatter?.title}\n </h1>\n\n {/* Meta line */}\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n {frontmatter?.date && (\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(frontmatter.date as string))}\n </time>\n )}\n </div>\n\n {frontmatter?.description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {frontmatter?.description}\n </div>\n )}\n </header>\n )}\n </div>\n )\n}"],"names":[],"mappings":";;;;AAIO,SAAS,cAAa;AAC3B,QAAM,EAAE,QAAQ,OAAO,IAAA,IAAQ,UAAA;AAC/B,QAAM,EAAE,aAAa,aAAa,cAAc,IAAI;AACpD,QAAM,EAAE,aAAa,aAAa,aAAa,IAAI;AAEnD,MAAI,cAAc,YAAY;AAE9B,6BACG,OAAA,EACE,WAAA,2CAAa,UACZ,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,2CAAa,OAChB;AAAA,wBAGC,OAAA,EAAI,WAAU,2DACZ,WAAA,2CAAa,SACZ,oBAAC,QAAA,EAAK,WAAU,kDACb,qBAAW,IAAI,KAAK,YAAY,IAAc,CAAC,GAClD,GAEJ;AAAA,KAEC,2CAAa,gBACZ,oBAAC,SAAI,WAAU,mEACZ,qDAAa,YAAA,CAChB;AAAA,EAAA,EAAA,CAEJ,EAAA,CAEJ;AAEJ;"}
|
|
@@ -2,10 +2,12 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { renderMathInText } from "../lib/render-math-in-text.js";
|
|
3
3
|
function FigureCaption({ caption, label }) {
|
|
4
4
|
if (!caption && !label) return null;
|
|
5
|
-
return /* @__PURE__ */ jsx("
|
|
6
|
-
label && /* @__PURE__ */
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
return /* @__PURE__ */ jsx("figcaption", { className: "px-[calc((var(--gallery-width)-var(--content-width))/2)] mt-4", children: /* @__PURE__ */ jsxs("p", { className: "text-[13px] leading-[1.6] text-muted-foreground", children: [
|
|
6
|
+
label && /* @__PURE__ */ jsxs("span", { className: "font-semibold text-foreground tracking-tight", children: [
|
|
7
|
+
label,
|
|
8
|
+
caption && /* @__PURE__ */ jsx("span", { className: "font-normal mx-1.5", children: "·" })
|
|
9
|
+
] }),
|
|
10
|
+
caption && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/90", children: renderMathInText(caption) })
|
|
9
11
|
] }) });
|
|
10
12
|
}
|
|
11
13
|
export {
|
|
@@ -1 +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 <
|
|
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 <figcaption className=\"px-[calc((var(--gallery-width)-var(--content-width))/2)] mt-4\">\n <p className=\"text-[13px] leading-[1.6] text-muted-foreground\">\n {label && (\n <span className=\"font-semibold text-foreground tracking-tight\">\n {label}\n {caption && <span className=\"font-normal mx-1.5\">·</span>}\n </span>\n )}\n {caption && (\n <span className=\"text-muted-foreground/90\">\n {renderMathInText(caption)}\n </span>\n )}\n </p>\n </figcaption>\n );\n}\n"],"names":[],"mappings":";;AAEO,SAAS,cAAc,EAAE,SAAS,SAA+C;AACtF,MAAI,CAAC,WAAW,CAAC,MAAO,QAAO;AAE/B,6BACG,cAAA,EAAW,WAAU,iEACpB,UAAA,qBAAC,KAAA,EAAE,WAAU,mDACV,UAAA;AAAA,IAAA,SACC,qBAAC,QAAA,EAAK,WAAU,gDACb,UAAA;AAAA,MAAA;AAAA,MACA,WAAW,oBAAC,QAAA,EAAK,WAAU,sBAAqB,UAAA,IAAA,CAAC;AAAA,IAAA,GACpD;AAAA,IAED,WACC,oBAAC,QAAA,EAAK,WAAU,4BACb,UAAA,iBAAiB,OAAO,EAAA,CAC3B;AAAA,EAAA,EAAA,CAEJ,EAAA,CACF;AAEJ;"}
|
|
@@ -2,9 +2,9 @@ import { jsxs, jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { renderMathInText } from "../lib/render-math-in-text.js";
|
|
3
3
|
function FigureHeader({ title, subtitle }) {
|
|
4
4
|
if (!title && !subtitle) return null;
|
|
5
|
-
return /* @__PURE__ */ jsxs("div", { className: "
|
|
6
|
-
title && /* @__PURE__ */ jsx("h3", { className: "text-
|
|
7
|
-
subtitle && /* @__PURE__ */ jsx("p", { className: "text-
|
|
5
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-[calc((var(--gallery-width)-var(--content-width))/2)] mb-4", children: [
|
|
6
|
+
title && /* @__PURE__ */ jsx("h3", { className: "text-[15px] font-medium tracking-[-0.01em] text-foreground", children: renderMathInText(title) }),
|
|
7
|
+
subtitle && /* @__PURE__ */ jsx("p", { className: "text-[13px] text-muted-foreground/80 leading-relaxed mt-1", children: renderMathInText(subtitle) })
|
|
8
8
|
] });
|
|
9
9
|
}
|
|
10
10
|
export {
|
|
@@ -1 +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=\"
|
|
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=\"px-[calc((var(--gallery-width)-var(--content-width))/2)] mb-4\">\n {title && (\n <h3 className=\"text-[15px] font-medium tracking-[-0.01em] text-foreground\">\n {renderMathInText(title)}\n </h3>\n )}\n {subtitle && (\n <p className=\"text-[13px] text-muted-foreground/80 leading-relaxed 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,iEACZ,UAAA;AAAA,IAAA,6BACE,MAAA,EAAG,WAAU,8DACX,UAAA,iBAAiB,KAAK,GACzB;AAAA,IAED,YACC,oBAAC,KAAA,EAAE,WAAU,6DACV,UAAA,iBAAiB,QAAQ,EAAA,CAC5B;AAAA,EAAA,GAEJ;AAEJ;"}
|
|
@@ -15,7 +15,7 @@ function Lightbox({
|
|
|
15
15
|
/* @__PURE__ */ jsxs(
|
|
16
16
|
"div",
|
|
17
17
|
{
|
|
18
|
-
className: "fixed inset-0 z-[9999] bg-background",
|
|
18
|
+
className: "fixed inset-0 z-[9999] bg-background/98 backdrop-blur-md animate-fade-in-slow",
|
|
19
19
|
onClick: onClose,
|
|
20
20
|
...{ [FULLSCREEN_DATA_ATTR]: "true" },
|
|
21
21
|
style: { top: 0, left: 0, right: 0, bottom: 0 },
|
|
@@ -23,21 +23,21 @@ function Lightbox({
|
|
|
23
23
|
/* @__PURE__ */ jsxs(
|
|
24
24
|
"div",
|
|
25
25
|
{
|
|
26
|
-
className: "fixed top-0 left-0 right-0 z-10 flex items-center justify-between px-
|
|
26
|
+
className: "fixed top-0 left-0 right-0 z-10 flex items-center justify-between px-6 py-4",
|
|
27
27
|
onClick: (e) => e.stopPropagation(),
|
|
28
28
|
children: [
|
|
29
|
-
/* @__PURE__ */ jsxs("div", { className: "font-mono text-
|
|
29
|
+
/* @__PURE__ */ jsxs("div", { className: "font-mono text-[11px] text-muted-foreground/60 tabular-nums tracking-wider uppercase", children: [
|
|
30
30
|
String(selectedIndex + 1).padStart(2, "0"),
|
|
31
|
-
" / ",
|
|
31
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1.5 text-muted-foreground/30", children: "/" }),
|
|
32
32
|
String(images.length).padStart(2, "0")
|
|
33
33
|
] }),
|
|
34
34
|
/* @__PURE__ */ jsx(
|
|
35
35
|
"button",
|
|
36
36
|
{
|
|
37
37
|
onClick: onClose,
|
|
38
|
-
className: "p-2 text-muted-foreground hover:text-foreground transition-colors",
|
|
38
|
+
className: "p-2 -m-2 text-muted-foreground/50 hover:text-foreground transition-colors duration-200",
|
|
39
39
|
"aria-label": "Close",
|
|
40
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-
|
|
40
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", strokeWidth: 1.5 })
|
|
41
41
|
}
|
|
42
42
|
)
|
|
43
43
|
]
|
|
@@ -50,9 +50,9 @@ function Lightbox({
|
|
|
50
50
|
e.stopPropagation();
|
|
51
51
|
onPrevious();
|
|
52
52
|
},
|
|
53
|
-
className: "fixed left-
|
|
53
|
+
className: "fixed left-6 top-1/2 -translate-y-1/2 z-10 p-3 -m-3 text-muted-foreground/40 hover:text-foreground transition-colors duration-200",
|
|
54
54
|
"aria-label": "Previous image",
|
|
55
|
-
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-
|
|
55
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-6 w-6", strokeWidth: 1.5 })
|
|
56
56
|
}
|
|
57
57
|
),
|
|
58
58
|
showNavigation && selectedIndex < images.length - 1 && /* @__PURE__ */ jsx(
|
|
@@ -62,9 +62,9 @@ function Lightbox({
|
|
|
62
62
|
e.stopPropagation();
|
|
63
63
|
onNext();
|
|
64
64
|
},
|
|
65
|
-
className: "fixed right-
|
|
65
|
+
className: "fixed right-6 top-1/2 -translate-y-1/2 z-10 p-3 -m-3 text-muted-foreground/40 hover:text-foreground transition-colors duration-200",
|
|
66
66
|
"aria-label": "Next image",
|
|
67
|
-
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-
|
|
67
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-6 w-6", strokeWidth: 1.5 })
|
|
68
68
|
}
|
|
69
69
|
),
|
|
70
70
|
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-16", children: /* @__PURE__ */ jsx(
|
|
@@ -72,16 +72,16 @@ function Lightbox({
|
|
|
72
72
|
{
|
|
73
73
|
src: current.src,
|
|
74
74
|
alt: current.label,
|
|
75
|
-
className: "max-w-full max-h-full object-contain",
|
|
75
|
+
className: "max-w-full max-h-full object-contain rounded-sm shadow-2xl",
|
|
76
76
|
onClick: (e) => e.stopPropagation()
|
|
77
77
|
}
|
|
78
78
|
) }),
|
|
79
79
|
/* @__PURE__ */ jsx(
|
|
80
80
|
"div",
|
|
81
81
|
{
|
|
82
|
-
className: "fixed bottom-0 left-0 right-0 z-10
|
|
82
|
+
className: "fixed bottom-0 left-0 right-0 z-10 px-6 py-5 text-center",
|
|
83
83
|
onClick: (e) => e.stopPropagation(),
|
|
84
|
-
children: /* @__PURE__ */ jsx("span", { className: "font-mono text-
|
|
84
|
+
children: /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-muted-foreground/50 tracking-wide", children: current.label })
|
|
85
85
|
}
|
|
86
86
|
)
|
|
87
87
|
]
|
|
@@ -1 +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-
|
|
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/98 backdrop-blur-md animate-fade-in-slow\"\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-6 py-4\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"font-mono text-[11px] text-muted-foreground/60 tabular-nums tracking-wider uppercase\">\n {String(selectedIndex + 1).padStart(2, '0')}\n <span className=\"mx-1.5 text-muted-foreground/30\">/</span>\n {String(images.length).padStart(2, '0')}\n </div>\n <button\n onClick={onClose}\n className=\"p-2 -m-2 text-muted-foreground/50 hover:text-foreground transition-colors duration-200\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4\" strokeWidth={1.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-6 top-1/2 -translate-y-1/2 z-10 p-3 -m-3 text-muted-foreground/40 hover:text-foreground transition-colors duration-200\"\n aria-label=\"Previous image\"\n >\n <ChevronLeft className=\"h-6 w-6\" strokeWidth={1.5} />\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-6 top-1/2 -translate-y-1/2 z-10 p-3 -m-3 text-muted-foreground/40 hover:text-foreground transition-colors duration-200\"\n aria-label=\"Next image\"\n >\n <ChevronRight className=\"h-6 w-6\" strokeWidth={1.5} />\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 rounded-sm shadow-2xl\"\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n\n {/* Caption */}\n <div\n className=\"fixed bottom-0 left-0 right-0 z-10 px-6 py-5 text-center\"\n onClick={(e) => e.stopPropagation()}\n >\n <span className=\"font-mono text-[11px] text-muted-foreground/50 tracking-wide\">\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,wFACZ,UAAA;AAAA,kBAAA,OAAO,gBAAgB,CAAC,EAAE,SAAS,GAAG,GAAG;AAAA,kBAC1C,oBAAC,QAAA,EAAK,WAAU,mCAAkC,UAAA,KAAC;AAAA,kBAClD,OAAO,OAAO,MAAM,EAAE,SAAS,GAAG,GAAG;AAAA,gBAAA,GACxC;AAAA,gBACA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,SAAS;AAAA,oBACT,WAAU;AAAA,oBACV,cAAW;AAAA,oBAEX,UAAA,oBAAC,GAAA,EAAE,WAAU,WAAU,aAAa,IAAA,CAAK;AAAA,kBAAA;AAAA,gBAAA;AAAA,cAC3C;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,WAAU,aAAa,IAAA,CAAK;AAAA,YAAA;AAAA,UAAA;AAAA,UAKtD,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,WAAU,aAAa,IAAA,CAAK;AAAA,YAAA;AAAA,UAAA;AAAA,UAKxD,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,gEACb,kBAAQ,MAAA,CACX;AAAA,YAAA;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,SAAS;AAAA,EAAA;AAEb;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { ImageOff } from "lucide-react";
|
|
4
4
|
import { cn } from "../../../lib/utils.js";
|
|
5
5
|
function LoadingImage({
|
|
6
6
|
className,
|
|
@@ -9,21 +9,22 @@ function LoadingImage({
|
|
|
9
9
|
}) {
|
|
10
10
|
const [isLoading, setIsLoading] = useState(true);
|
|
11
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
|
|
14
|
-
hasError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-muted/
|
|
15
|
-
/* @__PURE__ */ jsx(
|
|
16
|
-
/* @__PURE__ */ jsx("span", { className: "text-
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative overflow-hidden rounded-sm bg-muted/20", wrapperClassName), children: [
|
|
13
|
+
isLoading && !hasError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-r from-transparent via-muted/40 to-transparent animate-shimmer" }) }),
|
|
14
|
+
hasError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-muted/10 flex items-center justify-center backdrop-blur-sm", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-1", children: [
|
|
15
|
+
/* @__PURE__ */ jsx(ImageOff, { className: "h-4 w-4 text-muted-foreground/30 mx-auto", strokeWidth: 1.5 }),
|
|
16
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/30 block font-mono uppercase tracking-wider", children: "unavailable" })
|
|
17
17
|
] }) }),
|
|
18
18
|
/* @__PURE__ */ jsx(
|
|
19
19
|
"img",
|
|
20
20
|
{
|
|
21
21
|
...props,
|
|
22
22
|
className: cn(
|
|
23
|
-
|
|
24
|
-
"transition-
|
|
25
|
-
isLoading
|
|
26
|
-
hasError && "opacity-0"
|
|
23
|
+
"w-full h-full",
|
|
24
|
+
"transition-all duration-500 ease-out",
|
|
25
|
+
isLoading ? "opacity-0 scale-[1.02]" : "opacity-100 scale-100",
|
|
26
|
+
hasError && "opacity-0",
|
|
27
|
+
className
|
|
27
28
|
),
|
|
28
29
|
onLoad: (e) => {
|
|
29
30
|
var _a;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loading-image.js","sources":["../../../../../src/components/gallery/components/loading-image.tsx"],"sourcesContent":["import { useState, ImgHTMLAttributes } from \"react\";\nimport {
|
|
1
|
+
{"version":3,"file":"loading-image.js","sources":["../../../../../src/components/gallery/components/loading-image.tsx"],"sourcesContent":["import { useState, ImgHTMLAttributes } from \"react\";\nimport { ImageOff } 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 overflow-hidden rounded-sm bg-muted/20\", wrapperClassName)}>\n {isLoading && !hasError && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/40 to-transparent animate-shimmer\" />\n </div>\n )}\n {hasError && (\n <div className=\"absolute inset-0 bg-muted/10 flex items-center justify-center backdrop-blur-sm\">\n <div className=\"text-center space-y-1\">\n <ImageOff className=\"h-4 w-4 text-muted-foreground/30 mx-auto\" strokeWidth={1.5} />\n <span className=\"text-[10px] text-muted-foreground/30 block font-mono uppercase tracking-wider\">\n unavailable\n </span>\n </div>\n </div>\n )}\n <img\n {...props}\n className={cn(\n \"w-full h-full\",\n \"transition-all duration-500 ease-out\",\n isLoading ? \"opacity-0 scale-[1.02]\" : \"opacity-100 scale-100\",\n hasError && \"opacity-0\",\n className\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,mDAAmD,gBAAgB,GACnF,UAAA;AAAA,IAAA,aAAa,CAAC,YACb,oBAAC,OAAA,EAAI,WAAU,qDACb,UAAA,oBAAC,OAAA,EAAI,WAAU,iGAAA,CAAiG,EAAA,CAClH;AAAA,IAED,gCACE,OAAA,EAAI,WAAU,kFACb,UAAA,qBAAC,OAAA,EAAI,WAAU,yBACb,UAAA;AAAA,MAAA,oBAAC,UAAA,EAAS,WAAU,4CAA2C,aAAa,KAAK;AAAA,MACjF,oBAAC,QAAA,EAAK,WAAU,iFAAgF,UAAA,cAAA,CAEhG;AAAA,IAAA,EAAA,CACF,EAAA,CACF;AAAA,IAEF;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,YAAY,2BAA2B;AAAA,UACvC,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAEF,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;"}
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { useTheme } from "next-themes";
|
|
3
|
+
import { useParams } from "react-router-dom";
|
|
3
4
|
import { useDirectory } from "../../../plugin/src/client.js";
|
|
4
5
|
import { minimatch } from "minimatch";
|
|
6
|
+
function collectAllImages(entry) {
|
|
7
|
+
if (entry.type === "file") {
|
|
8
|
+
if (entry.name.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)) {
|
|
9
|
+
return [entry];
|
|
10
|
+
}
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
const images = [];
|
|
14
|
+
for (const child of entry.children || []) {
|
|
15
|
+
images.push(...collectAllImages(child));
|
|
16
|
+
}
|
|
17
|
+
return images;
|
|
18
|
+
}
|
|
5
19
|
function sortPathsNumerically(paths) {
|
|
6
20
|
paths.sort((a, b) => {
|
|
7
21
|
const nums = (s) => (s.match(/\d+/g) || []).map(Number);
|
|
@@ -52,18 +66,28 @@ function useGalleryImages({
|
|
|
52
66
|
page = 0
|
|
53
67
|
}) {
|
|
54
68
|
const { resolvedTheme } = useTheme();
|
|
69
|
+
const { "*": routePath = "" } = useParams();
|
|
70
|
+
const currentDir = routePath.replace(/\/[^/]+\.mdx$/i, "").replace(/\/$/, "") || ".";
|
|
55
71
|
let resolvedPath = path;
|
|
56
|
-
|
|
72
|
+
if (path == null ? void 0 : path.startsWith("./")) {
|
|
73
|
+
const relativePart = path.slice(2);
|
|
74
|
+
resolvedPath = currentDir === "." ? relativePart : `${currentDir}/${relativePart}`;
|
|
75
|
+
} else if (path && !path.startsWith("/") && !path.includes("/")) {
|
|
76
|
+
resolvedPath = currentDir === "." ? path : `${currentDir}/${path}`;
|
|
77
|
+
}
|
|
78
|
+
const directoryPath = resolvedPath || ".";
|
|
79
|
+
const { directory } = useDirectory(directoryPath);
|
|
57
80
|
const paths = useMemo(() => {
|
|
58
81
|
if (!directory) return [];
|
|
59
|
-
|
|
60
|
-
return !!child.name.match(/\.(png|jpeg|gif|svg|webp)$/i) && child.type === "file";
|
|
61
|
-
});
|
|
62
|
-
let imagePaths = imageChildren.map((child) => child.path);
|
|
82
|
+
let imagePaths;
|
|
63
83
|
if (globs && globs.length > 0) {
|
|
64
|
-
|
|
65
|
-
|
|
84
|
+
const allImages = collectAllImages(directory);
|
|
85
|
+
imagePaths = allImages.map((img) => img.path).filter((p) => globs.some((glob) => minimatch(p, glob, { matchBase: true })));
|
|
86
|
+
} else {
|
|
87
|
+
const imageChildren = directory.children.filter((child) => {
|
|
88
|
+
return !!child.name.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i) && child.type === "file";
|
|
66
89
|
});
|
|
90
|
+
imagePaths = imageChildren.map((child) => child.path);
|
|
67
91
|
}
|
|
68
92
|
sortPathsNumerically(imagePaths);
|
|
69
93
|
let filtered = filterPathsByTheme(imagePaths, resolvedTheme);
|
|
@@ -1 +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(
|
|
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 { useParams } from \"react-router-dom\";\nimport { useDirectory } from \"../../../../plugin/src/client\";\nimport { FileEntry, DirectoryEntry } from \"../../../../plugin/src/lib\";\nimport { minimatch } from \"minimatch\";\n\n// Recursively collect all image files from a directory tree\nfunction collectAllImages(entry: DirectoryEntry | FileEntry): FileEntry[] {\n if (entry.type === \"file\") {\n if (entry.name.match(/\\.(png|jpg|jpeg|gif|svg|webp)$/i)) {\n return [entry];\n }\n return [];\n }\n // It's a directory - recurse into children\n const images: FileEntry[] = [];\n for (const child of entry.children || []) {\n images.push(...collectAllImages(child));\n }\n return images;\n}\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 const { \"*\": routePath = \"\" } = useParams();\n\n // Get the current post's directory from the route\n // Route is like \"04-components/README.mdx\" -> \"04-components\"\n // Or \"gallery-examples\" -> \"gallery-examples\"\n const currentDir = routePath\n .replace(/\\/[^/]+\\.mdx$/i, \"\") // Remove /filename.mdx\n .replace(/\\/$/, \"\") // Remove trailing slash\n || \".\";\n\n // Resolve the path relative to current directory\n let resolvedPath = path;\n if (path?.startsWith(\"./\")) {\n // Relative path like \"./images\" -> \"gallery-examples/images\"\n const relativePart = path.slice(2);\n resolvedPath = currentDir === \".\" ? relativePart : `${currentDir}/${relativePart}`;\n } else if (path && !path.startsWith(\"/\") && !path.includes(\"/\")) {\n // Simple name like \"images\" -> \"gallery-examples/images\"\n resolvedPath = currentDir === \".\" ? path : `${currentDir}/${path}`;\n }\n\n // If only globs provided (no path), use root directory\n const directoryPath = resolvedPath || \".\";\n const { directory } = useDirectory(directoryPath);\n\n const paths = useMemo(() => {\n if (!directory) return [];\n\n let imagePaths: string[];\n\n if (globs && globs.length > 0) {\n // When globs provided, collect all images recursively and match against filename\n const allImages = collectAllImages(directory);\n imagePaths = allImages\n .map(img => img.path)\n .filter(p => globs.some(glob => minimatch(p, glob, { matchBase: true })));\n } else {\n // No globs - just get images from the specified directory\n const imageChildren = directory.children.filter((child): child is FileEntry => {\n return !!child.name.match(/\\.(png|jpg|jpeg|gif|svg|webp)$/i) && child.type === \"file\";\n });\n imagePaths = imageChildren.map(child => child.path);\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":"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"}
|
|
@@ -37,43 +37,50 @@ function Gallery({
|
|
|
37
37
|
[paths]
|
|
38
38
|
);
|
|
39
39
|
if (isLoading) {
|
|
40
|
-
return /* @__PURE__ */ jsx("
|
|
40
|
+
return /* @__PURE__ */ jsx("figure", { className: "not-prose py-6 md:py-8", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx(
|
|
41
41
|
"div",
|
|
42
42
|
{
|
|
43
|
-
className: "aspect-
|
|
44
|
-
|
|
43
|
+
className: "aspect-square rounded-sm bg-muted/20 relative overflow-hidden",
|
|
44
|
+
children: /* @__PURE__ */ jsx(
|
|
45
|
+
"div",
|
|
46
|
+
{
|
|
47
|
+
className: "absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer",
|
|
48
|
+
style: { animationDelay: `${i * 150}ms` }
|
|
49
|
+
}
|
|
50
|
+
)
|
|
45
51
|
},
|
|
46
52
|
i
|
|
47
53
|
)) }) });
|
|
48
54
|
}
|
|
49
55
|
if (isEmpty) {
|
|
50
|
-
return /* @__PURE__ */ jsx("
|
|
51
|
-
/* @__PURE__ */ jsx(Image, { className: "h-
|
|
52
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono text-
|
|
56
|
+
return /* @__PURE__ */ jsx("figure", { className: "not-prose py-12 text-center", children: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2.5 text-muted-foreground/40", children: [
|
|
57
|
+
/* @__PURE__ */ jsx(Image, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }),
|
|
58
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs uppercase tracking-widest", children: "No images" })
|
|
53
59
|
] }) });
|
|
54
60
|
}
|
|
55
61
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
56
|
-
/* @__PURE__ */ jsxs("
|
|
62
|
+
/* @__PURE__ */ jsxs("figure", { className: "not-prose relative py-6 md:py-8 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]", children: [
|
|
57
63
|
/* @__PURE__ */ jsx(FigureHeader, { title, subtitle }),
|
|
58
|
-
/* @__PURE__ */ jsxs(Carousel, { children: [
|
|
59
|
-
/* @__PURE__ */ jsx(CarouselContent, { children: images.map((img, index) => /* @__PURE__ */ jsx(
|
|
64
|
+
/* @__PURE__ */ jsxs(Carousel, { className: "w-full", children: [
|
|
65
|
+
/* @__PURE__ */ jsx(CarouselContent, { className: `-ml-2 md:-ml-3 ${images.length < 3 ? "justify-center" : ""}`, children: images.map((img, index) => /* @__PURE__ */ jsx(
|
|
60
66
|
CarouselItem,
|
|
61
67
|
{
|
|
62
|
-
className:
|
|
68
|
+
className: `pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group ${images.length < 3 ? "flex-none" : ""}`,
|
|
63
69
|
onClick: () => lightbox.open(index),
|
|
64
|
-
children: /* @__PURE__ */ jsx(
|
|
70
|
+
children: /* @__PURE__ */ jsx("div", { className: "aspect-square overflow-hidden rounded-sm ring-1 ring-border/50 transition-all duration-300 group-hover:ring-border group-hover:shadow-lg bg-muted/10", children: /* @__PURE__ */ jsx(
|
|
65
71
|
LoadingImage,
|
|
66
72
|
{
|
|
67
73
|
src: img.src,
|
|
68
|
-
alt: img.label
|
|
74
|
+
alt: img.label,
|
|
75
|
+
className: "object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
|
|
69
76
|
}
|
|
70
|
-
)
|
|
77
|
+
) })
|
|
71
78
|
},
|
|
72
79
|
index
|
|
73
80
|
)) }),
|
|
74
81
|
images.length > 3 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
75
|
-
/* @__PURE__ */ jsx(CarouselPrevious, {}),
|
|
76
|
-
/* @__PURE__ */ jsx(CarouselNext, {})
|
|
82
|
+
/* @__PURE__ */ jsx(CarouselPrevious, { className: "left-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" }),
|
|
83
|
+
/* @__PURE__ */ jsx(CarouselNext, { className: "right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" })
|
|
77
84
|
] })
|
|
78
85
|
] }),
|
|
79
86
|
/* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
|
|
@@ -1 +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 <
|
|
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 <figure className=\"not-prose py-6 md:py-8\">\n <div className=\"grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-square rounded-sm bg-muted/20 relative overflow-hidden\"\n >\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer\"\n style={{ animationDelay: `${i * 150}ms` }}\n />\n </div>\n ))}\n </div>\n </figure>\n );\n }\n\n if (isEmpty) {\n return (\n <figure className=\"not-prose py-12 text-center\">\n <div className=\"inline-flex items-center gap-2.5 text-muted-foreground/40\">\n <Image className=\"h-3.5 w-3.5\" strokeWidth={1.5} />\n <span className=\"font-mono text-xs uppercase tracking-widest\">No images</span>\n </div>\n </figure>\n );\n }\n\n return (\n <>\n <figure className=\"not-prose relative py-6 md:py-8 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]\">\n <FigureHeader title={title} subtitle={subtitle} />\n\n <Carousel className=\"w-full\">\n <CarouselContent className={`-ml-2 md:-ml-3 ${images.length < 3 ? 'justify-center' : ''}`}>\n {images.map((img, index) => (\n <CarouselItem\n key={index}\n className={`pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group ${images.length < 3 ? 'flex-none' : ''}`}\n onClick={() => lightbox.open(index)}\n >\n <div className=\"aspect-square overflow-hidden rounded-sm ring-1 ring-border/50 transition-all duration-300 group-hover:ring-border group-hover:shadow-lg bg-muted/10\">\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n\n {images.length > 3 && (\n <>\n <CarouselPrevious className=\"left-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border\" />\n <CarouselNext className=\"right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border\" />\n </>\n )}\n </Carousel>\n\n <FigureCaption caption={caption} label={captionLabel} />\n </figure>\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":"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"}
|
|
@@ -3,14 +3,16 @@ import { Link } from "react-router-dom";
|
|
|
3
3
|
import { ModeToggle } from "./mode-toggle.js";
|
|
4
4
|
import { SiGithub } from "@icons-pack/react-simple-icons";
|
|
5
5
|
import { ChevronUp, ChevronDown } from "lucide-react";
|
|
6
|
+
import siteConfig from "virtual:veslx-config";
|
|
6
7
|
function Header({ slideControls } = {}) {
|
|
8
|
+
const config = siteConfig;
|
|
7
9
|
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
10
|
/* @__PURE__ */ jsx("nav", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(
|
|
9
11
|
Link,
|
|
10
12
|
{
|
|
11
13
|
to: "/",
|
|
12
14
|
className: "rounded-lg font-mono py-1.5 text-sm font-medium text-muted-foreground hover:underline",
|
|
13
|
-
children:
|
|
15
|
+
children: config.name
|
|
14
16
|
}
|
|
15
17
|
) }),
|
|
16
18
|
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
@@ -40,10 +42,10 @@ function Header({ slideControls } = {}) {
|
|
|
40
42
|
)
|
|
41
43
|
] }),
|
|
42
44
|
/* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-2", children: [
|
|
43
|
-
/* @__PURE__ */ jsx(
|
|
45
|
+
config.github && /* @__PURE__ */ jsx(
|
|
44
46
|
Link,
|
|
45
47
|
{
|
|
46
|
-
to:
|
|
48
|
+
to: `https://github.com/${config.github}`,
|
|
47
49
|
target: "_blank",
|
|
48
50
|
className: "text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
|
|
49
51
|
"aria-label": "GitHub",
|