Add open-in-new-tab links in Sphinx/reStructuredText [duplicate]
Asked Answered
B

4

6

Here is a solution for the same problem:

Open a link in a new window in reStructuredText

However, when the document has a lot of links (especially when the links are in a table), this solution will not work well.

Are there any other solutions? Thanks for any help!

Belva answered 30/8, 2014 at 14:44 Comment(2)
It's a little different. The solution in that question works when the links are few and not in the table. But it would be terrible when the document contains lots of links.Belva
The question is the same. The answer might be awful and unpleasant but that just means someone should provide a better answer (though unfortunately it appears that there isn't a better answer and rst/sphinx is just missing this ability).Muskogee
H
2

To add target="_blank" but also rel="noopener noreferrer" to external links, it is necessary to copy Sphinx' entire visit_reference() method to patch this in:

atts['target'] = '_blank'
atts['rel'] = 'noopener noreferrer'

Sphinx' original code does not process a rel attribute that one might set like node['rel'] = 'noopener noreferrer', whereas it does apply a target attribute (atts['target'] = node['target']).

Below you find my solution. The base code is from Sphinx 3.0.3 (sphinx/writers/html.py):

from sphinx.writers.html import HTMLTranslator
from docutils import nodes
from docutils.nodes import Element

class PatchedHTMLTranslator(HTMLTranslator):

    def visit_reference(self, node: Element) -> None:
        atts = {'class': 'reference'}
        if node.get('internal') or 'refuri' not in node:
            atts['class'] += ' internal'
        else:
            atts['class'] += ' external'
            # ---------------------------------------------------------
            # Customize behavior (open in new tab, secure linking site)
            atts['target'] = '_blank'
            atts['rel'] = 'noopener noreferrer'
            # ---------------------------------------------------------
        if 'refuri' in node:
            atts['href'] = node['refuri'] or '#'
            if self.settings.cloak_email_addresses and atts['href'].startswith('mailto:'):
                atts['href'] = self.cloak_mailto(atts['href'])
                self.in_mailto = True
        else:
            assert 'refid' in node, \
                   'References must have "refuri" or "refid" attribute.'
            atts['href'] = '#' + node['refid']
        if not isinstance(node.parent, nodes.TextElement):
            assert len(node) == 1 and isinstance(node[0], nodes.image)
            atts['class'] += ' image-reference'
        if 'reftitle' in node:
            atts['title'] = node['reftitle']
        if 'target' in node:
            atts['target'] = node['target']
        self.body.append(self.starttag(node, 'a', '', **atts))
 
        if node.get('secnumber'):
            self.body.append(('%s' + self.secnumber_suffix) %
                             '.'.join(map(str, node['secnumber'])))

def setup(app):
    app.set_translator('html', PatchedHTMLTranslator)

You may paste this directly into your conf.py at the bottom.

Note that this patches the HTMLTranslator. If you use HTML 5 output then you need a modified HTML5Translator (sphinx/writers/html5.py).

Hadlee answered 7/5, 2020 at 23:15 Comment(5)
This works great, and should probably be the accepted answer. Source file link of class function that is patched with this: github.com/sphinx-doc/sphinx/blob/master/sphinx/writers/…Astred
Thanks @ScriptAutomate. I updated my answer, it was html.py and the HTMLTranslator which I had to patch to be effective for make html. I'm not sure how or when html5.py / HTML5Translator is used. It seems to be experimental according to the comment at the top of the code. make html5 gives me an error: Builder name html5 not registered or available through entry pointHadlee
Nice catch. I forgot to say I was doing the same thing and had the same confusion haha the code around visit_reference looked to be the same in both html and html5 when I had last looked. There might be something I missed that made that method differ. I noticed html5.py had comments at the top saying it was experimental.Astred
To use this do I replace html5.py with sphinx/writers/html5.py in my install? Could you please provide an example of syntax to use this? Thanks.Weiland
You need to patch the class, not replace it. As I say in the answer: You may paste the code directly into your conf.py at the bottom to use it.Hadlee
M
1

If you want external links to open in new tabs, add below code to your conf.py:

from sphinx.writers.html import HTMLTranslator
class PatchedHTMLTranslator(HTMLTranslator):
   def visit_reference(self, node):
      if node.get('newtab') or not (node.get('target') or node.get('internal') 
         or 'refuri' not in node):
            node['target'] = '_blank'
            super().visit_reference(node)

def setup(app):
    app.set_translator('html', PatchedHTMLTranslator)

This also gives you the ability to use the newtab parameter to a reference, have it show as internal, but open in a new tab. I use this for linking to slide PDFs.

source: http://jack.rosenth.al/hacking-docutils.html#external-links-in-new-tabs

Minuet answered 6/4, 2020 at 11:4 Comment(2)
Though this made external links open in new tabs for me, this broke my entire TOC, many internal links, etc. The answer provided by @codemanx made external links become new tabs without breaking internal links: https://mcmap.net/q/1759523/-add-open-in-new-tab-links-in-sphinx-restructuredtext-duplicateAstred
This is due to the fact, that the call to the super method is on the wrong nesting level. It needs to be called in any case, not only on external links. I find this change much more reliable, as only the required changes are done, instead of copying the complete code. I made an edit suggestion. It's waiting for approval.Ancilin
D
1

While the solution below isn't as performant as the one from this answer, it's simpler and less brittle in the case visit_reference() is patched in the future. Plus it works seamlessly with both the html and html5 translators:

from sphinx.writers.html import HTMLTranslator
from sphinx.writers.html5 import HTML5Translator
from sphinx.util.docutils import is_html5_writer_available


class PatchedHTMLTranslator(
    HTML5Translator if is_html5_writer_available() else HTMLTranslator
):
    def starttag(self, node, tagname, *args, **attrs):
        if (
            tagname == "a"
            and "target" not in attrs
            and (
                "external" in attrs.get("class", "")
                or "external" in attrs.get("classes", [])
            )
        ):
            attrs["target"] = "_blank"
            attrs["ref"] = "noopener noreferrer"
        return super().starttag(node, tagname, *args, **attrs)


def setup(app):
    app.set_translator("html", PatchedHTMLTranslator)
Diameter answered 18/4, 2021 at 21:34 Comment(0)
A
1

sphinx-new-tab-link might be a good candidate, with less code to type. To enable it, adapt conf.py:

 extensions = [
    # (...)
    "sphinx_new_tab_link",
]
Arondell answered 16/2 at 15:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.