link: introduce effects #850 #1008

- new link effects: target download
- effects can now be strings besides bools (to allow for custom download
  attribute on links)
- remove "no*" classes form image as they don't make sense for
  string effects and are cluttering the image class list for newly
  introduced effects
- refactor attribute parsing for later reusage in menu generation, button
  topbar, etc.
This commit is contained in:
Sören Weber 2025-02-07 14:28:26 +01:00
parent 516e552ece
commit aaf0c707a2
No known key found for this signature in database
GPG key ID: BEC6D55545451B6D
10 changed files with 230 additions and 93 deletions

View file

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -681,6 +681,34 @@ That's some more text with a footnote.[^someid]
Blue light glows blue.
{{% /notice %}}
### Link Effects
{{% badge color="#7dc903" icon="fa-fw fas fa-puzzle-piece" %}}Relearn{{% /badge %}} This theme allows additional non-standard formatting by setting query parameter at the end of the URL. See the [link effects docs](authoring/linkeffects) for a detailed example and how to configure it.
#### Target
Add query parameter `target=_self` or `target=_blank` to override [site-wide settings](authoring/frontmatter/linking#opening-links) of [the target behavior](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target) individuallly for each link.
````md
[Magic in new window](images/magic.gif?target=_blank)
````
{{% notice style="code" icon="eye" title="Result" %}}
[Magic in new window](images/magic.gif?target=_blank)
{{% /notice %}}
#### Download
Add query parameter `download` or `download=myfile.gif` to force your browser [to download the link target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#download) instead of opening it.
````md
[Magic as a download](images/magic.gif?download)
````
{{% notice style="code" icon="eye" title="Result" %}}
[Magic as a download](images/magic.gif?download)
{{% /notice %}}
## Images
### Basic Images

View file

@ -56,27 +56,21 @@ With this configuration in effect, the following URL
would result in
````html {title="HTML"}
<img src="https://octodex.github.com/images/minion.png" loading="lazy" alt="Minion" class="bg-white border nolazy lightbox noshadow">
<img src="https://octodex.github.com/images/minion.png" loading="lazy" alt="Minion" class="bg-white border lightbox">
````
## Styling Effects
If the resulting effect value is
- `true`: add a class with the effect's name
- `false`: add a class with the effect's name and a "no" prefix
If the resulting effect value is `true` a class with the effect's name will be added.
Styles for default effects are contained in the theme. Add styles for your custom effects to `layouts/partials/content-header.html`.
For the above example you could add styles for both boolean cases:
For the above custom effect you could add the following style:
````html {title="layouts/partials/content-header.html"}
<style>
img.bg-white {
background-color: white;
}
img.nobg-white {
background-color: transparent;
}
</style>
````

View file

@ -127,7 +127,7 @@ Open [http://localhost:1313](http://localhost:1313) in your web browser.
You can keep the server running while you edit. The browser will update automatically when you save changes.
{{% figure src="magic.gif" link="https://gohugo.io" alt="Magic" caption="It's a kind of magic" %}}
{{% figure src="images/magic.gif" link="https://gohugo.io" alt="Magic" caption="It's a kind of magic" %}}
## Build and Deploy

View file

@ -10,9 +10,24 @@ weight = -4
### New
- {{% badge style="info" icon="plus-circle" title=" " %}}New{{% /badge %}} If [`link.errorlevel` is configured](authoring/frontmatter/linking/#enabling-link-and-image-link-warnings), now also the `pageRef` of a [Hugo menu item](https://gohugo.io/content-management/menus/) and the `menuPageRef` in a [page's front matter](configuration/sidebar/menus/#displaying-arbitrary-links-in-a-page-menu) will be checked for existence.
- {{% badge style="info" icon="plus-circle" title=" " %}}New{{% /badge %}} The theme now allows page references (given as `pageRef` of a [Hugo menu item](https://gohugo.io/content-management/menus/), `pageRef` parameter of a [`sidebarmenus` entry ](configuration/sidebar/menus#defining-sidebar-menus) and the `menuPageRef` in a [page's front matter](configuration/sidebar/menus/#displaying-arbitrary-links-in-a-page-menu)) to also be global resources from your `assets` directory.
- {{% badge style="info" icon="plus-circle" title=" " %}}New{{% /badge %}} You can now [configure an ignore list](authoring/frontmatter/linking/#ignoring-false-negatives) of addresses that should be ignored if an errorlevel test fails. This is configured by setting `errorignore=[]` in your `hugo.toml`. This helps to remove false negatives from the output while still benefitting from the check for all other addresses.
- {{% badge style="info" icon="plus-circle" title=" " %}}New{{% /badge %}} If [`link.errorlevel` is configured](authoring/frontmatter/linking/#enabling-link-and-image-link-warnings), now also page references will be checked for existence.
- {{% badge style="info" icon="plus-circle" title=" " %}}New{{% /badge %}} It is now possible to add query parameter and an optional fragment part to all links. This is regardless whether it is a local or remote address and also applies to Hugo's internal path.
By that you can now use it on Markdown links, image links, page references.
- {{% badge style="info" icon="plus-circle" title=" " %}}New{{% /badge %}} This release introduces [link effects](authoring/markdown#link-effects) which work similar to [image effects](authoring/markdown#image-effects) and can be set as query parameter on any link.
With that you currently can specify
- a link target individually for each link
- that a link should result in a file download in your browser
- {{% badge style="info" icon="plus-circle" title=" " %}}New{{% /badge %}} You can now [configure an ignore list](authoring/frontmatter/linking/#ignoring-false-negatives) of addresses that should be ignored if an errorlevel test fails. This is configured by setting `errorignore=[]` globally in your `hugo.toml` for all errorlevel checks.
This helps to remove false negatives while still benefitting from the check for all other addresses.
- {{% badge style="info" icon="plus-circle" title=" " %}}New{{% /badge %}} The theme supports the new [`source` output format](configuration/sitemanagement/outputformats/#source-support) which behaves similar in configuration as the `markdown` output format but allows the original Markdown source including the front matter of a page to be viewed.

View file

@ -0,0 +1,71 @@
{{- $attributes := dict }}
{{- $height := "auto" }}
{{- $width := "auto" }}
{{- $effects := dict "border" false "lazy" true "lightbox" true "shadow" false }}
{{- if .page.Site.Params.imageeffects }}
{{- $effects = merge $effects .page.Site.Params.imageeffects }}
{{- end }}
{{- if .page.Params.imageEffects }}
{{- $effects = merge $effects .page.Params.imageEffects }}
{{- end }}
{{- $u := urls.Parse .url }}
{{- if $u.RawQuery }}
{{- if $u.Query.Has "classes" }}
{{- $classes := slice | append (split ($u.Query.Get "classes") ",") }}
{{- range $classes }}
{{- $k := . }}
{{- $v := true }}
{{- if strings.HasPrefix $k "no" }}
{{- $k := strings.TrimPrefix "no" $k }}
{{- $v := false }}
{{- end }}
{{- $effects = merge $effects (dict $k $v) }}
{{- end }}
{{- end }}
{{- if $u.Query.Has "featherlight" }}
{{- $filepath := "[virtual file]" }}{{ with and .page .page.File .page.File.Filename }}{{ $filepath = . }}{{ end }}
{{- warnf "%q: DEPRECATED usage of 'featherlight' image CSS class found, use 'lightbox' instead; see https://mcshelby.github.io/hugo-theme-relearn/introduction/releasenotes/5/#5-11-0" $filepath }}
{{- $effects = merge $effects (dict "lightbox" (ne ($u.Query.Get "featherlight") "false")) }}
{{- end }}
{{- range $k, $v := $effects }}
{{- if $u.Query.Has $k }}
{{- $effects = merge $effects (dict $k (ne ($u.Query.Get $k) "false")) }}
{{- end }}
{{- end }}
{{- range $k, $v := $effects }}
{{- if $u.Query.Has $k }}
{{- $paramValue := $u.Query.Get $k }}
{{- $newValue := true }}
{{- if eq $paramValue "" }}
{{- $newValue = true }}
{{- else if eq $paramValue "true" }}
{{- $newValue = true }}
{{- else if eq $paramValue "false" }}
{{- $newValue = false }}
{{- else }}
{{- $newValue = $paramValue }}
{{- end }}
{{- $effects = merge $effects (dict $k $newValue) }}
{{- end }}
{{- end }}
{{- with $u.Query.Get "height" }}
{{- $height = . }}
{{- end }}
{{- with $u.Query.Get "width" }}
{{- $width = . }}
{{- end }}
{{- end }}
{{- $classes := slice }}
{{- range $k, $v := $effects }}
{{- if $v }}
{{- if eq (printf "%T" $v) "bool" }}
{{- $classes = $classes | append $k }}
{{- end }}
{{- end }}
{{- end }}
{{- if $attributes.class }}{{ $classes = $classes | append $attributes.class }}{{ end }}
{{- $attributes = merge $attributes (dict "class" (delimit $classes " ")) }}
{{- $attributes = merge $attributes (dict "src" .url) }}
{{- $attributes = merge $attributes (dict "style" (printf " height: %s; width: %s;%s" $height $width (index $attributes "style" | default ""))) }}
{{- if $effects.lazy }}{{ $attributes = merge $attributes (dict "loading" "lazy") }}{{ end }}
{{- return $attributes }}

View file

@ -0,0 +1,72 @@
{{- $attributes := dict }}
{{- /* target will be boolean false if no user defined value was set and effect default should be applied */}}
{{- $target := false }}
{{- $u := urls.Parse .url }}
{{- if $u.IsAbs }}
{{- $attributes = merge $attributes (dict "rel" "external") }}
{{- $target = "_blank" }}
{{- if isset .page.Site.Params "externallinktarget" }}
{{- $target = .page.Site.Params.externalLinkTarget }}
{{- if in (slice "false" false 0) $target }}
{{- $target = "" }}
{{- end }}
{{- if in (slice "true" true 1) $target }}
{{- $target = "_blank" }}
{{- end }}
{{- end }}
{{- end }}
{{- $effects := dict "download" false "target" false }}
{{- if .page.Site.Params.linkeffects }}
{{- $effects = merge $effects .page.Site.Params.linkeffects }}
{{- end }}
{{- if .page.Params.linkeffects }}
{{- $effects = merge $effects .page.Params.linkeffects }}
{{- end }}
{{- $target := .target | default $target }}
{{- if ne (printf "%T" $target) "bool" }}
{{- $effects = merge $effects (dict "target" $target) }}
{{- end }}
{{- if $u.RawQuery }}
{{- if $u.Query.Has "classes" }}
{{- $classes := slice | append (split ($u.Query.Get "classes") ",") }}
{{- range $classes }}
{{- $k := . }}
{{- $v := true }}
{{- if strings.HasPrefix $k "no" }}
{{- $k := strings.TrimPrefix "no" $k }}
{{- $v := false }}
{{- end }}
{{- $effects = merge $effects (dict $k $v) }}
{{- end }}
{{- end }}
{{- range $k, $v := $effects }}
{{- if $u.Query.Has $k }}
{{- $paramValue := $u.Query.Get $k }}
{{- $newValue := true }}
{{- if eq $paramValue "" }}
{{- $newValue = true }}
{{- else if eq $paramValue "true" }}
{{- $newValue = true }}
{{- else if eq $paramValue "false" }}
{{- $newValue = false }}
{{- else }}
{{- $newValue = $paramValue }}
{{- end }}
{{- $effects = merge $effects (dict $k $newValue) }}
{{- end }}
{{- end }}
{{- end }}
{{- $classes := slice }}
{{- range $k, $v := $effects }}
{{- if $v }}
{{- if eq (printf "%T" $v) "bool" }}
{{- $classes = $classes | append $k }}
{{- end }}
{{- end }}
{{- end }}
{{- if $attributes.class }}{{ $classes = $classes | append $attributes.class }}{{ end }}
{{- $attributes = merge $attributes (dict "class" (delimit $classes " ")) }}
{{- $attributes = merge $attributes (dict "href" .url) }}
{{- $attributes = merge $attributes (dict "download" $effects.download) }}
{{- $attributes = merge $attributes (dict "target" $effects.target) }}
{{- return $attributes }}

View file

@ -7,98 +7,56 @@
{{- end }}
{{- $title := .title }}
{{- $alt := .alt }}
{{- $effects := dict "border" false "lazy" true "lightbox" true "shadow" false }}
{{- if $page.Site.Params.imageeffects }}
{{- $effects = merge $effects $page.Site.Params.imageeffects }}
{{- end }}
{{- if $page.Params.imageEffects }}
{{- $effects = merge $effects $page.Params.imageEffects }}
{{- end }}
{{- $height := "auto" }}
{{- $width := "auto" }}
{{- $attributes := .attributes | default dict }}
{{- $u := urls.Parse .url }}
{{- $src := $u.String }}
{{- if $u.IsAbs }}
{{- partialCached "_relearn/urlExists.gotmpl" (dict "url" .url "page" $page "type" "image") $u.String }}
{{- else }}
{{- $path := strings.TrimPrefix "./" $u.Path }}
{{- with or
($page.Resources.Get $path)
(resources.Get $path)
}}
{{- $src = .RelPermalink }}
{{- with $u.RawQuery }}
{{- $src = printf "%s?%s" $src . }}
{{- end }}
{{- with $u.Fragment }}
{{- $src = printf "%s#%s" $src . }}
{{- end }}
{{- $linkObject := partial "_relearn/linkObject.gotmpl" (dict "url" .url "page" $page "searchPage" false) }}
{{- if $linkObject }}
{{- $src = partial "_relearn/decoratedLink.gotmpl" (dict "url" .url "page" $page "linkObject" $linkObject "param" "image") }}
{{- else }}
{{- $filepath := "[virtual file]" }}{{ with and $page $page.File $page.File.Filename }}{{ $filepath = . }}{{ end }}
{{- $msg := printf "%q: image '%s' is not a resource" $filepath .url }}
{{- partial "_relearn/urlErrorReport.gotmpl" (dict "url" .url "page" $page "param" "image" "msg" $msg) }}
{{- end }}
{{- end }}
{{- if $u.RawQuery }}
{{- if $u.Query.Has "classes" }}
{{- $classes := slice | append (split ($u.Query.Get "classes") ",") }}
{{- range $classes }}
{{- $k := . }}
{{- $v := true }}
{{- if strings.HasPrefix $k "no" }}
{{- $k := strings.TrimPrefix "no" $k }}
{{- $v := false }}
{{- end }}
{{- $effects = merge $effects (dict $k $v) }}
{{- end }}
{{- end }}
{{- if $u.Query.Has "featherlight" }}
{{- $filepath := "[virtual file]" }}{{ with and $page $page.File $page.File.Filename }}{{ $filepath = . }}{{ end }}
{{- warnf "%q: DEPRECATED usage of 'featherlight' image CSS class found, use 'lightbox' instead; see https://mcshelby.github.io/hugo-theme-relearn/introduction/releasenotes/5/#5-11-0" $filepath }}
{{- $effects = merge $effects (dict "lightbox" (ne ($u.Query.Get "featherlight") "false")) }}
{{- end }}
{{- range $k, $v := $effects }}
{{- if $u.Query.Has $k }}
{{- $effects = merge $effects (dict $k (ne ($u.Query.Get $k) "false")) }}
{{- end }}
{{- end }}
{{- with $u.Query.Get "height" }}
{{- $height = . }}
{{- end }}
{{- with $u.Query.Get "width" }}
{{- $width = . }}
{{- end }}
{{- end }}
{{- $classes := slice }}
{{- range $k, $v := $effects }}
{{- $c := printf "%s%s" (cond $v "" "no") $k }}
{{- $classes = $classes | append $c }}
{{- end }}
{{- $id := cond (or (eq .id nil) (eq .id "")) (partial "_relearn/makeRandomMd5.gotmpl" $page) .id }}
{{- $attributes = merge $attributes (dict "alt" $alt "src" $src "title" ($title | transform.HTMLEscape)) }}
{{- if $effects.lazy }}
{{- $attributes = merge $attributes (dict "loading" "lazy") }}
{{- end }}
{{- if $effects.lightbox -}}
{{- $attributes = merge $attributes (partial "_relearn/imageAttributes.gotmpl" (dict "url" $src "page" $page)) }}
{{- $attributes = merge $attributes (dict "alt" $alt "title" ($title | transform.HTMLEscape)) }}
{{- $classes := split $attributes.class " " }}
{{- $isLightbox := in $classes "lightbox" }}
{{- if $isLightbox -}}
<a href="#R-image-{{ $id }}" class="lightbox-link">
{{- end }}
{{- $attributes_figure := merge $attributes (dict "class" (delimit (append (index $attributes "class" | default slice) "figure-image" $classes) " ")) }}
{{- $attributes_figure = merge $attributes_figure (dict "style" (delimit (slice (index $attributes "style") (printf "height: %s; width: %s;" $height $width)) " ")) -}}
{{- $attributes_figure := $attributes }}
{{- $attributes_figure = merge $attributes_figure (dict "class" (delimit ((split $attributes_figure.class " ") | append "figure-image") " ")) }}
{{- $attributes_figure = merge $attributes_figure (dict "style" (index $attributes_figure "style")) -}}
<img
{{- range $k, $v := $attributes_figure }}
{{- if $v }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- if eq (printf "%T" $v) "bool" }}
{{- printf " %s" $k | safeHTMLAttr }}
{{- else }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end }}
{{- end }}
{{- end }}>
{{- if $effects.lightbox -}}
{{- if $isLightbox -}}
</a>
<a href="javascript:history.back();" class="lightbox-back" id="R-image-{{ $id }}">
{{- $attributes_lightbox := merge $attributes (dict "class" (delimit (append (index $attributes "class" | default slice) "lightbox-image" $classes) " ")) -}}
{{- $attributes_lightbox := $attributes }}
{{- $attributes_lightbox = merge $attributes_lightbox (dict "class" (delimit ((split $attributes_lightbox.class " ") | append "lightbox-image") " ")) }}
{{- $attributes_lightbox = merge $attributes_lightbox (dict "style" "") -}}
<img
{{- range $k, $v := $attributes_lightbox }}
{{- if $v }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- if eq (printf "%T" $v) "bool" }}
{{- printf " %s" $k | safeHTMLAttr }}
{{- else }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end }}
{{- end }}
{{- end }}></a>
{{- end }}

View file

@ -12,33 +12,32 @@
{{- $attributes := .attributes | default dict }}
{{- $title := .title | default "" }}
{{- $title = trim $title " " }}
{{- $attributes = $attributes | merge (dict "title" ($title | transform.HTMLEscape)) }}
{{- $attributes = merge $attributes (dict "title" ($title | transform.HTMLEscape)) }}
{{- $content := .content }}
{{- $target := .target | default "" }}
{{- /* target will be boolean false if no user defined value was set and effect default should be applied */}}
{{- $target := false }}
{{- $u := urls.Parse .url }}
{{- $href := $u.String }}
{{- if $u.IsAbs }}
{{- partialCached "_relearn/urlExists.gotmpl" (dict "url" .url "page" $page "type" "link") $u.String }}
{{- $attributes = merge $attributes (dict "rel" "external") }}
{{- $target = "_blank" }}
{{- if isset $page.Site.Params "externallinktarget" }}
{{- $target = $page.Site.Params.externalLinkTarget }}
{{- end }}
{{- $attributes = $attributes | merge (dict "target" $target) }}
{{- else }}
{{- $linkObject := partial "_relearn/linkObject.gotmpl" (dict "url" $.url "page" $page) }}
{{- with $linkObject }}
{{- $href = partial "_relearn/decoratedLink.gotmpl" (dict "url" $.url "page" $page "linkObject" . "param" "link") }}
{{- $linkObject := partial "_relearn/linkObject.gotmpl" (dict "url" .url "page" $page) }}
{{- if $linkObject }}
{{- $href = partial "_relearn/decoratedLink.gotmpl" (dict "url" .url "page" $page "linkObject" $linkObject "param" "link") }}
{{- else }}
{{- $filepath := "[virtual file]" }}{{ with and $page $page.File $page.File.Filename }}{{ $filepath = . }}{{ end }}
{{- $msg := printf "%q: link '%s' is not a page or a resource" $filepath .url }}
{{- partial "_relearn/urlErrorReport.gotmpl" (dict "url" .url "page" $page "param" "link" "msg" $msg) }}
{{- end }}
{{- end }}
{{- $attributes = $attributes | merge (dict "href" $href) -}}
{{- $attributes = merge $attributes (partial "_relearn/linkAttributes.gotmpl" (dict "url" $href "page" $page "target" .target)) -}}
<a
{{- range $k, $v := $attributes }}
{{- if $v }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- if eq (printf "%T" $v) "bool" }}
{{- printf " %s" $k | safeHTMLAttr }}
{{- else }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end }}
{{- end }}
{{- end }}>{{ $content | safeHTML }}</a>

View file

@ -1 +1 @@
7.3.2+14c19bb13cecd2d3e72f5f53ad2b03f71dfcef81
7.3.2+516e552ecefcdf2f67cb0c34869f225affe45f73