@simple-photo-gallery/theme-modern 0.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,52 +1,5 @@
1
- # Astro Starter Kit: Basics
1
+ # Simple Photo Gallery - Modern Theme
2
2
 
3
- ```sh
4
- npm create astro@latest -- --template basics
5
- ```
3
+ This is the modern theme for Simple Photo Gallery built with Astro. This theme provides a clean, responsive interface for displaying photo galleries.
6
4
 
7
- [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
8
- [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
9
- [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
10
-
11
- > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
12
-
13
- ![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554)
14
-
15
- ## 🚀 Project Structure
16
-
17
- Inside of your Astro project, you'll see the following folders and files:
18
-
19
- ```text
20
- /
21
- ├── public/
22
- │ └── favicon.svg
23
- ├── src
24
- │   ├── assets
25
- │   │   └── astro.svg
26
- │   ├── components
27
- │   │   └── Welcome.astro
28
- │   ├── layouts
29
- │   │   └── Layout.astro
30
- │   └── pages
31
- │   └── index.astro
32
- └── package.json
33
- ```
34
-
35
- To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/).
36
-
37
- ## 🧞 Commands
38
-
39
- All commands are run from the root of the project, from a terminal:
40
-
41
- | Command | Action |
42
- | :------------------------ | :----------------------------------------------- |
43
- | `npm install` | Installs dependencies |
44
- | `npm run dev` | Starts local dev server at `localhost:4321` |
45
- | `npm run build` | Build your production site to `./dist/` |
46
- | `npm run preview` | Preview your build locally, before deploying |
47
- | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
48
- | `npm run astro -- --help` | Get help using the Astro CLI |
49
-
50
- ## 👀 Want to learn more?
51
-
52
- Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
5
+ > Note: advanced theme support is coming soon.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-photo-gallery/theme-modern",
3
- "version": "0.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "Modern theme for Simple Photo Gallery",
5
5
  "license": "MIT",
6
6
  "author": "Vladimir Haltakov, Tomasz Rusin",
@@ -6,6 +6,5 @@
6
6
  .container {
7
7
  max-width: 1200px;
8
8
  margin: 0 auto;
9
- padding: 0 16px;
10
9
  }
11
10
  </style>
@@ -0,0 +1,43 @@
1
+ ---
2
+ // Footer component for Simple Photo Gallery
3
+ ---
4
+
5
+ <footer class="footer">
6
+ <p class="footer__text">
7
+ Created with <a href="https://simple.photo" class="footer__link" target="_blank" rel="noopener noreferrer"
8
+ >Simple Photo Gallery</a
9
+ >
10
+ </p>
11
+ </footer>
12
+
13
+ <style>
14
+ .footer {
15
+ padding: 2rem 1.5rem 1.5rem;
16
+ text-align: center;
17
+ background-color: transparent;
18
+ }
19
+
20
+ .footer__text {
21
+ font-size: 0.875rem;
22
+ color: #6b7280;
23
+ margin: 0;
24
+ font-weight: 400;
25
+ }
26
+
27
+ .footer__link {
28
+ color: #6b7280;
29
+ text-decoration: none;
30
+ transition: color 0.2s ease;
31
+ }
32
+
33
+ .footer__link:hover {
34
+ color: #374151;
35
+ text-decoration: underline;
36
+ }
37
+
38
+ .footer__link:focus {
39
+ outline: 2px solid #6b7280;
40
+ outline-offset: 2px;
41
+ border-radius: 2px;
42
+ }
43
+ </style>
@@ -8,23 +8,28 @@ import type { GallerySection as GallerySectionType } from '@/features/themes/bas
8
8
  interface Props {
9
9
  section: GallerySectionType;
10
10
  sectionIndex: number;
11
+ mediaBaseUrl?: string;
11
12
  }
12
13
 
13
- const { section, sectionIndex } = Astro.props;
14
+ const { section, sectionIndex, mediaBaseUrl } = Astro.props;
15
+
16
+ const validImages = section.images.filter(
17
+ (image) => image.width > 0 && image.height > 0 && image.thumbnail?.width > 0 && image.thumbnail?.height > 0,
18
+ );
14
19
  ---
15
20
 
16
21
  <section class={`gallery-section gallery-section-${sectionIndex}`}>
17
22
  <Container>
18
23
  <GallerySectionHeader section={section} />
19
24
  <div class="gallery-section__gallery" id={`gallery-${sectionIndex}`}>
20
- {section.images.map((image) => <GallerySectionItem image={image} />)}
25
+ {validImages.map((image) => <GallerySectionItem image={image} mediaBaseUrl={mediaBaseUrl} />)}
21
26
  </div>
22
27
  </Container>
23
28
  </section>
24
29
 
25
30
  <style>
26
31
  .gallery-section {
27
- padding: 5rem 1rem;
32
+ padding: 1rem 1rem;
28
33
  }
29
34
 
30
35
  .gallery-section:nth-child(even) {
@@ -38,38 +43,17 @@ const { section, sectionIndex } = Astro.props;
38
43
  }
39
44
 
40
45
  .gallery-section__gallery {
41
- columns: 1;
42
- column-gap: 1rem;
46
+ display: flex;
47
+ flex-wrap: wrap;
43
48
  margin-bottom: 1rem;
44
49
  }
45
50
 
46
- @media (min-width: 640px) {
47
- .gallery-section__gallery {
48
- columns: 2;
49
- }
50
- }
51
-
52
- @media (min-width: 768px) {
53
- .gallery-section__gallery {
54
- columns: 3;
55
- }
56
- }
57
-
58
- @media (min-width: 1024px) {
59
- .gallery-section__gallery {
60
- columns: 4;
61
- }
62
- }
63
-
64
- @media (min-width: 1280px) {
65
- .gallery-section__gallery {
66
- columns: 5;
67
- }
68
- }
69
-
70
- @media (min-width: 1536px) {
71
- .gallery-section__gallery {
72
- columns: 6;
73
- }
51
+ .gallery-section__gallery::after {
52
+ --w: 2;
53
+ --h: 1;
54
+ --ratio: calc(var(--w) / var(--h));
55
+ content: '';
56
+ flex-basis: calc(var(--ratio) * var(--row-height));
57
+ flex-grow: 1000000;
74
58
  }
75
59
  </style>
@@ -20,7 +20,7 @@ const { section } = Astro.props;
20
20
  }
21
21
 
22
22
  .gallery-section__header-title {
23
- font-size: clamp(2.5rem, 5vw, 5rem);
23
+ font-size: clamp(1.5rem, 3vw, 2.5rem);
24
24
  font-weight: 700;
25
25
  margin-bottom: 1.5rem;
26
26
  color: #111827;
@@ -1,37 +1,45 @@
1
1
  ---
2
- import { getGalleryPath } from '@/features/themes/base-theme/utils';
2
+ import { getPhotoPath, getRelativePath } from '@/features/themes/base-theme/utils';
3
3
 
4
4
  import type { GalleryImage } from '@/features/themes/base-theme/types/gallery';
5
5
 
6
6
  interface Props {
7
7
  image: GalleryImage;
8
+ mediaBaseUrl?: string;
8
9
  }
9
10
 
10
- const { image } = Astro.props;
11
+ const { image, mediaBaseUrl } = Astro.props;
11
12
  ---
12
13
 
13
- <div class="gallery-section__item">
14
- <a
15
- href={getGalleryPath(image.path)}
16
- data-pswp-src={getGalleryPath(image.path)}
17
- data-pswp-width={image.width}
18
- data-pswp-height={image.height}
19
- data-pswp-type={image.type}
20
- data-pswp-caption={image.description ? `<p class="image-caption">${image.description}</p>` : ''}>
21
- <img
22
- src={getGalleryPath(image.thumbnail?.path)}
23
- alt={image.alt}
24
- loading="lazy"
25
- width={image.thumbnail?.width}
26
- height={image.thumbnail?.height}
27
- />
28
- </a>
29
- </div>
14
+ <a
15
+ class="gallery-section__item"
16
+ href={getPhotoPath(image.path, mediaBaseUrl)}
17
+ data-pswp-src={getPhotoPath(image.path, mediaBaseUrl)}
18
+ data-pswp-width={image.width}
19
+ data-pswp-height={image.height}
20
+ data-pswp-type={image.type}
21
+ data-pswp-caption={image.alt ? `<p class="image-caption">${image.alt}</p>` : ''}
22
+ style={`--w: ${image.thumbnail?.width}; --h: ${image.thumbnail?.height}`}>
23
+ <img
24
+ src={getRelativePath(image.thumbnail?.path)}
25
+ srcset={`${getRelativePath(image.thumbnail?.path)} 1x, ${getRelativePath(image.thumbnail?.pathRetina)} 2x`}
26
+ alt={image.alt}
27
+ loading="lazy"
28
+ width={image.thumbnail?.width}
29
+ height={image.thumbnail?.height}
30
+ />
31
+ </a>
30
32
 
31
33
  <style>
34
+ :root {
35
+ --row-height: 6rem;
36
+ }
37
+
32
38
  .gallery-section__item {
33
- break-inside: avoid;
34
- margin-bottom: 1rem;
39
+ --ratio: calc(var(--w) / var(--h));
40
+ flex-basis: calc(var(--ratio) * var(--row-height));
41
+ flex-grow: calc(var(--ratio) * 100);
42
+ margin: 0.25rem;
35
43
  border-radius: 0.75rem;
36
44
  overflow: hidden;
37
45
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
@@ -51,7 +59,25 @@ const { image } = Astro.props;
51
59
  transition: transform 0.5s ease;
52
60
  }
53
61
 
54
- .gallery-item:hover img {
62
+ .gallery-section__item:hover img {
55
63
  transform: scale(1.05);
56
64
  }
65
+
66
+ @media (min-width: 640px) {
67
+ :root {
68
+ --row-height: 8rem;
69
+ }
70
+ }
71
+
72
+ @media (min-width: 768px) {
73
+ :root {
74
+ --row-height: 9rem;
75
+ }
76
+ }
77
+
78
+ @media (min-width: 1024px) {
79
+ :root {
80
+ --row-height: 10rem;
81
+ }
82
+ }
57
83
  </style>
@@ -1,21 +1,22 @@
1
1
  ---
2
2
  import { DEFAULT_STATIC_ASSETS_PATH } from '@/config';
3
3
  import HeroScrollToGalleryBtn from '@/features/themes/base-theme/components/hero/HeroScrollToGalleryBtn.astro';
4
- import { getGalleryPath } from '@/features/themes/base-theme/utils';
4
+ import { getPhotoPath } from '@/features/themes/base-theme/utils';
5
5
 
6
6
  interface Props {
7
7
  title: string;
8
8
  description?: string;
9
9
  backgroundImage: string;
10
+ mediaBaseUrl?: string;
10
11
  }
11
12
 
12
- const { title, description, backgroundImage } = Astro.props;
13
+ const { title, description, backgroundImage, mediaBaseUrl } = Astro.props;
13
14
  ---
14
15
 
15
16
  <section class="hero">
16
17
  <div
17
18
  class="hero__bg"
18
- style={`background-image: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url('${getGalleryPath(backgroundImage) || `${DEFAULT_STATIC_ASSETS_PATH}/images/hero-placeholder.png`}')`}>
19
+ style={`background-image: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url('${getPhotoPath(backgroundImage, mediaBaseUrl) || `${DEFAULT_STATIC_ASSETS_PATH}/images/hero-placeholder.png`}')`}>
19
20
  </div>
20
21
  <div class="hero__content">
21
22
  <h1 class="hero__title">{title}</h1>
@@ -28,7 +29,7 @@ const { title, description, backgroundImage } = Astro.props;
28
29
  .hero {
29
30
  position: relative;
30
31
  min-height: 450px;
31
- height: 100dvh;
32
+ height: 100vh;
32
33
  display: flex;
33
34
  align-items: center;
34
35
  justify-content: center;
@@ -56,7 +57,7 @@ const { title, description, backgroundImage } = Astro.props;
56
57
  }
57
58
 
58
59
  .hero__title {
59
- font-size: clamp(2rem, 8vw, 7rem);
60
+ font-size: clamp(1.5rem, 6vw, 5rem);
60
61
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
61
62
  text-align: center;
62
63
  line-height: 1.2;
@@ -39,7 +39,7 @@
39
39
  let captionHTML = '';
40
40
  if (currSlideElement) {
41
41
  const caption = currSlideElement?.getAttribute('data-pswp-caption');
42
- captionHTML = caption ?? currSlideElement?.querySelector('img')?.getAttribute('alt') ?? '';
42
+ captionHTML = caption || currSlideElement?.querySelector('img')?.getAttribute('alt') || '';
43
43
  el.innerHTML = captionHTML || '';
44
44
  }
45
45
  });
@@ -81,18 +81,15 @@
81
81
  }
82
82
 
83
83
  .pswp__caption .image-caption {
84
- background: rgba(0, 0, 0, 0.4);
85
- backdrop-filter: blur(20px);
86
- -webkit-backdrop-filter: blur(20px);
87
- border: 1px solid rgba(255, 255, 255, 0.2);
84
+ text-align: center;
85
+ background: rgba(128, 128, 128, 0.3);
86
+ backdrop-filter: blur(8px);
87
+ -webkit-backdrop-filter: blur(8px);
88
88
  color: white;
89
89
  padding: 16px;
90
90
  border-radius: 16px;
91
91
  margin: 1rem;
92
92
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
93
- animation: glassSlideIn 0.6s ease-out;
94
- transform-origin: bottom center;
95
- transition: all 0.3s ease;
96
93
  @media (max-width: 768px) {
97
94
  padding: 8px 16px;
98
95
  font-size: 0.8rem;
@@ -100,17 +97,6 @@
100
97
  }
101
98
  }
102
99
 
103
- @keyframes glassSlideIn {
104
- 0% {
105
- opacity: 0;
106
- transform: translateY(20px) scale(0.95);
107
- }
108
- 100% {
109
- opacity: 1;
110
- transform: translateY(0) scale(1);
111
- }
112
- }
113
-
114
100
  .pswp__caption__center {
115
101
  text-align: center;
116
102
  max-width: 42rem;
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import { DEFAULT_STATIC_ASSETS_PATH } from '@/config';
3
3
  import Container from '@/features/themes/base-theme/components/container/Container.astro';
4
- import { getGalleryPath } from '@/features/themes/base-theme/utils';
4
+ import { getRelativePath, getSubgalleryThumbnailPath } from '@/features/themes/base-theme/utils';
5
5
 
6
6
  interface SubGallery {
7
7
  title: string;
@@ -31,10 +31,13 @@ const { title, description, subGalleries } = Astro.props;
31
31
  <div class="sub-galleries__grid">
32
32
  {
33
33
  subGalleries.map((subgallery) => (
34
- <a href={getGalleryPath(subgallery.path)} class="sub-galleries__item">
34
+ <a href={getRelativePath(subgallery.path)} class="sub-galleries__item">
35
35
  <div class="sub-galleries__item-image">
36
36
  <img
37
- src={getGalleryPath(subgallery.headerImage) || `${DEFAULT_STATIC_ASSETS_PATH}/images/gallery-placeholder.png`}
37
+ src={
38
+ getSubgalleryThumbnailPath(subgallery.headerImage) ||
39
+ `${DEFAULT_STATIC_ASSETS_PATH}/images/gallery-placeholder.png`
40
+ }
38
41
  alt={subgallery.title}
39
42
  loading="lazy"
40
43
  />
@@ -1,14 +1,14 @@
1
1
  ---
2
- import { DEFAULT_STATIC_ASSETS_PATH } from '@/config';
3
-
4
2
  import type { GalleryMetadata } from '@/features/themes/base-theme/types/gallery';
5
3
 
6
4
  interface Props {
7
5
  title: string;
6
+ description?: string;
7
+ url?: string;
8
8
  metadata?: GalleryMetadata;
9
9
  }
10
10
 
11
- const { title, metadata } = Astro.props;
11
+ const { title, description, url, metadata } = Astro.props;
12
12
  ---
13
13
 
14
14
  <head>
@@ -19,32 +19,32 @@ const { title, metadata } = Astro.props;
19
19
  <base href="/" />
20
20
 
21
21
  {/* Basic SEO */}
22
+ <meta name="description" content={description} />
22
23
  {metadata?.keywords && <meta name="keywords" content={metadata.keywords} />}
23
24
  {metadata?.author && <meta name="author" content={metadata.author} />}
24
- {metadata?.canonicalUrl && <link rel="canonical" href={metadata.canonicalUrl} />}
25
+ {metadata?.canonicalUrl || (url && <link rel="canonical" href={metadata?.canonicalUrl || url} />)}
25
26
  {metadata?.robots && <meta name="robots" content={metadata.robots} />}
26
27
  {metadata?.language && <meta name="language" content={metadata.language} />}
27
28
 
28
- {/* Favicon */}
29
- <link rel="icon" type="image/x-icon" href="/favicon.ico" />
30
-
31
29
  {/* Open Graph / Facebook */}
32
- {metadata?.ogType && <meta property="og:type" content={metadata.ogType} />}
33
- {metadata?.ogUrl && <meta property="og:url" content={metadata.ogUrl} />}
34
30
  <meta property="og:title" content={title} />
35
- {metadata?.description && <meta property="og:description" content={metadata.description} />}
36
- {metadata?.ogImage && <meta property="og:image" content={metadata.ogImage} />}
37
- {metadata?.ogImageWidth && <meta property="og:image:width" content={metadata.ogImageWidth.toString()} />}
38
- {metadata?.ogImageHeight && <meta property="og:image:height" content={metadata.ogImageHeight.toString()} />}
31
+ <meta property="og:description" content={description} />
32
+ {metadata?.image && <meta property="og:image" content={metadata.image} />}
33
+ <meta property="og:image:width" content={String(metadata?.imageWidth) || '1200'} />
34
+ <meta property="og:image:height" content={String(metadata?.imageHeight) || '631'} />
35
+ {metadata?.ogType && <meta property="og:type" content={metadata.ogType} />}
36
+ {metadata?.ogUrl || (url && <meta property="og:url" content={metadata?.ogUrl || url} />)}
39
37
  {metadata?.ogSiteName && <meta property="og:site_name" content={metadata.ogSiteName} />}
40
38
 
41
39
  {/* Twitter */}
42
- {metadata?.twitterCard && <meta name="twitter:card" content={metadata.twitterCard} />}
40
+ <meta name="twitter:card" content="summary_large_image" />
41
+ <meta name="twitter:title" content={title} />
42
+ <meta name="twitter:description" content={description} />
43
+ {metadata?.image && <meta name="twitter:image" content={metadata.image} />}
44
+ <meta name="twitter:image:width" content={String(metadata?.imageWidth) || '1200'} />
45
+ <meta name="twitter:image:height" content={String(metadata?.imageHeight) || '631'} />
43
46
  {metadata?.twitterSite && <meta name="twitter:site" content={metadata.twitterSite} />}
44
47
  {metadata?.twitterCreator && <meta name="twitter:creator" content={metadata.twitterCreator} />}
45
- <meta name="twitter:title" content={title} />
46
- {metadata?.description && <meta name="twitter:description" content={metadata.description} />}
47
- {metadata?.ogImage && <meta name="twitter:image" content={metadata.ogImage} />}
48
48
 
49
49
  <script is:inline>
50
50
  (function () {
@@ -63,7 +63,6 @@ const { title, metadata } = Astro.props;
63
63
  })();
64
64
  </script>
65
65
 
66
- <link rel="icon" href={`${DEFAULT_STATIC_ASSETS_PATH}/images/favicon.ico`} sizes="32x32" />
67
66
  <link rel="preconnect" href="https://fonts.googleapis.com" />
68
67
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
69
68
  <link
@@ -5,23 +5,22 @@ import type { GalleryMetadata } from '@/features/themes/base-theme/types/gallery
5
5
 
6
6
  interface Props {
7
7
  title: string;
8
+ description?: string;
9
+ url?: string;
8
10
  metadata?: GalleryMetadata;
9
11
  }
10
12
 
11
- const { title, metadata } = Astro.props;
13
+ const { title, description, metadata, url } = Astro.props;
12
14
  ---
13
15
 
14
16
  <!doctype html>
15
17
  <html lang={metadata?.language || 'en'}>
16
- <MainHead title={title} metadata={metadata} />
18
+ <MainHead title={title} description={description} metadata={metadata} url={url} />
17
19
  <body>
18
20
  <slot />
19
21
  </body>
20
22
  </html>
21
23
 
22
-
23
-
24
-
25
24
  <style is:global>
26
25
  * {
27
26
  margin: 0;
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import fs from 'node:fs';
3
3
 
4
+ import Footer from '@/features/themes/base-theme/components/footer/Footer.astro';
4
5
  import GallerySection from '@/features/themes/base-theme/components/gallery-section/GallerySection.astro';
5
6
  import Hero from '@/features/themes/base-theme/components/hero/Hero.astro';
6
7
  import PhotoSwipe from '@/features/themes/base-theme/components/lightbox/PhotoSwipe.astro';
@@ -15,11 +16,11 @@ const galleryData = JSON.parse(fs.readFileSync(galleryJsonPath, 'utf8'));
15
16
 
16
17
  const gallery = galleryData as GalleryData;
17
18
 
18
- const { title, description, headerImage, metadata, sections, subGalleries } = gallery;
19
+ const { title, description, headerImage, metadata, sections, subGalleries, mediaBaseUrl, url } = gallery;
19
20
  ---
20
21
 
21
- <MainLayout title={title} metadata={metadata}>
22
- <Hero title={title} description={description} backgroundImage={headerImage} />
22
+ <MainLayout title={title} description={description} metadata={metadata} url={url}>
23
+ <Hero title={title} description={description} backgroundImage={headerImage} mediaBaseUrl={mediaBaseUrl} />
23
24
 
24
25
  {
25
26
  subGalleries && subGalleries.galleries.length > 0 && (
@@ -30,7 +31,12 @@ const { title, description, headerImage, metadata, sections, subGalleries } = ga
30
31
  />
31
32
  )
32
33
  }
33
- {sections.map((section, sectionIndex) => <GallerySection section={section} sectionIndex={sectionIndex} />)}
34
+ {
35
+ sections.map((section, sectionIndex) => (
36
+ <GallerySection section={section} sectionIndex={sectionIndex} mediaBaseUrl={mediaBaseUrl} />
37
+ ))
38
+ }
34
39
 
40
+ <Footer />
35
41
  <PhotoSwipe />
36
42
  </MainLayout>
@@ -1,12 +1,12 @@
1
1
  export interface GalleryImage {
2
2
  path: string;
3
3
  alt?: string;
4
- description?: string;
5
4
  width: number;
6
5
  height: number;
7
6
  type: 'image' | 'video';
8
7
  thumbnail: {
9
8
  path: string;
9
+ pathRetina: string;
10
10
  width: number;
11
11
  height: number;
12
12
  };
@@ -25,14 +25,12 @@ export interface SubGallery {
25
25
  }
26
26
 
27
27
  export interface GalleryMetadata {
28
- description?: string;
28
+ image?: string;
29
+ imageWidth?: number;
30
+ imageHeight?: number;
29
31
  ogUrl?: string;
30
- ogImage?: string;
31
- ogImageWidth?: number;
32
- ogImageHeight?: number;
33
32
  ogType?: string;
34
33
  ogSiteName?: string;
35
- twitterCard?: string;
36
34
  twitterSite?: string;
37
35
  twitterCreator?: string;
38
36
  author?: string;
@@ -44,10 +42,11 @@ export interface GalleryMetadata {
44
42
 
45
43
  export interface GalleryData {
46
44
  title: string;
47
- description?: string;
48
- outputDir?: string;
45
+ description: string;
46
+ url?: string;
49
47
  headerImage: string;
50
48
  metadata?: GalleryMetadata;
49
+ mediaBaseUrl?: string;
51
50
  sections: GallerySection[];
52
51
  subGalleries?: {
53
52
  title?: string;
@@ -1,13 +1,12 @@
1
1
  import path from 'node:path';
2
2
 
3
3
  /**
4
- * Normalizes resource paths to be relative to the gallery root directory instead of the gallery.json file.
4
+ * Normalizes resource paths to be relative to the gallery root directory.
5
5
  *
6
6
  * @param resourcePath - The resource path (file or directory), typically relative to the gallery.json file
7
7
  * @returns The normalized path relative to the gallery root directory
8
8
  */
9
-
10
- export const getGalleryPath = (resourcePath: string) => {
9
+ export const getRelativePath = (resourcePath: string) => {
11
10
  const galleryConfigPath = path.resolve(process.env.GALLERY_JSON_PATH || '');
12
11
  const galleryConfigDir = path.dirname(galleryConfigPath);
13
12
 
@@ -16,3 +15,28 @@ export const getGalleryPath = (resourcePath: string) => {
16
15
 
17
16
  return path.relative(baseDir, absoluteResourcePath);
18
17
  };
18
+
19
+ /**
20
+ * Get the path to a photo that is always in the gallery root directory.
21
+ *
22
+ * @param resourcePath - The path to the photo on the hard disk
23
+ * @returns The normalized path relative to the gallery root directory
24
+ */
25
+ export const getPhotoPath = (photoPath: string, mediaBaseUrl?: string) => {
26
+ const resourceBasename = path.basename(photoPath);
27
+
28
+ return mediaBaseUrl ? `${mediaBaseUrl}/${resourceBasename}` : path.join('.', resourceBasename);
29
+ };
30
+
31
+ /**
32
+ * Get the path to a subgallery thumbnail that is always in the subgallery directory.
33
+ *
34
+ * @param subgalleryHeaderImagePath - The path to the subgallery header image on the hard disk
35
+ * @returns The normalized path relative to the subgallery directory
36
+ */
37
+ export const getSubgalleryThumbnailPath = (subgalleryHeaderImagePath: string) => {
38
+ const photoBasename = path.basename(subgalleryHeaderImagePath);
39
+ const subgalleryFolderName = path.basename(path.dirname(subgalleryHeaderImagePath));
40
+
41
+ return path.join(subgalleryFolderName, 'gallery', 'thumbnails', photoBasename);
42
+ };
Binary file