Hangchen Y.
Hangchen Y.

Reputation: 355

Add open-in-new-tab links in Sphinx/reStructuredText

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!

Upvotes: 6

Views: 4584

Answers (4)

Tiger-222
Tiger-222

Reputation: 7150

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",
]

Upvotes: 1

rmorshea
rmorshea

Reputation: 852

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)

Upvotes: 1

CodeManX
CodeManX

Reputation: 11905

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).

Upvotes: 2

Jaguarfi
Jaguarfi

Reputation: 142

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

Upvotes: 1

Related Questions