Making external links open in a new window in wagtail
Asked Answered
B

2

10

I recently implemented adding target="_blank" to external links like this:

@hooks.register('after_edit_page')
def do_after_page_edit(request, page):
    if hasattr(page, "body"):
        soup = BeautifulSoup(page.body)
        for a in soup.findAll('a'):
            if hasattr(a, "href"):
            a["target"] = "_blank"
        page.body = str(soup)
        page.body = page.body.replace("<html><head></head><body>", "")
        page.body = page.body.replace("</body></html>", "")
        page.body = page.body.replace("></embed>", "/>")
        page.save()

@hooks.register('construct_whitelister_element_rules')
def whitelister_element_rules():
    return {
        'a': attribute_rule({'href': check_url, 'target': True}),
    }

Problems:

  1. Beautiful soup messes with the output, adding html, head & body tags - Don't put html, head and body tags automatically, beautifulsoup

  2. It also messes with the embed tags - How to get BeautifulSoup 4 to respect a self-closing tag?

  3. Hence my crappy "fix" manually replacing parts of the output with blank strings.

Question:

What is the correct and best way to do this?

Buffalo answered 23/10, 2015 at 11:11 Comment(0)
B
24

Starting with Wagtail v2.5, there is an API to do customisations like this as part of Wagtail’s rich text processing: Rewrite handlers, with the register_rich_text_features hook.

Here is an example of using this new API to make a rewrite handler that sets a target="_blank" attribute to all external links:

from django.utils.html import escape
from wagtail.core import hooks
from wagtail.core.rich_text import LinkHandler


class NewWindowExternalLinkHandler(LinkHandler):
    # This specifies to do this override for external links only.
    # Other identifiers are available for other types of links.
    identifier = 'external'

    @classmethod
    def expand_db_attributes(cls, attrs):
        href = attrs["href"]
        # Let's add the target attr, and also rel="noopener" + noreferrer fallback.
        # See https://github.com/whatwg/html/issues/4078.
        return '<a href="%s" target="_blank" rel="noopener noreferrer">' % escape(href)


@hooks.register('register_rich_text_features')
def register_external_link(features):
    features.register_link_type(NewWindowExternalLinkHandler)

In this example I'm also adding rel="noopener" to fix a known security issue with target="_blank".


Compared to previous solutions to this problem, this new approach is the most reliable: it’s completely server-side and only overrides how links are rendered on the site’s front-end rather than how they are stored, and only relies on documented APIs instead of internal ones / implementation details.

Bozeman answered 24/4, 2019 at 20:2 Comment(2)
Does this deal with links not inside rich text though?Buffalo
It’s only for rich text links, as those links are the only ones in Wagtail where the link markup is generated directly with Wagtail. For other links, adding the target attribute should just be a matter of editing the templates. Other options like using JS will work too, but then that’s not really specific to Wagtail in any way.Bozeman
A
9

Have been struggling with the same problem and couldn’t achieve it using wagtailhooks. My initial solution was to manipulate the content in base.html, using a filter. The filter to cut pieces of code works perfectly when placed in the content block, example:

{{ self.body|cut: ‘ href="http:’}}

Above filter deletes parts of the content, but unfortunately ‘replace’ is not available as a filter (I´m using Python 3.x). Therefor my next approach was building a custom_filter to create ´replace´ as filter option. Long story short: It partly worked but only if the content was converted from the original ‘StreamValue’ datatype to ‘string’. This conversion resulted in content with all html tags shown, so the replacement did not result in working html. I couldn´t get the content back to StreamValue again and no other Python datatype remedied the issue. Eventually JQuery got the job done for me:

$(document).ready(function(){
$('a[href^="http://"]').attr('target', '_blank');
});        

This code adds ‘target="_blank"’ to each link containing ‘http://’, so all internal links stay in the existing tab. It needs to be placed at the end of your base.html (or similar) and of course you need to load JQuery before you run it. Got my answer from here . Don’t know if JQuery is the correct and best way to do it, but it works like a charm for me with minimal coding.

Artichoke answered 14/12, 2015 at 15:34 Comment(5)
I'm definitely not someone to shy away from using jquery like this, and it looks like it doesn't hurt seo (not sure why it would, but worth a check). Yet, this doesn't exactly feel like the 100% best solution either. Feel free to disagree with me and I'll give you the answer :) Otherwise I'll probably leave it open.Buffalo
Manipulating links using Wagtailhooks is probably possible. However, in my opinion there are a few reasons why you would rather use JQuery: 1.The manipulation code in hooks will hit the core of your website, therefor if it bugs, potentially your entire site could go down. If the JQuery bugs, your site is unaffected, only the external links will open in the same tab. 2.Using Wagtailhooks to manipulate content like this, is probably not in direct scope of what it's intended for. Otherwise there would probably be more information available on it and this question would've been answered earlier.Artichoke
3. How will the code hold up with future Wagtail releases? Will it work with all your future additions? Why would you create a potential problem when it is not absolutely necessary? My idea is, the simpler solution is mostly the best and better to maintain in the future. And if you are already using JQuery in your site, it's no big deal to also use it for your external links.Artichoke
Just to confirm, you're absolutely right. This is now my preferred method of achieving this.Buffalo
You should also add $('a[href^="https://"]').attr('target', '_blank'); to cover https urls.Lindsley

© 2022 - 2024 — McMap. All rights reserved.