Compare commits

5 Commits

Author SHA1 Message Date
4b5fe07ece rework CSS and JS 2024-01-30 22:47:35 +01:00
8e426a6611 current stage of CSS tests, but we'll switch drastically again 2024-01-30 14:39:01 +01:00
adcd4c4ee0 support SCSS 2024-01-30 12:34:28 +01:00
c79c9cd1fc support reading files from directory 2024-01-30 12:29:32 +01:00
33fbd81151 first work into renewed gallery 2024-01-30 12:18:28 +01:00
12 changed files with 708 additions and 418 deletions

100
README.md
View File

@@ -5,22 +5,20 @@
# hugo-snap-gallery
Automagical css image gallery in [Hugo](https://gohugo.io/) using shortcodes. Lightweight, slim and fully local JavaScript.
Automagical css image gallery in [Hugo](https://gohugo.io/) using shortcodes. No JavaScript is used, just plain CSS.
## Features
- Custom `{{< snap-gallery >}}` shortcode that allows to display multiple images
- Two modes:
- **slideshow** displaying only one image at a time with the ability to navigate
- **gallery** displaying all selected pictures next to each other
- All pictures can be expanded on click in a lightbox
- Manually select the images you want to display, or provide the path to a directory to use all images inside. This can be combined!
- Next/prev buttons in slideshow and lightbox views
- The gallery is responsive, images are scaled/cropped to fill 16:10 tiles
- CSS and JS is automatically loaded the first time you use the `{{< snap-gallery >}}` shortcode on each page
- Multiple galleries/slideshows per page supported, no interference
- Automatic rotation of slideshow with a configurable interval. Can also be disabled.
- Supports providing metadata such as `alt` and `title` attributes as well as captions
- Custom `{{< figure >}}` shortcode that enables new features but is mostly backwards-compatible with Hugo's built-in `{{< figure >}}`shortcode
- All pictures can be expanded on click in a CSS-only lightbox
- Use the `{{< figure >}}` shortcode by itself to enable pretty captions
- Put multiple `{{< figure >}}` shortcodes inside a `{{< gallery >}}` to create a pretty image gallery
- Use the `{{< snap-dir >}}` shortcode inside a `{{< gallery >}}` to show all containing files nicely
- Next/prev buttons in galleries
- The gallery is responsive, images are scaled/cropped to fill square tiles
- Pretty captions outside and inside lightbox
- Only requires 4kB of CSS (unminified; you can minify it if you want)
- CSS is automatically loaded the first time you use the `{{< figure >}}` shortcode on each page
## Installation
@@ -31,56 +29,64 @@ Use this like an additional Hugo theme, so add it to the `theme` config. Example
theme = [ "hugo-sustain", "hugo-snap-gallery" ]
```
## `{{< snap-gallery >}}` shortcode usage
Quickstart:
## `{{< figure >}}` shortcode usage
- `{{< snap-gallery src="image1.jpg, image2.png" >}}`: Display these two images in **gallery** mode
- `{{< snap-gallery src="image1.jpg, image2.png" mode="slideshow" >}}`: Display these two images in **slideshow** mode
- `{{< snap-gallery src="img/folder1/, image2.png" >}}`: Display all images in the directory `img/folder1` and the single image `image2.png` in **gallery** mode
Specifying your image files:
All parameters:
- `{{< figure src="image.jpg" >}}` will just show the image with no caption, and open the full version of it when clicked
- `{{< figure src="image.jpg" caption="My description" >}}` will show the image and open the full version of it when clicked, and shows the caption text in both views. Markdown is possible
- `{{< figure src="image.jpg" link="http://example.com" >}}` will use `image.jpg` for thumbnail and link to `http://example.com` when clicked
- `src`: Must contain either a comma-separated list of paths to images, or a directory path containing images. Note that the paths are absolute, so imagine a `/` in front of them. Also note that the shortcode assumes that they are all stored in `/static/`.
- `lightbox`: Whether a click on an image shall open a lightbox modal. Default: `true`.
- `aspectratio`: Define the aspect ratio of the images in the slideshow/gallery. Default: `16/10`.
- `metadata`: See below for how to add metadata to your files. Default: `map[]`.
- `mode`: Can be either `gallery` or `slideshow`. Default: `gallery`.
- For gallery mode:
- `columns`: Amount of columns the images are displayed in. Default: `4`.
- `minwidth`: Minimum width that each image shall have, e.g. `150px` or `30%`. May conflict with the desired amount of columns. Default: `200px`.
- For slideshow mode:
- `slideshowwidth`: Width of slideshow, e.g. `300px` or `80%`. Default: `100%`.
- `slideshowrotate`: Whether the slideshow shall automatically rotate through the images. Default: `true`.
- `slideshowrotate_timer`: Interval of automatic slideshow rotation (if enabled), in milliseconds. Default: `5000` (5 seconds).
Optional parameters:
**Note: Boolean values (`true`/`false`) must be provided without surrounding `"` characters!** `lightbox=false` disables the lightbox, while `lightbox="false"` does not.
- All the [features/parameters](https://gohugo.io/content-management/shortcodes/#figure) of Hugo's built-in `figure` shortcode work as normal, i.e. src, link, rel, title, caption, class, attr (attribution), attrlink, alt. width and height might lead to strange results when used inside `{{< gallery >}}`.
- `class` allows you to set any custom classes you want on the `<figure>` tag. The values `no-border`, `sm`, `md`, `lg`, `pull-left` and `pull-right` are made available by this project.
- `lightbox` allows you to control the lightbox. The value `none` will disable the lightbox completely.
### Metadata
Optional parameters work for standalone `{{< figure >}}` shortcodes and inside of `{{< gallery >}}`. However, they cannot be applied to `{{< snap-dir >}}`.
Using separate data files, you can provide metadata to the image files. Imagine using the following shortcode: `{{< snap-gallery src="image1.jpg, img/folder1/" metadata="images.en" >}}`.
This would assume you have a file named `/data/images.en.yaml`. It may contain the following data:
## `{{< gallery >}}` shortcode usage
```yaml
- src: image1.jpg
html:
alt: Alternative text
title: Title, text displayed when hovering
- src: img/folder1/foo.png
html:
alt: Alternative text for the first picture in the image folder
### Using defined images
To specify individual image files, call it like in the following example. All parameters for the figure should work as described above.
```
{{< gallery >}}
{{< figure src="image1.jpg" >}}
{{< figure src="image2.jpg" >}}
{{< figure src="image3.jpg" >}}
{{< /gallery >}}
```
This way, you can add any HTML attributes to the `<img>` element for the images you describe in the metadata file. In this example, you add this for two images, one of them is in a folder whose path you provided. You don't have to add information for all files.
Inside of the lightbox (so when clicked on one image), you will see forward and backward arrows on the right and left side. The backward arrow will not work when you are on the first image of a gallery. The forward arrow however will still show when on the last image but just close the frame.
This flexible way allows you to also translate metadata. Just use different `metadata` values to the shortcodes depending on the language.
Note that a `title` is also taken as a caption to the picture in order to reduce duplicated work.
### Using a whole directory
To specify a directory full of image files, use the example below. This will use all files (make sure it's only images!) and display them in a gallery. You cannot define captions or other parameters for the individual images:
```
{{< gallery >}}
{{< snap-dir srcdir="/img/blog/orr" >}}
{{< /gallery >}}
```
The navigation inside the lightbox will work as with the individually defined gallery image, and even recognise when the gallery is at its last image.
## CSS Hackers
`snap-gallery.css` is designed to provide square tiles. The gallery contains three tiles per row on larger screens, and will limit to 2 or 1 tile per row if the screen is smaller. To change that, you should look at the three definition of `.snap-gallery figure`. Please feel free to contact me if you found a more flexible way to change that.
Other than that, the CSS should be simple enough to allow modifications.
## Credits
The original inspiration for this shortcode came from [Li-Wen Yip's easy-gallery](https://github.com/liwenyip/hugo-easy-gallery). The first major version of this was already a 90% rewrite, and the current one has even less to do with it. However, the rewrite took some inspirations from [W3Schools](https://www.w3schools.com/howto/howto_js_lightbox.asp), thanks!
The original inspiration for this shortcode comes from [Li-Wen Yip's easy-gallery](https://github.com/liwenyip/hugo-easy-gallery). However, snap-gallery is a 98% rewrite.
## License

282
assets/scss/old.css Normal file
View File

@@ -0,0 +1,282 @@
/*
SPDX-FileCopyrightText: 2020 Max Mehl <mail@mehl.mx>
SPDX-License-Identifier: MIT
*/
/** GENERAL FIGURE LAYOUT **/
figure {
max-width: 90%;
margin: 10px auto;
display: block;
text-align: center;
}
/* make box with box-shadow only as large as image */
.snap-wrapper {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
display: inline-block;
}
figure a:hover {
text-decoration: none;
}
figure img {
max-width: 100%;
max-height: 100%;
}
figure.sm {
max-width: 30%;
}
figure.md {
max-width: 50%;
}
figure.lg {
max-width: 70%;
}
figure.pull-right {
padding: 10px 0 10px 10px;
}
figure.pull-left {
padding: 10px 10px 10px 0;
}
figure figcaption {
background-color: rgba(0, 0, 0, 0.5);
display: block;
font-size: .8em;
padding: 1px;
position: static;
text-align: center;
bottom: 0;
left: 0;
right: 0;
color: #FFF;
}
/* Extra classes for figures */
figure.no-border .snap-wrapper {
box-shadow: none;
}
/** GALLERY MARKUP **/
.snap-gallery {
margin: 10px;
display: flex;
justify-content: center;
flex-wrap: wrap;
}
/* avoid sick effects of inline-block on gallery elements */
.snap-gallery .snap-wrapper {
display: block;
}
.snap-gallery figure {
position: relative;
width: 30%;
padding-bottom: 30%;
margin: 1%;
text-align: left;
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.2),0 2px 4px 0 rgba(0,0,0,0.19);
}
.snap-gallery figcaption {
position: absolute;
}
.snap-gallery img.snap-thumb {
position: absolute;
object-fit: cover;
object-position: center;
height: 100%;
width: 100%;
}
.snap-gallery figure img.snap-thumb:hover {
transform: scale(1.02);
-webkit-transition: all 0.3s ease-in-out;
-moz-transition: all 0.3s ease-in-out;
-o-transition: all 0.3s ease-in-out;
transition: all 0.3s ease-in-out;
}
@media screen and (max-width: 767px) {
.snap-gallery figure {
width: 45%;
padding-bottom: 45%;
margin: 2%
}
}
@media screen and (max-width: 479px) {
.snap-gallery figure {
width: 90%;
padding-bottom: 90%;
margin: 3%;
}
}
/** LIGHTBOX MARKUP **/
.snap-lightbox {
/** Default lightbox to hidden */
display: none;
/** Position and style */
position: fixed;
z-index: 999;
width: 100%;
height: 100%;
text-align: center;
white-space: nowrap;
top: 0;
left: 0;
background: rgba(0,0,0,0.8);
}
/* effects when "activating" the figure */
.snap-lightbox:target {
/** Remove default browser outline */
outline: none;
/** Unhide lightbox **/
display: block;
}
/* Click on the complete background closes the lightbox */
/* Exception: arrow in gallery, they have higher z-index */
a.snap-lightbox-close {
position: fixed;
z-index: 800;
width: 100%;
height: 100%;
text-align: center;
white-space: nowrap;
top: 0;
left: 0;
}
/* keep lightbox in middle. TODO: hacky, and not realibly in middle */
.snap-lightbox::before {
content: "";
display: inline-block;
vertical-align: middle;
width: 0;
/* adjust for white space between pseudo element and next sibling */
margin-right: -.25em;
/* stretch line height */
height: 100%;
}
/* Container for image */
.snap-lightbox-inner {
display: inline-block;
vertical-align: middle;
white-space: normal;
max-width: 90%;
max-height: 80%;
height: 100%;
}
.snap-lightbox-inner p {
color: #fff;
z-index: 810;
position: relative;
}
/* prev/next arrows & close button */
.snap-lightbox-close-button {
position: fixed;
z-index: 950;
width: 5%;
height: calc(5vw); /* ~5% width */
right: 0;
top: 0;
}
.snap-lightbox-x::after {
content: "\00d7"; /* This will render the 'X' */
font-size: 3em;
font-style: normal;
font-weight: 700;
color: #000;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.snap-lightbox-close-button:hover .snap-lightbox-x::after {
color: #fff;
transition: all 0.25s ease;
}
.snap-lightbox-prev, .snap-lightbox-next {
position: fixed;
z-index: 800;
width: 5%;
height: 100%;
text-align: center;
top: 0;
}
.snap-lightbox-prev {
left: 0;
}
.snap-lightbox-next {
right: 0;
}
.snap-lightbox-arrow {
display: inline-block;
position: fixed;
top: 50%;
z-index: 900;
border: solid #000;
border-width: 0 7px 7px 0;
display: inline-block;
padding: 1%;
}
span .snap-lightbox-arrow {
border-color: #5e5e5e;
}
.snap-lightbox-prev .snap-lightbox-arrow {
left: 2%;
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
}
.snap-lightbox-next .snap-lightbox-arrow {
right: 2%;
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
a.snap-lightbox-prev:hover > .snap-lightbox-arrow,
a.snap-lightbox-next:hover > .snap-lightbox-arrow {
border-color: #fff;
transition: all 0.25s ease;
}
@media screen and (max-width: 767px) {
.snap-lightbox-inner {
max-width: 80%;
}
.snap-lightbox-prev, .snap-lightbox-next {
width: 10%;
}
.snap-lightbox-close-button {
width: 10%;
height: calc(10vw); /* ~10% width */
}
}

View File

@@ -1,63 +1,20 @@
/*
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
SPDX-License-Identifier: MIT
*/
$gap: var(--gap);
.snap-gallery,
.snap-slideshow {
.snap-wrapper {
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin-bottom: 15px;
/* Create equal columns in flexbox */
.snap-image {
cursor: var(--cursor);
/* Column amount and width are configurable by style variables */
width: calc(100% / var(--columns) - #{$gap});
min-width: var(--min-width);
img {
aspect-ratio: var(--aspectratio);
width: 100%;
object-fit: cover;
}
}
}
.snap-gallery {
flex-wrap: wrap;
gap: $gap;
justify-content: space-around;
gap: 10px;
}
.snap-slideshow {
width: var(--slideshow-width);
margin-left: auto;
margin-right: auto;
/* Create equal columns in flexbox */
.snap-image {
// TODO: Make this configurable with variables
width: calc(100% / 4 - 10px);
min-width: 200px;
// Hide all contained images except the first
.snap-image:not(:first-child) {
display: none;
}
}
// Animation
.snap-slideshow .snap-image,
.snap-lightbox .snap-lightbox-inner {
animation: 1s fade;
@keyframes fade {
from {
opacity: .4
}
to {
opacity: 1
}
img {
aspect-ratio: 16/10;
width: 100%;
object-fit: cover;
}
}
@@ -87,12 +44,7 @@ $gap: var(--gap);
/* stretch line height */
height: 100%;
}
}
// Disable cursor selection in lightbox and on slideshow, especially controls
.snap-lightbox,
.snap-slideshow {
user-select: none;
}
// /* Click on the complete background closes the lightbox */
@@ -117,45 +69,24 @@ $gap: var(--gap);
max-height: 80%;
height: 100%;
/* Number text (1/3 etc) */
.numbertext {
color: #f2f2f2;
background-color: #000;
font-size: 12px;
padding: 8px 12px;
position: absolute;
top: 10%;
}
img {
max-height: 100%;
}
@media screen and (max-width: 767px) {
max-width: 80%;
}
}
/* Number text (1/3 etc) and captions*/
.snap-numbertext {
position: absolute;
color: #f2f2f2;
background-color: #000;
font-size: 12px;
padding: 8px 12px;
}
.snap-caption {
bottom: 0;
color: #f2f2f2;
padding: 8px 0;
left: 10%;
width: 80%;
text-shadow: 1px 1px 10px #000;
font-weight: 700;
font-size: 1.1em;
text-align: center;
// As slideshow, position on picture on mid-screens and wider
.snap-slideshow & {
position: absolute;
@media screen and (max-width: 767px) {
display: none;
}
}
// In lightbox, always show below picture
.snap-lightbox & {
// TODO: Untested
p {
color: #fff;
z-index: 810;
position: relative;
}
}
@@ -169,70 +100,49 @@ $gap: var(--gap);
// }
/* prev/next arrows & close button */
.snap-prev,
.snap-next {
color: #fff;
text-decoration: none !important;
font-size: 30px;
position: absolute;
height: 100%;
.snap-lightbox-prev,
.snap-lightbox-next {
position: fixed;
z-index: 800;
width: 5%;
cursor: pointer;
height: 100%;
text-align: center;
top: 0;
// Slideshow specifics
.snap-slideshow & {
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.7);
height: 15%;
width: 10%;
min-height: 50px;
min-width: 50px;
&:hover {
background-color: rgba(0, 0, 0, 0.8);
transition: all 0.25s ease;
}
}
// Lightbox-specifics
.snap-lightbox & {
@media screen and (max-width: 767px) {
width: 10%;
}
}
// Item containing arrow
span {
.snap-lightbox-arrow {
display: inline-block;
position: fixed;
top: 50%;
z-index: 900;
border: solid #fff;
border-width: 0 7px 7px 0;
display: inline-block;
padding: 1%;
}
// Lightbox specifics
.snap-lightbox & {
position: fixed;
top: 50%;
}
&:hover>.snap-lightbox-arrow {
border-color: #999;
transition: all 0.25s ease;
}
}
// Position arrows for left/right
.snap-prev {
.snap-lightbox-prev {
left: 0;
.snap-lightbox & span {
left: 0.5%;
.snap-lightbox-arrow {
left: 2%;
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
}
}
.snap-next {
.snap-lightbox-next {
right: 0;
.snap-lightbox & span {
right: 0.5%;
.snap-lightbox-arrow {
right: 2%;
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
}
@@ -258,3 +168,20 @@ $gap: var(--gap);
cursor: pointer;
}
}
@media screen and (max-width: 767px) {
.snap-lightbox-inner {
max-width: 80%;
}
.snap-lightbox-prev,
.snap-lightbox-next {
width: 10%;
}
.snap-lightbox-close-button {
width: 10%;
height: calc(10vw);
/* ~10% width */
}
}

View File

@@ -1,18 +0,0 @@
<!--
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
SPDX-License-Identifier: MIT
-->
{{- $imgs := .imgs -}}
{{- $galno := .galno -}}
<div class="snap-gallery" style="--columns:{{ default 4 .columns }};--min-width:{{ default "200px" .minwidth }};--gap:10px;--cursor:{{ .cursor }};--aspectratio:{{ safeCSS (default "16/10" .aspectratio) }};">
{{- range $i, $img := $imgs }}
<div class="snap-image">
<img
src="{{ relURL $img.src }}"
{{ range $attr, $value := $img.html -}}
{{ safeHTMLAttr $attr }}={{ $value }}
{{ end -}}
onclick="openLightbox({{ $galno }});openLightboxItem({{ $galno }}, {{ add $i 1 }});" />
</div>
{{- end }}
</div>

View File

@@ -1,28 +0,0 @@
<!--
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
SPDX-License-Identifier: MIT
-->
{{- $imgs := .imgs -}}
{{- $galno := .galno -}}
<div class="snap-lightbox" id="snap-lightbox-{{ $galno }}">
{{- range $i, $img := $imgs }}
<div class="snap-lightbox-inner">
<div class="snap-numbertext">{{ add $i 1 }} / {{ len $imgs }}</div>
<img
src="{{ relURL $img.src }}"
{{ range $attr, $value := $img.html -}}
{{ safeHTMLAttr $attr }}={{ $value }}
{{ end -}}
/>
{{- with $img.html.title }}
<div class="snap-caption">{{ . }}</div>
{{- end }}
</div>
{{- end -}}
<!-- Close and Next/previous controls -->
<span class="snap-close" onclick="closeLightbox({{ $galno }})">&times;</span>
<a class="snap-prev" onclick="moveLightboxItem({{ $galno }}, -1)"><span>&#10094;</span></a>
<a class="snap-next" onclick="moveLightboxItem({{ $galno }}, 1)"><span>&#10095;</span></a>
</div>

View File

@@ -1,25 +0,0 @@
<!--
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
SPDX-License-Identifier: MIT
-->
{{- $imgs := .imgs -}}
{{- $galno := .galno -}}
<div class="snap-slideshow" id="snap-slideshow-{{ $galno }}" style="--columns:1;--min-width:0px;--gap:0px;--slideshow-width:{{ default "100%" .width }};--cursor:{{ .cursor }};--aspectratio:{{ safeCSS (default "16/10" .aspectratio) }};">
{{- range $i, $img := $imgs }}
<div class="snap-image">
<div class="snap-numbertext">{{ add $i 1 }} / {{ len $imgs }}</div>
{{- with $img.html.title }}
<div class="snap-caption">{{ . }}</div>
{{- end }}
<img
src="{{ relURL $img.src }}"
{{ range $attr, $value := $img.html -}}
{{ safeHTMLAttr $attr }}={{ $value }}
{{ end -}}
onclick="openLightbox({{ $galno }});openLightboxItem({{ $galno }}, {{ add $i 1 }});" />
</div>
{{- end }}
<a class="snap-prev" onclick="moveSlideshowItem({{ $galno }}, -1)"><span>&#10094;</span></a>
<a class="snap-next" onclick="moveSlideshowItem({{ $galno }}, 1)"><span>&#10095;</span></a>
</div>

View File

@@ -0,0 +1,87 @@
<!--
SPDX-FileCopyrightText: 2020 Max Mehl <mail@mehl.mx>
SPDX-License-Identifier: MIT
-->
<!-- count how many times we've called this shortcode; load the css if it's the first time -->
{{- if not ($.Page.Scratch.Get "figurecount") }}<link rel="stylesheet" href="/css/snap-gallery.css" />{{ end }}
{{- $.Page.Scratch.Add "figurecount" 1 -}}
<!-- use src image -->
{{- $thumb := .Get "src" -}}
<!-- set unique ID depending on whether part of gallery or alone -->
{{- $figid := "" -}}
{{- $galid := "" -}}
{{- $id := "" -}}
{{- $previd := "" -}}
{{- $nextid := "" -}}
{{- if .Parent -}} <!-- gallery -->
{{- $galid = .Page.Scratch.Get "gallery" -}}
{{- if not $galid }}{{ $galid = 0 }}{{ end -}}
{{- $galid = add $galid 1 -}}
{{- $.Page.Scratch.Add "thisgalfig" 1 -}}
{{- $figid = .Page.Scratch.Get "thisgalfig" -}}
{{- else -}} <!-- standalone figure -->
{{- $.Page.Scratch.Add "nogalfig" 1 -}}
{{- $figid = .Page.Scratch.Get "nogalfig" -}}
{{- end -}}
{{- if $galid -}} <!-- gallery -->
{{- $id = print "gal" $galid "-" "fig" $figid -}}
{{- $previd = print "gal" $galid "-" "fig" (sub $figid 1) -}}
{{- $nextid = print "gal" $galid "-" "fig" (add $figid 1) -}}
{{- else -}} <!-- standalone figure -->
{{- $id = print "fig" $figid -}}
{{- end -}}
<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
<div class="snap-wrapper">
{{ if (.Get "link") -}}
<a href="{{ .Get "link" }}"
{{- with .Get "target" }} target="{{ . }}"{{ end -}}
{{- with .Get "rel" }} rel="{{ . }}"{{ end -}}
>
{{ else -}}
{{ if not (eq (.Get "lightbox") "none") }}<a href="#{{ $id }}">{{ end }}
{{- end -}}
<!-- THUMBNAIL -->
<img src="{{ $thumb | relURL }}" class="snap-thumb"
{{- with .Get "alt" }} alt="{{ . }}"{{ end -}}
{{- with .Get "title" }} title="{{ . }}"{{ end -}}
{{- with .Get "height" }} height="{{ . }}"{{ end -}}
{{- with .Get "width" }} width="{{ . }}"{{ end -}}
/>
{{- if or (.Get "caption") (.Get "attr")}}
<figcaption>
{{- .Get "caption" | markdownify -}}
{{- with .Get "attr" }} {{ . | markdownify}}{{ end -}}
</figcaption>
{{- end }}
</a>
<!-- FULL IMAGE; HIDDEN -->
{{- if not (eq (.Get "lightbox") "none") -}}
<div class="snap-lightbox" id="{{ $id }}">
<a href="#_" class="snap-lightbox-close"></a>
<div class="snap-lightbox-inner">
<img src="{{ .Get "src" }}" {{ with .Get "alt" }}alt="{{ . }}"{{ end }} />
<p>
{{- if or (.Get "caption") (.Get "attr") -}}
{{- .Get "caption" | markdownify -}}
{{- with .Get "attrlink" }} <a href="{{ . }}">{{- end -}}
{{- with .Get "attr" }} {{ . | markdownify}}{{ end -}}
{{- with .Get "attrlink" }}</a>{{- end -}}
{{- end }}
</p>
</div>
<a href="#_" class="snap-lightbox-close-button"><i class="snap-lightbox-x"></i></a>
{{- if .Parent -}}
{{- if not (eq $figid 1) -}}
<a href="#{{ $previd }}" class="snap-lightbox-prev"><i class="snap-lightbox-arrow"></i></a>
{{- else -}}
<span class="snap-lightbox-prev"><i class="snap-lightbox-arrow"></i></span>
{{- end -}}
<!-- TODO: Somehow try to identify whether this is the last element in the gallery -->
<a href="#{{ $nextid }}" class="snap-lightbox-next"><i class="snap-lightbox-arrow"></i></a>
{{- end }}
</div>
{{- end -}}
</div>
</figure>

View File

@@ -0,0 +1,68 @@
<!-- Images used to open the lightbox -->
<div class="row">
<div class="column">
<img src="img1.jpg" onclick="openModal();currentSlide(1)" class="hover-shadow">
</div>
<div class="column">
<img src="img2.jpg" onclick="openModal();currentSlide(2)" class="hover-shadow">
</div>
<div class="column">
<img src="img3.jpg" onclick="openModal();currentSlide(3)" class="hover-shadow">
</div>
<div class="column">
<img src="img4.jpg" onclick="openModal();currentSlide(4)" class="hover-shadow">
</div>
</div>
<!-- The Modal/Lightbox -->
<div id="myModal" class="modal">
<span class="close cursor" onclick="closeModal()">&times;</span>
<div class="modal-content">
<div class="mySlides">
<div class="numbertext">1 / 4</div>
<img src="img1.jpg" style="width:100%">
</div>
<div class="mySlides">
<div class="numbertext">2 / 4</div>
<img src="img2.jpg" style="width:100%">
</div>
<div class="mySlides">
<div class="numbertext">3 / 4</div>
<img src="img3.jpg" style="width:100%">
</div>
<div class="mySlides">
<div class="numbertext">4 / 4</div>
<img src="img4.jpg" style="width:100%">
</div>
<!-- Next/previous controls -->
<a class="prev" onclick="plusSlides(-1)">&#10094;</a>
<a class="next" onclick="plusSlides(1)">&#10095;</a>
<!-- Caption text -->
<div class="caption-container">
<p id="caption"></p>
</div>
<!-- Thumbnail image controls -->
<div class="column">
<img class="demo" src="img1.jpg" onclick="currentSlide(1)" alt="Nature">
</div>
<div class="column">
<img class="demo" src="img2.jpg" onclick="currentSlide(2)" alt="Snow">
</div>
<div class="column">
<img class="demo" src="img3.jpg" onclick="currentSlide(3)" alt="Mountains">
</div>
<div class="column">
<img class="demo" src="img4.jpg" onclick="currentSlide(4)" alt="Lights">
</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<!--
SPDX-FileCopyrightText: 2020 Max Mehl <mail@mehl.mx>
SPDX-License-Identifier: MIT
-->
<!-- count how many times we've called this shortcode; load the css if it's the first time -->
{{- .Page.Scratch.Add "gallery" 1 -}}
<div class="snap-gallery">
{{ .Inner }}
</div>
<!-- delete count of gallery figures (but not those of standalone figures -->
{{- .Page.Scratch.Delete "thisgalfig" -}}

View File

@@ -0,0 +1,59 @@
<!--
SPDX-FileCopyrightText: 2020 Max Mehl <mail@mehl.mx>
SPDX-License-Identifier: MIT
-->
<!-- This file is a variation of figure.html specifically to load all
files from a directory. Assumes that is has the gallery shortcode
as parent
-->
<!-- count how many times we've called this shortcode; load the css if it's the first time -->
{{- if not ($.Page.Scratch.Get "figurecount") }}<link rel="stylesheet" href="/css/snap-gallery.css" />{{ end }}
{{- $.Page.Scratch.Add "figurecount" 1 -}}
{{- $files := readDir (print "/static" (.Get "srcdir")) -}}
{{- $maxid := len $files -}}
{{- range $files -}}
{{ $src := (print ($.Get "srcdir") "/" .Name) }}
{{- $thumb := $src -}}
<!-- set unique ID for gallery and figure -->
{{- $figid := "" -}}
{{- $galid := "" -}}
{{- $id := "" -}}
{{- $previd := "" -}}
{{- $nextid := "" -}}
{{- $galid = $.Page.Scratch.Get "gallery" -}}
{{- if not $galid }}{{ $galid = 0 }}{{ end -}}
{{- $galid = add $galid 1 -}}
{{- $.Page.Scratch.Add "thisgalfig" 1 -}}
{{- $figid = $.Page.Scratch.Get "thisgalfig" -}}
{{- $id = print "gal" $galid "-" "fig" $figid -}}
{{- $previd = print "gal" $galid "-" "fig" (sub $figid 1) -}}
{{- $nextid = print "gal" $galid "-" "fig" (add $figid 1) -}}
<figure>
<!-- THUMBNAIL -->
<a href="#{{ $id }}">
<img src="{{ $thumb | relURL }}" class="snap-thumb" />
</a>
<!-- FULL IMAGE; HIDDEN -->
<div class="snap-lightbox" id="{{ $id }}">
<a href="#_" class="snap-lightbox-close"></a>
<div class="snap-lightbox-inner">
<img src='{{ $src }}' />
</div>
<a href="#_" class="snap-lightbox-close-button"><i class="snap-lightbox-x"></i></a>
{{- if not (eq $figid 1) -}}
<a href="#{{ $previd }}" class="snap-lightbox-prev"><i class="snap-lightbox-arrow"></i></a>
{{- else -}}
<span class="snap-lightbox-prev"><i class="snap-lightbox-arrow"></i></span>
{{- end -}}
{{- if not (eq $figid $maxid) -}}
<a href="#{{ $nextid }}" class="snap-lightbox-next"><i class="snap-lightbox-arrow"></i></a>
{{- else -}}
<span class="snap-lightbox-next"><i class="snap-lightbox-arrow"></i></span>
{{- end }}
</div>
</figure>
{{ end }}

View File

@@ -1,87 +1,55 @@
<!--
SPDX-FileCopyrightText: 2024 Max Mehl <https://mehl.mx>
SPDX-License-Identifier: MIT
-->
{{/* Count number of gallery on page */}}
{{- $.Page.Scratch.Add "gallerycount" 1 -}}
{{- $galno := $.Page.Scratch.Get "gallerycount" -}}
{{/* Load CSS/JS and make sure it is only loaded once */}}
{{- if eq ($.Page.Scratch.Get "gallerycount") 1 -}}
{{- with resources.Get "scss/snap-gallery.scss" | toCSS -}}
{{/* TODO: Ensure CSS/JS is only loaded once */}}
{{- with resources.Get "scss/snap-gallery.scss" | toCSS }}
<link rel="stylesheet" href="{{ .RelPermalink }}" crossorigin="anonymous">
{{- end -}}
<script src="/js/snap-gallery.js"></script>
{{- end -}}
{{- end }}
{{/* Initialise index of this gallery */}}
<script>imageIndex[{{ $galno }}] = 1;</script>
{{/* Initialise variables */}}
{{ $imgs := slice }}
{{/* Initialise variables holding image paths and extension */}}
{{- $imgs_collect := slice -}}
{{- $imgs := slice -}}
{{- $img_exts := slice ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" -}}
{{/* Get/sanitise image paths */}}
{{ if .Params.isdir }}
{{/* Get images from folder, put into map */}}
{{ $imgdir := print "/static/" .Params.src }}
{{- range readDir $imgdir -}}
{{ $imgs = $imgs | append (print $.Params.src "/" .Name ) }}
{{ end }}
{{ else }}
{{/* Get images from src Param, separated by comma */}}
{{ range (split .Params.src ",") }}
{{ $imgs = $imgs | append (trim . " ") }}
{{ end }}
{{ end }}
{{/* Get information from optional metadata file */}}
{{- $metadata := dict -}}
{{- with .Params.metadata -}}
{{- $metadata = index $.Site.Data . -}}
{{- end -}}
{{/* Get images from src Param, separated by comma */}}
{{- range (split .Params.src ",") -}}
{{- $img := (trim . " ") -}}
{{- $img_static := print "/static/" $img -}}
{{/* Only proceed when path exists */}}
{{- if os.FileExists $img_static -}}
{{/* If current item is a directory, range each of them, and add with full path to the slice */}}
{{- if (os.Stat $img_static).IsDir -}}
{{- range readDir $img_static -}}
{{- $imgs_collect = $imgs_collect | append (path.Join $img .Name ) -}}
{{- end -}}
{{/* If a single file, just add it to the slice */}}
{{- else -}}
{{- $imgs_collect = $imgs_collect | append $img -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/* Filter collected files, store them as slice of dicts/maps */}}
{{- range $imgs_collect -}}
{{/* Only process files if they are recognised as an image */}}
{{- if in $img_exts (lower (path.Ext .)) -}}
{{/* Create a dict holding path and optional metadata */}}
{{- $imgdict := dict "src" . -}}
{{/* If metadata for this image path found, add all of it to dict */}}
{{- $img_metadata := where $metadata "src" . -}}
{{- with $img_metadata -}}
{{- $imgdict = merge $imgdict (index . 0) -}}
{{- end -}}
{{/* Add final dict to slice */}}
{{- $imgs = $imgs | append $imgdict -}}
{{- end -}}
{{- end -}}
{{/* Define cursor when hovering over images, depending on lightbox status */}}
{{- $lightbox := (default true .Params.lightbox) -}}
{{- $cursor := "auto" -}}
{{- if $lightbox }}{{ $cursor = "zoom-in" }}{{ end -}}
{{/* Visible images in separate modes */}}
{{- $mode := default "gallery" .Params.mode -}}
{{/* Gallery mode */}}
{{- if eq $mode "gallery" -}}
{{- partial "gallery" (dict "columns" .Params.columns "minwidth" .Params.minwidth "imgs" $imgs "galno" $galno "cursor" $cursor "aspectratio" .Params.aspectratio) -}}
{{/* Slideshow mode */}}
{{- else if eq $mode "slideshow" -}}
{{- partial "slideshow" (dict "imgs" $imgs "galno" $galno "width" .Params.slideshowwidth "cursor" $cursor "aspectratio" .Params.aspectratio) -}}
{{/* Set autorotate timer for slideshow, if configured (default: yes) */}}
{{- if (default true .Params.slideshowrotate) -}}
<script>const autoSlideshow = setInterval(moveSlideshowItem, {{ default 5000 .Params.slideshowrotate_timer }}, {{ $galno }}, 1, "auto");</script>
{{- end -}}
{{- end -}}
{{/* Visible images */}}
<div class="snap-wrapper">
{{ range $i, $img := $imgs }}
<div class="snap-image">
<img src="{{ $img }}" onclick="openModal();currentSlide({{ add $i 1 }});" class="hover-shadow">
</div>
{{ end }}
</div>
{{/* The Modal/Lightbox */}}
{{ if $lightbox -}}
{{- partial "lightbox" (dict "galno" $galno "imgs" $imgs) -}}
{{- end -}}
<div class="snap-lightbox">
{{ range $i, $img := $imgs }}
<div class="snap-lightbox-inner">
<div class="numbertext">{{ add $i 1 }} / {{ len $imgs }}</div>
<img src="{{ $img }}" style="width:100%">
</div>
{{ end }}
<!-- Close and Next/previous controls -->
<span class="snap-close" onclick="closeModal()">&times;</span>
<a class="snap-lightbox-prev" onclick="plusSlides(-1)"><i class="snap-lightbox-arrow"></i></a>
<a class="snap-lightbox-next" onclick="plusSlides(1)"><i class="snap-lightbox-arrow"></i></a>
{{/* <a class="prev" onclick="plusSlides(-1)">&#10094;</a>
<a class="next" onclick="plusSlides(1)">&#10095;</a> */}}
{{/* <!-- Caption text -->
<div class="caption-container">
<p id="caption"></p>
</div> */}}
</div>
<script src="/js/snap-gallery.js"></script>

View File

@@ -1,80 +1,33 @@
/*
SPDX-FileCopyrightText: 2023 Max Mehl <https://mehl.mx>
SPDX-License-Identifier: MIT
*/
// Open the Modal
function openModal() {
document.getElementsByClassName("snap-lightbox")[0].style.display = "block";
}
// Variables
var lightbox_baseid = "snap-lightbox-";
var slideshow_baseid = "snap-slideshow-";
//var imageIndex = {1: 1, 2: 1, 3: 1};
var imageIndex = {}
// Close the Modal
function closeModal() {
document.getElementsByClassName("snap-lightbox")[0].style.display = "none";
}
// Open the Lightbox
function openLightbox(id) {
document.getElementById(lightbox_baseid + id).style.display = "block";
var slideIndex = 1;
showSlides(slideIndex);
// Kill automatic slideshow when lightbox opened
try {
clearInterval(autoSlideshow);
} catch (e) {
console.log("Lightbox error: " + e)
// Next/previous controls
function plusSlides(n) {
showSlides(slideIndex += n);
}
// Thumbnail image controls
function currentSlide(n) {
showSlides(slideIndex = n);
}
function showSlides(n) {
var i;
var slides = document.getElementsByClassName("snap-lightbox-inner");
if (n > slides.length) { slideIndex = 1 }
if (n < 1) { slideIndex = slides.length }
for (i = 0; i < slides.length; i++) {
slides[i].style.display = "none";
}
}
// Close the Lightbox
function closeLightbox(id) {
document.getElementById(lightbox_baseid + id).style.display = "none";
}
// Move lightbox to the specified item
function openLightboxItem(id, n) {
showItem(lightbox_baseid, id, imageIndex[id] = n, ".snap-lightbox-inner");
}
// Next/previous controls for lightbox
function moveLightboxItem(id, n) {
showItem(lightbox_baseid, id, imageIndex[id] += n, ".snap-lightbox-inner");
}
// Next/previous controls for slideshow
function moveSlideshowItem(id, n, mode) {
showItem(slideshow_baseid, id, imageIndex[id] += n, ".snap-image");
// Kill automatic slideshow once the slideshow has been moved manually
if (mode !== "auto") {
clearInterval(autoSlideshow);
}
}
// In the slideshow or lightbox, make a specific image visible, make others hidden
function showItem(baseId, id, n, className) {
// Get elements that shall be rotated
const element = document.getElementById(baseId + id);
const items = element.querySelectorAll(className);
// Increment item index
const updateIndex = () => {
if (n > items.length) {
imageIndex[id] = 1;
}
if (n < 1) {
imageIndex[id] = items.length;
}
};
// hide all selected elements
const hideAllItems = () => {
for (let i = 0; i < items.length; i++) {
items[i].style.display = "none";
}
};
// make desired item visible
const showCurrentItem = () => {
items[imageIndex[id] - 1].style.display = "inline-block";
};
updateIndex();
hideAllItems();
showCurrentItem();
slides[slideIndex - 1].style.display = "inline-block";
}