Compare commits

...

40 Commits

Author SHA1 Message Date
Mark Dumay
82d426c14b Merge pull request #951 from gethinode/image
Fix linting issue
2024-05-25 19:43:35 +02:00
Mark Dumay
a2c5073a3d Merge branch 'main' into image 2024-05-25 19:36:59 +02:00
Mark Dumay
11077b9178 Merge pull request #945 from gethinode/hugo-mod-dependencies
Update Hugo module dependencies
2024-05-25 19:36:37 +02:00
Mark Dumay
6e8011f111 Fix linting issue 2024-05-25 19:36:08 +02:00
Mark Dumay
3267f1165e Merge branch 'main' into hugo-mod-dependencies 2024-05-25 19:20:39 +02:00
Mark Dumay
e46d4764fd Merge pull request #950 from gethinode/image
Extend image adapters
2024-05-25 19:20:10 +02:00
Mark Dumay
2fb4829f68 Merge branch 'main' into image 2024-05-25 19:12:59 +02:00
Mark Dumay
6009c45e5a Bump package release 2024-05-25 19:11:21 +02:00
Mark Dumay
69505a1dca Make image adapters configurable 2024-05-25 19:10:53 +02:00
Mark Dumay
c195d903c7 Add examples 2024-05-25 19:10:32 +02:00
Mark Dumay
07c1422bbb Update build stats 2024-05-25 19:10:08 +02:00
Mark Dumay
6be688515d Adjust CSP 2024-05-25 19:10:00 +02:00
Mark Dumay
5d83f12b35 Add imagekit.io adapter 2024-05-25 19:09:15 +02:00
Mark Dumay
008c6a099a Add imgix adapter 2024-05-25 19:09:05 +02:00
Mark Dumay
125b72177c Test if image adapter is available 2024-05-25 19:08:43 +02:00
markdumay
d36ce74eae fix: update Hugo module dependencies 2024-05-23 03:05:50 +00:00
Mark Dumay
2433fa8a66 Merge pull request #943 from gethinode/image
Refine CDN image handling
2024-05-20 15:26:53 +02:00
Mark Dumay
adbff26d18 Merge branch 'main' into image 2024-05-20 14:49:16 +02:00
Mark Dumay
bdcdeb9b55 Bump package release 2024-05-20 14:48:39 +02:00
Mark Dumay
0636708dac Add DAM provider config 2024-05-20 14:48:10 +02:00
Mark Dumay
eb65b94ef4 Fix fallback URL of Cloudinary images 2024-05-20 14:47:10 +02:00
Mark Dumay
01b51f6ef1 Provide default dimensions when site data is unavailable 2024-05-20 14:46:30 +02:00
Mark Dumay
89b81cf1b8 Handle undefined dimensions 2024-05-20 14:46:06 +02:00
Mark Dumay
54396f4dc0 Fix dimensions rounding errors 2024-05-20 13:23:26 +02:00
Mark Dumay
0660a4c6e9 Merge pull request #942 from gethinode/image
Fix conversion of image dimensions
2024-05-20 09:00:30 +02:00
Mark Dumay
cba7768e39 Merge branch 'main' into image 2024-05-20 08:45:01 +02:00
Mark Dumay
a040ab42c3 Bump package release 2024-05-20 08:44:27 +02:00
Mark Dumay
fa6c01a380 Fix conversion of image dimensions 2024-05-20 08:43:49 +02:00
Mark Dumay
ed6c415bb2 Merge pull request #940 from gethinode/image
Add support for image CDNs
2024-05-19 19:03:48 +02:00
Mark Dumay
59c71d9bb2 Add initial alpha release 2024-05-19 18:56:57 +02:00
Mark Dumay
7dde1b3bb7 Merge branch 'main' into image 2024-05-19 18:55:48 +02:00
Mark Dumay
1d994fa1fe Add support for cloudinary images 2024-05-19 18:55:16 +02:00
Mark Dumay
5a9191fea5 Merge pull request #939 from gethinode/develop
Bump package release
2024-05-19 12:22:30 +02:00
Mark Dumay
790b3ac3d2 Merge branch 'main' into develop 2024-05-19 12:10:45 +02:00
Mark Dumay
f298dd2979 Bump package release 2024-05-19 12:10:20 +02:00
Mark Dumay
df7a7e2d3d Merge pull request #938 from vinser/link-external-attr
Add 'external' param to link shortcut
2024-05-19 11:59:18 +02:00
vinser
e28ca112de Add 'external' param to link shortcut 2024-05-19 13:28:21 +05:00
Mark Dumay
a34dc0f4a0 Merge pull request #936 from gethinode/develop
Fix link to Hugo download
2024-05-18 05:53:54 +02:00
Mark Dumay
3eb682b0d9 Merge branch 'main' into develop 2024-05-18 05:48:17 +02:00
Mark Dumay
d824ab39dd Fix link to Hugo download 2024-05-18 05:47:52 +02:00
27 changed files with 641 additions and 247 deletions

View File

@@ -69,7 +69,7 @@ Hinode is a [Hugo theme that uses modules][hugo_modules] to install and maintain
The installation instructions in this readme install Hinode as a regular Hugo theme. Hinode requires the following software to be installed on your local machine.
- [Go binary][golang_download]
- [Hugo][nodejs] (extended version)
- [Hugo][hugo_download] (extended version)
[Git][git_download] is recommended, but is not a strict requirement.

View File

@@ -68,8 +68,7 @@
}
}
.card-zoom card-body-link,
.card-body-link {
.card-zoom .card-body-link {
&:hover,
&:focus {
color: shade-color($primary, $link-shade-percentage) if($enable-important-utilities, !important, null);
@@ -78,8 +77,7 @@
@if $enable-dark-mode {
@include color-mode(dark) {
.card-zoom card-body-link,
.card-body-link {
.card-zoom .card-body-link {
&:hover,
&:focus {
color: $primary-bg-subtle-dark if($enable-important-utilities, !important, null);

View File

@@ -36,6 +36,16 @@
raw = false
# toml-docs-end modules
# toml-docs-start images
[images]
[images.cloudinary]
host = "cloudinary"
[images.imagekit]
host = "imagekit"
[images.imgix]
host = "imgix"
# toml-docs-end images
# toml-docs-start debugging
[debugging]
showJS = false

View File

@@ -15,7 +15,7 @@ for = '/**'
https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com; \
font-src 'self' https://fonts.gstatic.com; \
frame-src 'self' https://www.youtube-nocookie.com https://www.youtube.com; \
img-src 'self' data: https://i.vimeocdn.com https://i.ytimg.com https://*.google-analytics.com https://*.googletagmanager.com https://tile.openstreetmap.org; \
img-src 'self' data: https://*.imgix.net https://*.imagekit.io https://*.cloudinary.com https://i.vimeocdn.com https://i.ytimg.com https://*.google-analytics.com https://*.googletagmanager.com https://tile.openstreetmap.org; \
manifest-src 'self'; \
media-src 'self' \
"""

48
data/dimensions.yml Normal file
View File

@@ -0,0 +1,48 @@
- ratio: 4x3
dimensions:
- 576x432
- 768x576
- 992x744
- 1200x900
- 1400x1050
- 2800x2100
- ratio: 3x2
dimensions:
- 576x384
- 768x512
- 992x661
- 1200x800
- 1400x933
- 2800x1867
- ratio: 1x1
dimensions:
- 576x576
- 768x768
- 992x992
- 1200x1200
- 1400x1400
- 2800x2800
- ratio: 16x9
dimensions:
- 576x324
- 768x432
- 992x558
- 1200x675
- 1400x788
- 2800x1575
- ratio: 21x9
dimensions:
- 576x247
- 768x329
- 992x425
- 1200x514
- 1400x600
- 2800x1200
- ratio: auto
dimensions:
- 576
- 768
- 992
- 1200
- 1400
- 2800

View File

@@ -0,0 +1,62 @@
comment: >-
Defines the interface to be implemented by a CDN image adapter.
arguments:
host:
type: string
optional: true
comment: >-
Host of an URL. For example, the host of the URL
'https://example.com/first/second/third.webp' equals 'example.com'.
dir:
type: string
optional: true
comment: >-
All but the last element of an URL extension. For example, the dir of the
URL 'https://example.com/first/second/third.webp' equals '/first/second/'.
file:
type: string
optional: false
comment: >-
The last element of an URL extension. For example, the file of the
URL 'https://example.com/first/second/third.webp' equals 'third.webp'.
absoluteURL:
type: string
optional: true
default: false
comment: >-
Defines if a local image should use absolute instead of relative paths.
img:
type: "*resources.resourceAdapter"
optional: true
comment: >-
Image resource to process. Must be set when handling local images.
format:
type: select
optional: true
comment: >-
Image format; leave empty for an auto format (if supported) or default
format (usually jpg).
options:
values:
- png
- jpg
- gif
- tiff
- bmp
- webp
transform:
type: select
optional: false
comment: Image transformation.
options:
values:
- fill
- fit
height:
type: int
optional: false
comment: Height of the image in pixels.
width:
type: int
optional: false
comment: Width of the image in pixels.

View File

@@ -56,6 +56,12 @@ arguments:
Flag to indicate if the retrieved title (e.g. no inner text is provided)
of an internal link should use its original case. If false, the title is
set to lower case.
external:
type: bool
optional: true
default: false
comment: >-
Flag to indicate if a link that contains baseURL host should be forced as external.
class:
type: string
optional: true

View File

@@ -70,6 +70,14 @@
[messages]
placement = "bottom-right"
[dam]
dimensions = "dimensions.yml"
[[dam.providers]]
name = "Cloudinary"
pattern = "cloudinary"
adapter = "assets/adapters/cloudinary.html"
[sharing]
enabled = true
sort = "weight"

View File

@@ -15,7 +15,7 @@ for = '/**'
https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com; \
font-src 'self' https://fonts.gstatic.com; \
frame-src 'self' https://www.youtube-nocookie.com https://www.youtube.com; \
img-src 'self' data: https://i.vimeocdn.com https://i.ytimg.com https://*.google-analytics.com https://*.googletagmanager.com https://tile.openstreetmap.org; \
img-src 'self' data: https://*.imgix.net https://*.imagekit.io https://*.cloudinary.com https://i.vimeocdn.com https://i.ytimg.com https://*.google-analytics.com https://*.googletagmanager.com https://tile.openstreetmap.org; \
manifest-src 'self'; \
media-src 'self' \
"""

View File

@@ -0,0 +1,42 @@
---
author: Mark Dumay
title: Local and remote images
date: 2024-05-19
description: Include responsive images from local sources and selected CDN image providers.
tags: ["blog"]
thumbnail:
url: https://assets.imgix.net/examples/bluehat.jpg
---
## Cloudinary
As an example, the following shortcode displays an image with rounded corners and a 21x9 aspect ratio.
<!-- markdownlint-disable MD037 -->
{{< example lang="hugo" >}}
{{</* image src="https://res.cloudinary.com/demo/image/upload/dog.webp"
ratio="21x9" caption="Cloudinary image" class="rounded" */>}}
{{< /example >}}
<!-- markdownlint-enable MD037 -->
## ImageKit.io
As an example, the following shortcode displays an image with rounded corners and a 21x9 aspect ratio.
<!-- markdownlint-disable MD037 -->
{{< example lang="hugo" >}}
{{</* image src="https://ik.imagekit.io/demo/default-image.jpg"
ratio="21x9" caption="ImageKit.io image" class="rounded" */>}}
{{< /example >}}
<!-- markdownlint-enable MD037 -->
## Imgix
As an example, the following shortcode displays an image with rounded corners and a 21x9 aspect ratio.
<!-- markdownlint-disable MD037 -->
{{< example lang="hugo" >}}
{{</* image src="https://assets.imgix.net/examples/bluehat.jpg"
ratio="21x9" caption="imgix image" class="rounded" */>}}
{{< /example >}}
<!-- markdownlint-enable MD037 -->

View File

@@ -492,6 +492,7 @@
"carrousel",
"carte",
"chronologie",
"cloudinary",
"collapse",
"collapse-1",
"command-prompt",
@@ -510,6 +511,8 @@
"groupe-de-cartes",
"icon",
"image",
"imagekitio",
"imgix",
"indicateur-de-chargement",
"infobulle",
"invite-de-commandes",

4
go.mod
View File

@@ -9,8 +9,8 @@ require (
github.com/gethinode/mod-fontawesome v1.8.2 // indirect
github.com/gethinode/mod-katex v1.0.9 // indirect
github.com/gethinode/mod-leaflet v1.0.1 // indirect
github.com/gethinode/mod-lottie v1.4.6 // indirect
github.com/gethinode/mod-utils/v2 v2.2.2 // indirect
github.com/gethinode/mod-lottie v1.4.7 // indirect
github.com/gethinode/mod-utils/v2 v2.3.0 // indirect
github.com/nextapps-de/flexsearch v0.0.0-20240110101704-4c3966709f85 // indirect
github.com/twbs/bootstrap v5.3.3+incompatible // indirect
)

4
go.sum
View File

@@ -160,6 +160,8 @@ github.com/gethinode/mod-lottie v1.4.4 h1:Lv7J+lAMx++aK9h0L7vAgsjyOZgrjux4xPz4Tp
github.com/gethinode/mod-lottie v1.4.4/go.mod h1:7tsZjlFgMlj2iWBIS9uOtHHsCrfx9W7S8OsBrZeSVGU=
github.com/gethinode/mod-lottie v1.4.6 h1:L6zvfbq+FQUWTG3X/wWFCvb91WOxJuIkJUlZOd+QJ1Q=
github.com/gethinode/mod-lottie v1.4.6/go.mod h1:1/+7U/Q5IuaqkzTrvXK1CnPE7ANnTRsG5k+KaXQUQwE=
github.com/gethinode/mod-lottie v1.4.7 h1:5Hes0xY/l8ygzJ8YQIzdqCmvGrDQs8D8qZ/vR1fQh/Q=
github.com/gethinode/mod-lottie v1.4.7/go.mod h1:FvcG3NjXOBOnwou0QvXYNPHpybxwT6yxmSh4N+nBD9s=
github.com/gethinode/mod-utils v1.0.0 h1:cqHm2xS5uDiJzRm1KfHaNbq6uMVDKLhQa8/BuTZ1nhY=
github.com/gethinode/mod-utils v1.0.0/go.mod h1:ONJm3pHCq7nvaPNjusLZNCeCbhOhSBH4HVKHwK1FdYE=
github.com/gethinode/mod-utils v1.0.1 h1:jhZGlGFHHL1f5HXbBMXfiZ2gCz4TVafAzjnRPTIBSEE=
@@ -184,6 +186,8 @@ github.com/gethinode/mod-utils/v2 v2.1.1 h1:aLBcS9Zbx+dqnu5oBsTM01AkkN5nwwifgbV4
github.com/gethinode/mod-utils/v2 v2.1.1/go.mod h1:GTYeknoLujNjfDxI+V9Dcug26CYJSTJ0B/U2dagw9oY=
github.com/gethinode/mod-utils/v2 v2.2.2 h1:ZRxWIJKmZIDYsoMS3WhxxC+BWJrkd1dcZ8rz/cLZExc=
github.com/gethinode/mod-utils/v2 v2.2.2/go.mod h1:GTYeknoLujNjfDxI+V9Dcug26CYJSTJ0B/U2dagw9oY=
github.com/gethinode/mod-utils/v2 v2.3.0 h1:e3uhlAGasvXe+cgvcmzRe05Zo4UEx86uk0TbnxtyB6U=
github.com/gethinode/mod-utils/v2 v2.3.0/go.mod h1:GTYeknoLujNjfDxI+V9Dcug26CYJSTJ0B/U2dagw9oY=
github.com/nextapps-de/flexsearch v0.0.0-20230711092928-1243fd883ec3 h1:H/qVR5O4BXjRjD+5PZB+r4ug2BSJ2Of4RtwOntd+OKo=
github.com/nextapps-de/flexsearch v0.0.0-20230711092928-1243fd883ec3/go.mod h1:5GdMfPAXzbA2gXBqTjC6l27kioSYzHlqDMh0+wyx7sU=
github.com/nextapps-de/flexsearch v0.0.0-20240108021025-afd75f742f22 h1:re7L8FxbXQpnX8BgzkdUnDpsUmloGNyLmiy2ZCln8pg=

View File

@@ -0,0 +1,43 @@
<!--
Copyright © 2024 The Hinode Team / Mark Dumay. All rights reserved.
Use of this source code is governed by The MIT License (MIT) that can be found in the LICENSE file.
Visit gethinode.com/license for more details.
-->
{{ $error := false }}
<!-- Validate arguments -->
{{ if partial "utilities/IsInvalidArgs.html" (dict "structure" "image-adapter" "args" . "group" "partial") }}
{{ errorf "partial [assets/adapter/cloudinary.html] - Invalid arguments" -}}
{{ $error = true }}
{{ end }}
<!-- Initialize arguments -->
{{ $host := .host }}
{{ $dir := .dir }}
{{ $file := .file }}
{{ $format := .format }}
{{ $transform := .transform }}
{{ $height := .height }}
{{ $width := .width }}
{{ if eq $transform "fill" }}{{ $transform = "c_fill" }}{{ else }}{{ $transform = "c_fit" }}{{ end }}
{{ $element := "" }}
<!-- Split path between upload dir and sub dir -->
{{ $newdir := urls.JoinPath (index (split $dir "upload") 0) "upload" }}
{{ $file = urls.JoinPath (index (split $dir "upload") 1) $file }}
{{ $dir = $newdir }}
<!-- Generate image URL -->
{{ if not $error }}
{{ $operation := "" }}
{{ if $format }}
{{ $operation = printf "%s,h_%d,w_%d" $transform $height $width }}
{{ $file = printf "%s.%s" (strings.TrimSuffix (path.Ext $file) $file) $format }}
{{ else }}
{{ $operation = printf "f_auto,%s,h_%d,w_%d" $transform $height $width }}
{{ end }}
{{- $element = urls.JoinPath (slice "https://" $host $dir $operation $file) -}}
{{ end }}
{{ return $element }}

View File

@@ -0,0 +1,64 @@
<!--
Copyright © 2024 The Hinode Team / Mark Dumay. All rights reserved.
Use of this source code is governed by The MIT License (MIT) that can be found in the LICENSE file.
Visit gethinode.com/license for more details.
-->
{{ $error := false }}
<!-- Validate arguments -->
{{ if partial "utilities/IsInvalidArgs.html" (dict "structure" "image-adapter" "args" . "group" "partial") }}
{{ errorf "partial [assets/adapter/hugo.html] - Invalid arguments" -}}
{{ $error = true }}
{{ end }}
<!-- Initialize arguments -->
{{ $host := .host }}
{{ $dir := .dir }}
{{ $file := .file }}
{{ $img := .img }}
{{ $transform := .transform }}
{{ $height := .height }}
{{ $width := .width }}
{{ $element := "" }}
{{ $absoluteURL := .absoluteURL }}
{{ $url := urls.JoinPath $dir $file }}
{{ if $host }}
{{ $url = urls.JoinPath "https://" $host $url }}
{{ end }}
<!-- Initialize image if needed -->
{{ $format := .format | default "webp" }}
{{ if and (not $img) (ne $format "svg") }}
{{- $res := partial "utilities/GetImage.html" (dict "url" $url "page" page) -}}
{{ if $res }}
{{ $img = $res.resource }}
<!-- TODO: $res.mirror -->
{{ end }}
{{ if not $img }}
{{ errorf "partial [assets/adapter/hugo.html] - Expected 'img' argument" -}}
{{ $error = true }}
{{ end }}
{{ end }}
<!-- Process image and generate image URL -->
{{ if not $error }}
{{ $scaled := "" }}
{{ if eq $transform "fill" }}
{{- $scaled = $img.Fill (printf "%dx%d %s" $width $height $format) -}}
{{ else }}
{{- $scaled = $img.Fit (printf "%dx%d %s" $width $height $format) -}}
{{ end }}
{{- $clean := path.Ext $img.RelPermalink -}}
{{- $scaled = $scaled | resources.Copy (replace $img.RelPermalink $clean (printf "-%dx%d.%s" $width $height $format)) -}}
{{- if $absoluteURL -}}
{{- $element = $scaled.Permalink -}}
{{- else -}}
{{- $element = $scaled.RelPermalink -}}
{{- end -}}
{{ end }}
{{ return $element }}

View File

@@ -0,0 +1,48 @@
<!--
Copyright © 2024 The Hinode Team / Mark Dumay. All rights reserved.
Use of this source code is governed by The MIT License (MIT) that can be found in the LICENSE file.
Visit gethinode.com/license for more details.
-->
{{ $error := false }}
<!-- Validate arguments -->
{{ if partial "utilities/IsInvalidArgs.html" (dict "structure" "image-adapter" "args" . "group" "partial") }}
{{ errorf "partial [assets/adapter/imagekit.html] - Invalid arguments" -}}
{{ $error = true }}
{{ end }}
<!-- Initialize arguments -->
{{ $host := .host }}
{{ $dir := .dir }}
{{ $file := .file }}
{{ $format := .format }}
{{ $transform := .transform }}
{{ $height := .height }}
{{ $width := .width }}
{{ if eq $transform "fill" }}{{ $transform = "c-maintain_ratio" }}{{ else }}{{ $transform = "c-at_max" }}{{ end }}
{{ $element := "" }}
<!-- Validate image format is supported -->
{{ if and $format (not (in (slice "jpg" "jpeg" "webp" "avif" "png") $format)) }}
{{ warnf "Image format not supported by ImageKit.io: %s.%s" (path.BaseName $file) $format }}
{{ $format = "jpg" }}
{{ end }}
<!-- Split path between end point and image path -->
{{ $newdir := printf "/%s" (index (split $dir "/") 1) }}
{{ $file = urls.JoinPath (strings.TrimPrefix $newdir $dir) $file }}
{{ $dir = $newdir }}
<!-- Generate image URL -->
{{ if not $error }}
{{ $operation := "" }}
{{ if $format }}
{{ $operation = printf "tr:f-%s,%s,h-%d,w-%d" $format $transform $height $width }}
{{ else }}
{{ $operation = printf "tr:f-auto,%s,h-%d,w-%d" $transform $height $width }}
{{ end }}
{{- $element = urls.JoinPath (slice "https://" $host $dir $operation $file) -}}
{{ end }}
{{ return $element }}

View File

@@ -0,0 +1,43 @@
<!--
Copyright © 2024 The Hinode Team / Mark Dumay. All rights reserved.
Use of this source code is governed by The MIT License (MIT) that can be found in the LICENSE file.
Visit gethinode.com/license for more details.
-->
{{ $error := false }}
<!-- Validate arguments -->
{{ if partial "utilities/IsInvalidArgs.html" (dict "structure" "image-adapter" "args" . "group" "partial") }}
{{ errorf "partial [assets/adapter/imgix.html] - Invalid arguments" -}}
{{ $error = true }}
{{ end }}
<!-- Initialize arguments -->
{{ $host := .host }}
{{ $dir := .dir }}
{{ $file := .file }}
{{ $format := .format }}
{{ $transform := .transform }}
{{ $height := .height }}
{{ $width := .width }}
{{ if eq $transform "fill" }}{{ $transform = "crop" }}{{ else }}{{ $transform = "max" }}{{ end }}
{{ $element := "" }}
<!-- Validate image format is supported -->
{{ if and $format (not (in (slice "avif" "gif" "jp2" "jpg" "json" "jxr" "pjpg" "mp4" "png" "png8" "png32" "webm" "webp" "blurhash") $format)) }}
{{ warnf "Image format not supported by imgix: %s.%s" (path.BaseName $file) $format }}
{{ $format = "jpg" }}
{{ end }}
<!-- Generate image URL -->
{{ if not $error }}
{{ $operation := "" }}
{{ if $format }}
{{ $operation = printf "fm=%s&fit=%s&h=%d&w=%d" $format $transform $height $width }}
{{ else }}
{{ $operation = printf "f_auto&fit=%s&h=%d&w=%d" $transform $height $width }}
{{ end }}
{{- $element = printf "%s?%s" (urls.JoinPath (slice "https://" $host $dir $file)) $operation -}}
{{ end }}
{{ return $element }}

View File

@@ -0,0 +1,25 @@
<!--
Copyright © 2024 The Hinode Team / Mark Dumay. All rights reserved.
-->
{{ $ratio := .ratio }}
{{ $dim := "" }}
{{ $default := dict "4x3" "1400x1050" "3x2" "1400x933" "1x1" "1400x1400" "16x9" "1400x788" "21x9" "1400x600" "auto" "1400" }}
<!-- Initialize dimensions data - stripping unsupported file extension -->
{{ $config := "dimensions" }}
{{ with index site.Params "dam" }}{{ with index . "dimensions" }}{{ $config = . }}{{ end }}{{ end }}
{{ $config = path.Join (path.Dir $config) (path.BaseName $config) }}
{{ with index site.Data $config }}
{{ $matches := first 1 (where . "ratio" $ratio) }}
{{ if eq ($matches | len) 1 }}
{{ $dim = (index $matches 0).dimensions }}
{{ end }}
{{ end }}
{{ if not $dim }}
{{ $dim = slice (index $default $ratio) }}
{{ end }}
{{ return $dim }}

View File

@@ -0,0 +1,23 @@
{{- $dim := .dim -}}
{{- $dimensions := slice }}
{{- $dimensions = $dimensions | append $dim -}}
{{- $portraits := slice }}
{{- range $d := $dimensions -}}
{{- $p := split $d "x" }}
{{ if ne (len $p) 2 }}
{{- warnf "partial [assets/image.html] - Invalid dimension: %s" $d -}}
{{- $portraits = $portraits | append $d -}}
{{- else -}}
{{- $portraits = $portraits | append (printf "%sx%s" (index $p 1) (index $p 0)) -}}
{{- end -}}
{{- end -}}
{{- $ret := "" }}
{{- if reflect.IsSlice $dim -}}
{{ $ret = $portraits -}}
{{- else if gt (len $portraits) 0 -}}
{{- $ret = index $portraits 0 -}}
{{- end -}}
{{- return $ret -}}

View File

@@ -0,0 +1,127 @@
<!-- Initialize arguments -->
{{- $page := .page -}}
{{- $ratio := .ratio -}}
{{- $portrait := .portrait -}}
{{- $wrapper := .wrapper -}}
{{- $class := .class -}}
{{- $title := .title -}}
{{- $caption := .caption -}}
{{- $figclass := .figclass -}}
{{- $lazy := .lazy -}}
{{- $priority := .priority -}}
{{- $sizes := .sizes -}}
{{- $absoluteURL := .absoluteURL -}}
{{- $url := .url -}}
{{- $mode := .mode -}}
{{- $modes := .modes -}}
{{- $fallbackURL := "" -}}
{{- $anchor := "" -}}
{{- $imgset := "" -}}
{{- $isVector := false -}}
<!-- Split url into base and anchor when applicable (only relevant for vector images) -->
{{- $segments := split $url "#" -}}
{{- if gt (len $segments) 2 -}}
{{- errorf "Invalid path or url: %q" $url -}}
{{- else if eq (len $segments) 2 }}
{{- $url = index $segments 0 -}}
{{- $anchor = index $segments 1 -}}
{{- end -}}
<!-- Identify image provider -->
{{ $hook := "" }}
{{ range $provider, $val := site.Params.images }}
{{ if not $hook }}
{{ with index $val "host" }}
{{ if (findRE . (urls.Parse $url).Hostname) }}
{{ $hook = $provider }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ if not $hook }}{{ $hook = "hugo" }}{{ end }}
<!-- Define image dimensions -->
{{ $dims := slice }}
{{- $res := "" -}}
{{- $img := "" -}}
{{ $transform := "" }}
{{- if hasSuffix $url "svg" -}}
{{- $res = partial "utilities/GetResource.html" (dict "url" $url "page" $page) -}}
{{ if not $res }}
{{- if not (fileExists (path.Join "/static" $url)) -}}
{{ warnf "Cannot find vector image resource: %q" $url -}}
{{ else }}
{{ $width := string (partial "utilities/GetWidth.html" (dict "path" $url "height" 500)) }}
{{ $dims = $dims | append (printf "%sx500" $width) }}
{{ end }}
{{ end }}
{{ else if $ratio }}
{{ $transform = "fill" }}
{{ $dims = partial "assets/helpers/GetDimension.html" (dict "ratio" $ratio) }}
{{ if not $dims }}{{ errorf "partial [assets/image.html] - Cannot find dimension data: %s" $ratio }}{{ end }}
{{ else }}
{{ $transform = "fit" }}
{{- $res := partial "utilities/GetImage.html" (dict "url" $url "page" $page) -}}
{{ with $res }}
{{ $img = $res.resource }}
{{ if $res.mirror }}{{ $class = printf "%s mirrorred" $class }}{{ end }}
{{ end }}
{{ $widths := partial "assets/helpers/GetDimension.html" (dict "ratio" "auto") }}
{{ range $width := $widths -}}
{{- $dims = $dims | append (printf "%dx%d" (int $width) (int (math.Round (mul (div (float $width) $img.Width) $img.Height)))) -}}
{{- end -}}
{{ end }}
<!-- Derive image width and height -->
{{ $width := "" }}
{{ $height := "" }}
{{ with $dims }}
{{ range $dim := (. | last 1) }}
{{ $width = (int (index (split $dim "x") 0)) }}
{{ $height = (int (index (split $dim "x") 1)) }}
{{ end }}
{{ end }}
<!-- Generate image urls -->
{{ if hasSuffix $url "svg" }}
{{- $fallbackURL = $url -}}
{{ else }}
{{- $fallbackURL = partial "assets/helpers/image-set.html" (dict "url" $url "img" $img "dims" ($dims | last 1) "transform" $transform "hook" $hook "format" "jpg" "includeWidth" false) -}}
{{- $imgset = partial "assets/helpers/image-set.html" (dict "url" $url "img" $img "dims" $dims "transform" $transform "hook" $hook) -}}
{{ end }}
<!-- Add color modes -->
{{- range $none := $modes -}}
{{- if ne $none $mode -}}
{{- $wrapper = printf "%s d-none-%s" (or $wrapper "") $none -}}
{{- end -}}
{{- end -}}
<!-- Generate image definition -->
{{- if $caption -}}
<figure {{ with $wrapper }}class="{{ . }}"{{ end }}>
{{ end }}
<div class="{{ if not $caption }}{{ $wrapper }}{{ end }}">
{{- if not $anchor -}}
<img class="img-fluid {{ $class }}"
src="{{ $fallbackURL }}"
{{ if $lazy }}loading="lazy"{{ end }}
{{ with $priority }}fetchpriority="{{ . }}"{{ end }}
{{ with $imgset -}}srcset="{{ . }}" sizes="{{ $sizes }}"{{- end }}
{{ with $height }}height="{{ . }}"{{ end }}
{{ with $width }}width="{{ . }}"{{ end }}
{{ with (or $title $caption) }}alt="{{ . }}"{{ end }}>
{{- else }}
<svg class="{{ $class }}">
<use href="{{ $fallbackURL }}#{{ $anchor }}"></use>
</svg>
{{ end }}
</div>
{{- if $caption -}}
<figcaption class="figure-caption{{ with $figclass }} {{ . }}{{ end }}">{{ $caption | safeHTML }}</figcaption>
</figure>
{{- end -}}

View File

@@ -0,0 +1,46 @@
{{- $url := .url -}}
{{ $img := .img }}
{{ $absoluteURL := .absoluteURL }}
{{ $dims := .dims }}
{{- $ratio := .ratio -}}
{{ $hook := .hook }}
{{ $transform := .transform }}
{{ $format := .format }}
{{ $includeWidth := .includeWidth | default true }}
{{ $host := (urls.Parse $url).Hostname }}
{{ $dir := (urls.Parse $url).Path }}
{{ $file := index ((split $dir "/") | collections.Reverse) 0 }}
{{ $dir = strings.TrimSuffix $file $dir }}
{{ $adapter := (printf "assets/adapters/%s.html" $hook) }}
{{ if not (fileExists (path.Join "/layouts/partials" $adapter)) }}
{{ warnf "Cannot find adapter: %s" (path.Join "/layouts/partials" $adapter) }}
{{ $hook = "hugo" }}
{{ $adapter = "assets/adapters/hugo.html" }}
{{ end }}
{{ $imgset := slice }}
{{- range $index, $dim := $dims -}}
{{ $width := (int (index (split $dim "x") 0)) }}
{{ $height := (int (index (split $dim "x") 1)) }}
{{- $element := partial $adapter (dict
"host" $host
"dir" $dir
"file" $file
"img" $img
"absoluteURL" $absoluteURL
"transform" $transform
"width" $width
"height" $height
"format" $format
)}}
{{ if $includeWidth }}
{{ $imgset = $imgset | append (printf "%s %dw" $element $width) }}
{{ else }}
{{ $imgset = $imgset | append $element }}
{{ end }}
{{- end -}}
{{ return (delimit $imgset ", ") }}

View File

@@ -13,250 +13,29 @@
{{ end }}
<!-- Initialize arguments -->
{{ $destination := .destination }}
<!-- Initialize arguments -->
{{- $absoluteURL := site.Params.main.canonifyAssetsURLs | default false -}}
{{- $page := .page -}}
{{- $ratio := .ratio -}}
{{- $portrait := .portrait -}}
{{- $url := .url -}}
{{- $mode := .mode -}}
{{- $modes := site.Params.main.modes | default (slice "light" "dark") -}}
{{- $wrapper := .wrapper -}}
{{- $class := .class -}}
{{- $title := .title -}}
{{- $caption := .caption -}}
{{- $figclass := .figclass -}}
{{- $page := .page -}}
{{- $loading := .loading -}}
{{- $lazy := eq $loading "lazy" -}}
{{- $priority := .priority -}}
{{- if eq $priority "auto" }}{{ $priority = "" }}{{ end -}}
{{- $sizes := .sizes | default "100vw" -}}
{{- $absoluteURL := site.Params.main.canonifyAssetsURLs | default false -}}
{{- $url := .url -}}
{{- $mode := .mode -}}
{{- $modes := site.Params.main.modes | default (slice "light" "dark") -}}
{{- define "partials/image-portrait.html" -}}
{{- $dimensions := slice }}
{{- $dim := .dim -}}
{{- $dimensions = $dimensions | append $dim -}}
{{- $portraits := slice }}
{{- range $d := $dimensions -}}
{{- $p := split $d "x" }}
{{ if ne (len $p) 2 }}
{{- warnf "partial [assets/image.html] - Invalid dimension: %s" $d -}}
{{- $portraits = $portraits | append $d -}}
{{- else -}}
{{- $portraits = $portraits | append (printf "%sx%s" (index $p 1) (index $p 0)) -}}
{{- end -}}
{{- end -}}
{{- $ret := "" }}
{{- if reflect.IsSlice $dim -}}
{{ $ret = $portraits -}}
{{- else if gt (len $portraits) 0 -}}
{{- $ret = index $portraits 0 -}}
{{- end -}}
{{- return $ret -}}
{{- end -}}
<!-- Generate a fallback image of type jpg -->
{{- define "partials/image-default.html" -}}
{{- $img := .img -}}
{{- $ratio := .ratio -}}
{{- $portrait := and .portrait .ratio -}}
{{- $width := "1400" -}}
{{- $dim := "" -}}
{{- $fallback := "" -}}
{{- with $img -}}
{{- if eq $ratio "4x3" -}}{{- $dim = "1400x1050" }}
{{- else if eq $ratio "3x2" -}}{{- $dim = "1400x933" }}
{{- else if eq $ratio "1x1" -}}{{- $dim = "1400x1400" }}
{{- else if eq $ratio "16x9" -}}{{- $dim = "1400x788" -}}
{{- else if eq $ratio "21x9" -}}{{- $dim = "1400x600" -}}
{{- else -}}
{{- $dim = printf "%sx%d" $width (int (math.Round (mul (div (float $width) $img.Width) $img.Height))) -}}
{{- end -}}
{{- if $portrait -}}{{- $dim = partial "partials/image-portrait.html" (dict "dim" $dim) -}}{{- end -}}
{{- if $ratio -}}
{{- $fallback = ($img.Fill (printf "%s jpg" $dim)) -}}
{{- else -}}
{{- $fallback = ($img.Fit (printf "%s jpg" $dim)) -}}
{{- end -}}
{{- $clean := path.Ext $img.RelPermalink -}}
{{- $ext := path.Ext $img.RelPermalink -}}
{{- $fallback = $fallback | resources.Copy (replace $img.RelPermalink $clean (printf "-%s%s" $dim $ext)) -}}
{{- end -}}
{{- return $fallback -}}
{{- end -}}
<!-- Generate a image set of type webp -->
{{- define "partials/image-scaled.html " -}}
{{- $absoluteURL := site.Params.main.canonifyAssetsURLs | default false -}}
{{- $img := .img -}}
{{- $ratio := .ratio -}}
{{- $portrait := and .portrait .ratio -}}
{{- $imgset := "" -}}
{{- $widths := slice "576" "768" "992" "1200" "1400" -}}
{{- $dims := slice -}}
{{- with $img -}}
{{- if eq $ratio "4x3" -}}
{{- $dims = slice "576x432" "768x576" "992x744" "1200x900" "1400x1050" -}}
{{- else if eq $ratio "3x2" -}}
{{- $dims = slice "576x384" "768x512" "992x661" "1200x800" "1400x933" -}}
{{- else if eq $ratio "1x1" -}}
{{- $dims = slice "576x576" "768x768" "992x992" "1200x1200" "1400x1400" -}}
{{- else if eq $ratio "16x9" -}}
{{- $dims = slice "576x324" "768x432" "992x558" "1200x675" "1400x788" -}}
{{- else if eq $ratio "21x9" -}}
{{- $dims = slice "576x247" "768x329" "992x425" "1200x514" "1400x600" -}}
{{- else -}}
{{- range $width := $widths -}}
{{- $dims = $dims | append (printf "%sx%d" $width (int (math.Round (mul (div (float $width) $img.Width) $img.Height)))) -}}
{{- end -}}
{{- end -}}
{{- if $portrait -}}
{{- $dims = partial "partials/image-portrait.html" (dict "dim" $dims) -}}
{{- end -}}
{{- $scaled := "" -}}
{{- range $index, $dim := $dims -}}
{{- $clean := path.Ext $img.RelPermalink -}}
{{- if $ratio -}}
{{- $scaled = $img.Fill (printf "%s webp" $dim) -}}
{{- else -}}
{{- $scaled = $img.Fit (printf "%s webp" $dim) -}}
{{- end -}}
{{- $scaled = $scaled | resources.Copy (replace $img.RelPermalink $clean (printf "-%s.webp" $dim)) -}}
{{- if $absoluteURL -}}
{{- $imgset = printf "%s, %s %sw" $imgset $scaled.Permalink (index $widths $index) -}}
{{- else -}}
{{- $imgset = printf "%s, %s %sw" $imgset $scaled.RelPermalink (index $widths $index) -}}
{{- end -}}
{{- end -}}
{{- $imgset = strings.TrimPrefix ", " $imgset -}}
{{- end -}}
{{- return $imgset -}}
{{- end -}}
<!-- Define the img with optional caption -->
{{- define "partials/image-definition.html" -}}
{{- $absoluteURL := site.Params.main.canonifyAssetsURLs | default false -}}
{{- $ratio := .ratio -}}
{{- $portrait := .portrait -}}
{{- $url := .url -}}
{{- $wrapper := .wrapper -}}
{{- $class := .class -}}
{{- $title := .title -}}
{{- $caption := .caption -}}
{{- $figclass := .figclass -}}
{{- $fallbackURL := "" -}}
{{- $anchor := "" -}}
{{- $imgset := "" -}}
{{- $mode := .mode -}}
{{- $modes := .modes -}}
{{- $lazy := .lazy -}}
{{- $page := .page -}}
{{- $sizes := .sizes -}}
{{- $priority := .priority -}}
{{- $isVector := false -}}
{{- $segments := split $url "#" -}}
{{- if gt (len $segments) 2 -}}
{{- errorf "Invalid path or url: %q" $url -}}
{{- else if eq (len $segments) 2 }}
{{- $url = index $segments 0 -}}
{{- $anchor = index $segments 1 -}}
{{- end -}}
{{- if hasSuffix $url "svg" -}}
{{- $fallbackURL = $url -}}
{{- $isVector = true -}}
{{- $res := partial "utilities/GetResource.html" (dict "url" $url "page" $page) -}}
{{ if not $res }}
{{- if not (fileExists (path.Join "/static" $url)) -}}
{{ warnf "Cannot find vector image resource: %q" $url -}}
{{ end }}
{{ end }}
{{- else -}}
{{- $res := partial "utilities/GetImage.html" (dict "url" $url "page" $page) -}}
{{- $img := "" -}}
{{ with $res }}
{{ $img = $res.resource }}
{{ if $res.mirror }}{{ $class = printf "%s mirrorred" $class }}{{ end }}
{{ end }}
{{- if $img -}}
{{- $fallback := partial "partials/image-default.html" (dict "img" $img "ratio" $ratio "portrait" $portrait) -}}
{{- if $absoluteURL -}}
{{- $fallbackURL = $fallback.Permalink -}}
{{- else -}}
{{- $fallbackURL = $fallback.RelPermalink -}}
{{- end -}}
{{- $imgset = partial "partials/image-scaled.html " (dict "img" $img "ratio" $ratio "portrait" $portrait) -}}
{{- end -}}
{{- end -}}
{{- range $none := $modes -}}
{{- if ne $none $mode -}}
{{- $wrapper = printf "%s d-none-%s" (or $wrapper "") $none -}}
{{- end -}}
{{- end -}}
{{- $width := "" -}}
{{- $height := "" -}}
{{- $input := (split (path.BaseName $fallbackURL) "-") | collections.Reverse -}}
{{ if $isVector }}
{{- $height = "500" -}}
{{ $width = string (partial "utilities/GetWidth.html" (dict "path" $fallbackURL "height" 500)) }}
{{- else if gt ($input | len) 1 -}}
{{- $dim := (split (index $input 0) "x") -}}
{{- if eq ($dim | len) 2 -}}
{{- $width = index $dim 0 -}}
{{- $height = index $dim 1 -}}
{{- end -}}
{{- end }}
{{- if $caption -}}
<figure {{ with $wrapper }}class="{{ . }}"{{ end }}>
{{ end }}
<div class="{{ if not $caption }}{{ $wrapper }}{{ end }}">
{{- if not $anchor -}}
<img class="img-fluid {{ $class }}"
src="{{ $fallbackURL }}"
{{ if $lazy }}loading="lazy"{{ end }}
{{ with $priority }}fetchpriority="{{ . }}"{{ end }}
{{ with $imgset -}}srcset="{{ . }}" sizes="{{ $sizes }}"{{- end }}
{{ with $height }}height="{{ . }}"{{ end }}
{{ with $width }}width="{{ . }}"{{ end }}
{{ with $title }}alt="{{ . }}"{{ end }}>
{{- else }}
<svg class="{{ $class }}">
<use href="{{ $fallbackURL }}#{{ $anchor }}"></use>
</svg>
{{ end }}
</div>
{{- if $caption -}}
<figcaption class="figure-caption{{ with $figclass }} {{ . }}{{ end }}">{{ $caption | safeHTML }}</figcaption>
</figure>
{{- end -}}
{{- end -}}
<!-- Initiate the regular or color-mode image -->
<!-- Main code -->
{{ $params := dict }}
{{- $params = merge $params (dict
"page" $page
"ratio" $ratio
"portrait" $portrait
"wrapper" $wrapper
@@ -267,7 +46,8 @@
"lazy" $lazy
"priority" $priority
"sizes" $sizes
"page" $page)
"absoluteURL" $absoluteURL
)
-}}
{{- if $mode -}}
@@ -284,9 +64,11 @@
"mode" $suffix
"modes" $modes)
-}}
{{- partial "partials/image-definition.html" $params -}}
{{- end -}}
{{ else }}
{{- $params = merge $params (dict "url" $url) -}}
{{- partial "partials/image-definition.html" $params -}}
{{- end -}}
{{ if not $error }}
{{- partial "assets/helpers/image-definition.html" $params -}}
{{ end }}

View File

@@ -18,9 +18,10 @@
{{- $target := "" -}}
{{- $rel := "" -}}
{{- $case := .case | default true }}
{{- $external := .external | default false }}
{{- $cue := .cue | default site.Params.main.externalLinks.cue -}}
{{- $tab := .tab | default site.Params.main.externalLinks.tab -}}
{{- $isExternal := ne (urls.Parse (absURL $destination)).Host (urls.Parse site.BaseURL).Host -}}
{{- $isExternal := or (ne (urls.Parse (absURL $destination)).Host (urls.Parse site.BaseURL).Host) $external -}}
{{- $page := .page -}}
{{- $anchor := "" -}}
{{- $text := .text -}}

View File

@@ -18,6 +18,7 @@
{{ $url := "" }}
{{ $class := "" }}
{{ $case := true }}
{{ $external := false }}
{{ $cue := site.Params.main.externalLinks.cue }}
{{ $tab := site.Params.main.externalLinks.tab }}
{{ $text := trim .Inner " \r\n" | .Page.RenderString | safeHTML }}
@@ -30,6 +31,7 @@
{{- $cue = .Get "cue" | default site.Params.main.externalLinks.cue -}}
{{- $tab = .Get "tab" | default site.Params.main.externalLinks.tab -}}
{{- $case = .Get "case" | default true -}}
{{- $external = .Get "external" | default false -}}
{{- $class = .Get "class" | default "" -}}
{{ else }}
{{ $href = .Get 0 }}
@@ -42,7 +44,11 @@
{{ end }}
{{- if hasPrefix $href "http" -}}
{{- if $external -}}
{{ $url = $href }}
{{- else -}}
{{ $url = strings.TrimPrefix (strings.TrimSuffix "/" site.BaseURL) $href }}
{{- end -}}
{{- else if not (strings.Contains $href "/") -}}
{{ $url = index site.Params.links $href }}
{{- end -}}
@@ -65,7 +71,12 @@
{{ end }}
{{ end }}
{{- $isExternal := ne (urls.Parse (absURL $url)).Host (urls.Parse site.BaseURL).Host -}}
{{ if and $external (not (hasPrefix $url "http")) }}
{{ errorf "External link must start with 'http': %s" .Position -}}
{{ $error = true -}}
{{ end }}
{{- $isExternal := or (ne (urls.Parse (absURL $url)).Host (urls.Parse site.BaseURL).Host) $external -}}
{{- if not $isExternal -}}
{{ $ref := partial "utilities/GetPage.html" (dict "url" $url "page" .Page) }}
{{- if not $ref -}}
@@ -80,5 +91,5 @@
<!-- Main code -->
{{- if not $error -}}
{{ partial "assets/link.html" (dict "destination" $url "text" $text "cue" $cue "tab" $tab "case" $case "class" $class "page" .Page) }}
{{ partial "assets/link.html" (dict "destination" $url "text" $text "cue" $cue "tab" $tab "case" $case "external" $external "class" $class "page" .Page) }}
{{- end -}}

View File

@@ -33,7 +33,7 @@
font-src 'self' https://*.netlify.app https://fonts.gstatic.com; \
frame-src 'self' https://www.youtube-nocookie.com https://www.youtube.com \
app.netlify.com; \
img-src 'self' data: https://*.netlify.app https://i.vimeocdn.com https://i.ytimg.com https://*.google-analytics.com https://*.googletagmanager.com https://tile.openstreetmap.org; \
img-src 'self' data: https://*.imgix.net https://*.imagekit.io https://*.cloudinary.com https://*.netlify.app https://i.vimeocdn.com https://i.ytimg.com https://*.google-analytics.com https://*.googletagmanager.com https://tile.openstreetmap.org; \
manifest-src 'self'; \
media-src 'self' \
"""

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@gethinode/hinode",
"version": "0.23.20",
"version": "0.24.0-beta1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@gethinode/hinode",
"version": "0.23.20",
"version": "0.24.0-beta1",
"license": "MIT",
"devDependencies": {
"@fullhuman/postcss-purgecss": "^6.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@gethinode/hinode",
"version": "0.23.20",
"version": "0.24.0-beta1",
"description": "Hinode is a clean documentation and blog theme for Hugo, an open-source static site generator",
"keywords": [
"hugo",