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. Blue light glows blue.
{{% /notice %}} {{% /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 ## Images
### Basic Images ### Basic Images

View file

@ -56,27 +56,21 @@ With this configuration in effect, the following URL
would result in would result in
````html {title="HTML"} ````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 ## Styling Effects
If the resulting effect value is If the resulting effect value is `true` a class with the effect's name will be added.
- `true`: add a class with the effect's name
- `false`: add a class with the effect's name and a "no" prefix
Styles for default effects are contained in the theme. Add styles for your custom effects to `layouts/partials/content-header.html`. 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"} ````html {title="layouts/partials/content-header.html"}
<style> <style>
img.bg-white { img.bg-white {
background-color: white; background-color: white;
} }
img.nobg-white {
background-color: transparent;
}
</style> </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. 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 ## Build and Deploy

View file

@ -10,9 +10,24 @@ weight = -4
### New ### 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. - {{% 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 }} {{- end }}
{{- $title := .title }} {{- $title := .title }}
{{- $alt := .alt }} {{- $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 }} {{- $attributes := .attributes | default dict }}
{{- $u := urls.Parse .url }} {{- $u := urls.Parse .url }}
{{- $src := $u.String }} {{- $src := $u.String }}
{{- if $u.IsAbs }} {{- if $u.IsAbs }}
{{- partialCached "_relearn/urlExists.gotmpl" (dict "url" .url "page" $page "type" "image") $u.String }} {{- partialCached "_relearn/urlExists.gotmpl" (dict "url" .url "page" $page "type" "image") $u.String }}
{{- else }} {{- else }}
{{- $path := strings.TrimPrefix "./" $u.Path }} {{- $linkObject := partial "_relearn/linkObject.gotmpl" (dict "url" .url "page" $page "searchPage" false) }}
{{- with or {{- if $linkObject }}
($page.Resources.Get $path) {{- $src = partial "_relearn/decoratedLink.gotmpl" (dict "url" .url "page" $page "linkObject" $linkObject "param" "image") }}
(resources.Get $path)
}}
{{- $src = .RelPermalink }}
{{- with $u.RawQuery }}
{{- $src = printf "%s?%s" $src . }}
{{- end }}
{{- with $u.Fragment }}
{{- $src = printf "%s#%s" $src . }}
{{- end }}
{{- else }} {{- else }}
{{- $filepath := "[virtual file]" }}{{ with and $page $page.File $page.File.Filename }}{{ $filepath = . }}{{ end }} {{- $filepath := "[virtual file]" }}{{ with and $page $page.File $page.File.Filename }}{{ $filepath = . }}{{ end }}
{{- $msg := printf "%q: image '%s' is not a resource" $filepath .url }} {{- $msg := printf "%q: image '%s' is not a resource" $filepath .url }}
{{- partial "_relearn/urlErrorReport.gotmpl" (dict "url" .url "page" $page "param" "image" "msg" $msg) }} {{- partial "_relearn/urlErrorReport.gotmpl" (dict "url" .url "page" $page "param" "image" "msg" $msg) }}
{{- end }} {{- end }}
{{- 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 }} {{- $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)) }} {{- $attributes = merge $attributes (partial "_relearn/imageAttributes.gotmpl" (dict "url" $src "page" $page)) }}
{{- if $effects.lazy }} {{- $attributes = merge $attributes (dict "alt" $alt "title" ($title | transform.HTMLEscape)) }}
{{- $attributes = merge $attributes (dict "loading" "lazy") }} {{- $classes := split $attributes.class " " }}
{{- end }} {{- $isLightbox := in $classes "lightbox" }}
{{- if $effects.lightbox -}} {{- if $isLightbox -}}
<a href="#R-image-{{ $id }}" class="lightbox-link"> <a href="#R-image-{{ $id }}" class="lightbox-link">
{{- end }} {{- end }}
{{- $attributes_figure := merge $attributes (dict "class" (delimit (append (index $attributes "class" | default slice) "figure-image" $classes) " ")) }} {{- $attributes_figure := $attributes }}
{{- $attributes_figure = merge $attributes_figure (dict "style" (delimit (slice (index $attributes "style") (printf "height: %s; width: %s;" $height $width)) " ")) -}} {{- $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 <img
{{- range $k, $v := $attributes_figure }} {{- range $k, $v := $attributes_figure }}
{{- if $v }} {{- 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 }}
{{- end }}> {{- end }}>
{{- if $effects.lightbox -}} {{- if $isLightbox -}}
</a> </a>
<a href="javascript:history.back();" class="lightbox-back" id="R-image-{{ $id }}"> <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 <img
{{- range $k, $v := $attributes_lightbox }} {{- range $k, $v := $attributes_lightbox }}
{{- if $v }} {{- 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 }}
{{- end }}></a> {{- end }}></a>
{{- end }} {{- end }}

View file

@ -12,33 +12,32 @@
{{- $attributes := .attributes | default dict }} {{- $attributes := .attributes | default dict }}
{{- $title := .title | default "" }} {{- $title := .title | default "" }}
{{- $title = trim $title " " }} {{- $title = trim $title " " }}
{{- $attributes = $attributes | merge (dict "title" ($title | transform.HTMLEscape)) }} {{- $attributes = merge $attributes (dict "title" ($title | transform.HTMLEscape)) }}
{{- $content := .content }} {{- $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 }} {{- $u := urls.Parse .url }}
{{- $href := $u.String }} {{- $href := $u.String }}
{{- if $u.IsAbs }} {{- if $u.IsAbs }}
{{- partialCached "_relearn/urlExists.gotmpl" (dict "url" .url "page" $page "type" "link") $u.String }} {{- 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 }} {{- else }}
{{- $linkObject := partial "_relearn/linkObject.gotmpl" (dict "url" $.url "page" $page) }} {{- $linkObject := partial "_relearn/linkObject.gotmpl" (dict "url" .url "page" $page) }}
{{- with $linkObject }} {{- if $linkObject }}
{{- $href = partial "_relearn/decoratedLink.gotmpl" (dict "url" $.url "page" $page "linkObject" . "param" "link") }} {{- $href = partial "_relearn/decoratedLink.gotmpl" (dict "url" .url "page" $page "linkObject" $linkObject "param" "link") }}
{{- else }} {{- else }}
{{- $filepath := "[virtual file]" }}{{ with and $page $page.File $page.File.Filename }}{{ $filepath = . }}{{ end }} {{- $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 }} {{- $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) }} {{- partial "_relearn/urlErrorReport.gotmpl" (dict "url" .url "page" $page "param" "link" "msg" $msg) }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- $attributes = $attributes | merge (dict "href" $href) -}} {{- $attributes = merge $attributes (partial "_relearn/linkAttributes.gotmpl" (dict "url" $href "page" $page "target" .target)) -}}
<a <a
{{- range $k, $v := $attributes }} {{- range $k, $v := $attributes }}
{{- if $v }} {{- 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 }}
{{- end }}>{{ $content | safeHTML }}</a> {{- end }}>{{ $content | safeHTML }}</a>

View file

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