Using Hugo Render hooks to make links bend to your will

This all began with a post on my socials…

https://mastodon.neilzone.co.uk/@neil/113244632967157645

The consensus certainly seemed to be that it’s not possible to do this solely with CSS (which is a shame), but Neil did mention that they were inv…


This content originally appeared on DEV Community and was authored by Leigh Garland

This all began with a post on my socials...

https://mastodon.neilzone.co.uk/@neil/113244632967157645

https://mastodon.neilzone.co.uk/@neil/113244632967157645

The consensus certainly seemed to be that it's not possible to do this solely with CSS (which is a shame), but Neil did mention that they were investigating using Hugo to solve this.

My advice was to use Render Hooks, but I couldn't stop thinking about how I might do this, as it seemed like a neat way to present those long external links...

This is where I started

You can jump straight to the solution if you want. Otherwise, read on.

I spun up a standard Hugo quickstart project, and added some links to my site index page:



Here is a variety of links to test Hugo and/or CSS to show just the domain for external links...

**Internal links**

* [Custom text](/i_love_cats)
* [Custom text, with title](/i_love_cats "I love cats")
* [Custom text, absolute path](http://localhost:1313/i_love_cats)

**External links**

* Top-level domain, auto-generated text https://cats.org
* Deep link, auto-generated text https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats
* [Custom text, Top-level domain](https://cats.org)
* [Custom text, Top-level domain, with title ](https://cats.org "I love rescue cats")
* [Custom text, deep link](https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats)
* [Custom text, deep link, with title](https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats "Lost & found cats")


I wanted to account for all the ways you can present links in markdown, in Hugo. It looks something like this:

Links rendered without adaptation
I think it's those pesky auto-generated deeplinks that Neil wants to make a little prettier.

The rendered link looks like:



<a href="https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats">
    https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats
    </a>


Which doesn't give you much to hook into with CSS.

This is where Hugo's render hooks come in. For a certain selection of commonly used html elements, Hugo gives you the chance to override the default rendering, and define how you want it to render yourself using the Hugo template system. Hugo provides you with 3 variables for a link.



[Post 1](/posts/post-1 "My first post")
 ------  -------------  ------------- 
  text    destination      title


My first step was to add a data-domain attribute to see if I could use it to replace the content. I created a render-link.html file in



layouts/
    _default/
        _markup/
            render-link.html


This now overrides the default markdown rendering for links. I started with:



{{ $u := urls.Parse .Destination }}

<a href="{{ .Destination | safeURL }}" 
data-domain="{{ $u.Hostname }}">{{ .Text | safeHTML }}
</a>


It uses Hugo's urls.Parse function to take the destination and convert to an object of the various parts of a url. In our case, we just need the Hostname . I added it to the data-domain attribute, and our deep link now looks like



<a href="https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats" data-domain="www.cats.org.uk">https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats</a>


Then I added a bit to the CSS for links, to show the data-domain attribute as extended content for the link, like this:



a[data-domain]::after {
    content: ' - ' attr(data-domain);
}


But, this was a bit messy, as the data-domain was missing for relative links, and rendered some odd dashes at the end of lines. Also, where the markdown had infered the link test from the url, it looked even messier. I couldn't find a neat way to reliably style out the actual link text leaving only the shorter hostname.

Another screengrab of the output link list,

Given I had the urls.Parse method, I now realised I could do something a little more fancy-schmancy, and build up some additional elements, easily styled by CSS. Something like this:



{{ $u := urls.Parse .Destination }}
<a href="{{ .Destination | safeURL }}"
data-domain="{{ $u.Hostname }}">
    <span class="link-text">{{ .Text | safeHTML }}</span>
    <span class="hostname-text">{{ $u.Hostname }}</span>
</a>


Which didn't really change the output, but now I had some CSS selectors to work with.

I created this monstrosity (you would swap out localhost for your site's domain)



a[href]:not([href*="://localhost"]) .link-text {
    display: none;
}


Thinking this would pretty much do the trick, but... it fails on relative links, because they too don't match the ://localhost domain. Looks like:

Screenshot, internal links don't show their text

Oops 😦

I needed to provide a few more class names for the CSS to be able to hook into.

First up - is this a relative link? For this I need to check if the hostname is empty.



{{ if not (strings.ContainsNonSpace $u.Hostname )}}


Basically this checks if the hostname is empty using the strings.ContainsNonSpace functionality.

So I added a $class variable to store some output, and our template looks like this:



{{ $u := urls.Parse .Destination }}
{{ $class := "" }}

{{ if not (strings.ContainsNonSpace $u.Hostname)}}
    {{ $class = "relative" }}
{{ end }}

<a href="{{ .Destination | safeURL }}" 
class="{{ $class }}"
data-domain="{{ $u.Hostname }}">
    <span class="link-text">{{ .Text | safeHTML }}</span>
    <span class="hostname-text">{{ $u.Hostname }}</span>
</a>


the monster css selector looks like:



a[href]:not([href*="://localhost"]):not(.relative) .link-text {
    display: none;
}


And this renders like this:

Screenshot, looking better, but still some missing text

Which is looking loads better, but has nixed the explicit text on the last four of those links 😢 and is still showing the domain for the absolute link too.

I tried a few variations with the CSS, but this is the best it got.

I needed to make sure I could also explicitly detect an external link AND / OR one with explicit text.

For this, I needed some more template magic.



{{ $u := urls.Parse .Destination }}
{{ $yourDomain := "localhost" }}
{{ $class := "" }}

{{ if not (strings.ContainsNonSpace $u.Hostname)}}
    {{ $class = "relative" }}
{{ else }}
    {{ if not (compare.Eq $u.Hostname $yourDomain) }}
        {{ $class = "external" }}
        {{ if (compare.Eq $u.String .Text) }}
            {{ $class = "external implied"}}
        {{ end }}
    {{ end }}
{{ end }}

<a href="{{ .Destination | safeURL }}" 
class="{{ $class }}" 
data-domain="{{ $u.Hostname }}">
    <span class="link-text">{{ .Text | safeHTML }}</span>
    <span class="hostname-text">{{ $u.Hostname }}</span>
</a>


Now we have a way, by comparing the $u.Hostname to a $yourDomain variable, to see if it explicitly external



{{ if not (compare.Eq $u.Hostname $yourDomain) }}


AND we subsequently check to see if the url is the same as the .Text



{{ if (compare.Eq $u.String .Text) }}


And change the class appropriately. Now we're getting closer! Our rendered deeplink looks like this:



<a href="https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats" 
   class="external implied" 
   data-domain="www.cats.org.uk">
    <span class="link-text">https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats</span>
    <span class="hostname-text">www.cats.org.uk</span>
</a>


We can update our CSS now, to be a little more readable:



a.relative {
  .hostname-text {
    display: none;
  }
}

a.external:not(.implied) {
  .hostname-text { display:none }
}

a.external.implied {
  .link-text { display: none; }
}


And this renders like this. Pretty much nails it...

Screenshot, all text restored, but is showing the link on the absolute internal link

Bingo!

Grr, then I noticed the absolute link is not accounted for. A simple default on the $class variable, and an additional definition in the CSS, and it's fixed.



{{ $u := urls.Parse .Destination }}
{{ $yourDomain := "localhost" }}
{{ $class := "internal" }}

{{ if not (strings.ContainsNonSpace $u.Hostname)}}
    {{ $class = "relative" }}
{{ else }}
    {{ if not (compare.Eq $u.Hostname $yourDomain) }}
        {{ $class = "external" }}
        {{ if (compare.Eq $u.String .Text) }}
            {{ $class = "external implied"}}
        {{ end }}
    {{ end }}
{{ end }}

<a href="{{ .Destination | safeURL }}" 
class="{{ $class }}" 
data-domain="{{ $u.Hostname }}">
    <span class="link-text">{{ .Text | safeHTML }}</span>
    <span class="hostname-text">{{ $u.Hostname }}</span>
</a>


And the CSS...



a.internal,
a.relative {
  .hostname-text {
    display: none;
  }
}

a.external:not(.implied) {
  .hostname-text { display:none }
}

a.external.implied {
  .link-text { display: none; }
}


...and bingo.

Over-engineering

Yep, that's it. I think this meets the needs of what we wanted in the first place. Namely that it only shows the top-level domain for those implied-text, deeplink urls, and everything else looks as expected...

But there's a little more...

Don't forget, Hugo allows us to pass in a 'title' into our markdown, like this:



[Custom text, deep link, with title](https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats "Lost & found cats")


But that title attribute is not currently rendered by our customer render hook. Let's fix that:



{{ $u := urls.Parse .Destination }}
{{ $yourDomain := "localhost" }}
{{ $class := "internal" }}

{{ if not (strings.ContainsNonSpace $u.Hostname)}}
    {{ $class = "relative" }}
{{ else }}
    {{ if not (compare.Eq $u.Hostname $yourDomain) }}
        {{ $class = "external" }}
        {{ if (compare.Eq $u.String .Text) }}
            {{ $class = "external implied"}}
        {{ end }}
    {{ end }}
{{ end }}

<a href="{{ .Destination | safeURL }}" 
{{ with .Title}}title="{{ . }}"{{ end }} 
class="{{ $class }}" 
data-domain="{{ $u.Hostname }}">
    <span class="link-text">{{ .Text | safeHTML }}</span>
    <span class="hostname-text">{{ $u.Hostname }}</span>
</a>


We've simply tested to see if the .Title variable exists, and if it does, add the title attribute. Our link now renders like:



<a href="https://www.cats.org.uk/help-and-advice/lost-found-and-feral-cats/lost-found-and-feral-cats" 
   title="Lost &amp; found cats" 
   class="external" 
   data-domain="www.cats.org.uk">
    <span class="link-text">Custom text, deep link, with title</span>
    <span class="hostname-text">www.cats.org.uk</span>
</a>


Not a big deal, but what's nice is that you can use the existence of the title attribute to give some of those deeplinks a bit more context, without showing the whole URL. We add this to the end of our CSS:



a[title]::after {
  content: '(' attr(title) ')';
}


and we get titles. Which you can style if you want. It also gives you the title on when the mouse hovers over the link.

Screenshot, now showing content from the title attribute

One more thing

Last of all, I want my external links to always open in a new tab, so we can add a target attribute to our link. And that is the solution!

Solution

/layouts/_default/_markup/render-link.html



{{ $u := urls.Parse .Destination }}
{{ $yourDomain := "localhost" }}
{{ $class := "internal" }}
{{ $target := "" }}

{{ if not (strings.ContainsNonSpace $u.Hostname)}}
    {{ $class = "relative" }}
{{ else }}
    {{ if not (compare.Eq $u.Hostname $yourDomain) }}
        {{ $class = "external" }}
        {{ $target = "_blank" }}
        {{ if (compare.Eq $u.String .Text) }}
            {{ $class = "external implied"}}
        {{ end }}
    {{ end }}
{{ end }}

<a href="{{ .Destination | safeURL }}" 
{{ with .Title}}title="{{ . }}"{{ end }} 
target="{{ $target }}"
class="{{ $class }}" 
data-domain="{{ $u.Hostname }}"><span class="link-text">{{ .Text | safeHTML }}</span> <span class="hostname-text">{{ $u.Hostname }}</span></a>


And the final CSS...




a.internal,
a.relative {
  .hostname-text {
    display: none;
  }
}

a.external:not(.implied) {
  .hostname-text { display:none }
}

a.external.implied {
  .link-text { display: none; }
}

a[title]::after {
  content: '(' attr(title) ')';
}


Obviously, this is a little over-engineered, but it gives you a lot of flexibility to display links however you like.

Enjoy!

If you found this useful, or have feedback please drop me a line @toychicken

Finally, if you too love cats, make a donation to the Cats protection league today. 🐈‍⬛


This content originally appeared on DEV Community and was authored by Leigh Garland


Print Share Comment Cite Upload Translate Updates
APA

Leigh Garland | Sciencx (2024-10-07T09:11:57+00:00) Using Hugo Render hooks to make links bend to your will. Retrieved from https://www.scien.cx/2024/10/07/using-hugo-render-hooks-to-make-links-bend-to-your-will/

MLA
" » Using Hugo Render hooks to make links bend to your will." Leigh Garland | Sciencx - Monday October 7, 2024, https://www.scien.cx/2024/10/07/using-hugo-render-hooks-to-make-links-bend-to-your-will/
HARVARD
Leigh Garland | Sciencx Monday October 7, 2024 » Using Hugo Render hooks to make links bend to your will., viewed ,<https://www.scien.cx/2024/10/07/using-hugo-render-hooks-to-make-links-bend-to-your-will/>
VANCOUVER
Leigh Garland | Sciencx - » Using Hugo Render hooks to make links bend to your will. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/10/07/using-hugo-render-hooks-to-make-links-bend-to-your-will/
CHICAGO
" » Using Hugo Render hooks to make links bend to your will." Leigh Garland | Sciencx - Accessed . https://www.scien.cx/2024/10/07/using-hugo-render-hooks-to-make-links-bend-to-your-will/
IEEE
" » Using Hugo Render hooks to make links bend to your will." Leigh Garland | Sciencx [Online]. Available: https://www.scien.cx/2024/10/07/using-hugo-render-hooks-to-make-links-bend-to-your-will/. [Accessed: ]
rf:citation
» Using Hugo Render hooks to make links bend to your will | Leigh Garland | Sciencx | https://www.scien.cx/2024/10/07/using-hugo-render-hooks-to-make-links-bend-to-your-will/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.