jupyter-book icon indicating copy to clipboard operation
jupyter-book copied to clipboard

Open external URLs in new tab

Open markusritschel opened this issue 4 years ago • 29 comments

Hi, Is there a configuration flag, which opens all links going to external sites in a new tab? Thanks a lot in advance!

markusritschel avatar Nov 01 '20 21:11 markusritschel

hi @markusritschel I don't think is is an option for jupyter-book.

It may not be too hard to support as a feature request: https://stackoverflow.com/questions/25583581/add-open-in-new-tab-links-in-sphinx-restructuredtext

mmcky avatar Nov 03 '20 04:11 mmcky

just as part of this discussion - we've had some feedback on our projects that some users don't like this as default behaviour with a preference to open links in a new tab themselves. But I guess the argument for external only links is that you can't tell the difference between internal and external links in many cases.

mmcky avatar Nov 03 '20 04:11 mmcky

Hi @mmcky. Thanks for the answer! Maybe another option to make them recognizable would be adding a tiny in-line icon to all links starting with a http/https during the compiling process.?

markusritschel avatar Nov 03 '20 09:11 markusritschel

thanks @markusritschel -- that's a nice idea too. I think a user specified option to enable new tab for external links should be achievable. Thanks for the feedback and ideas. @AakashGfude this might be something we can implement in myst_nb and add a conf variable mystnb_externallink_tabs = False/True [Default: False]

mmcky avatar Nov 03 '20 22:11 mmcky

I feel like this should be its own separate sphinx extension, since you'd need to over-write Sphinx's own "link writing" nodes to adopt this behavior, I suspect

choldgraf avatar Nov 03 '20 23:11 choldgraf

Can we hijack the output of the HTML renderer? or alter the a tag in HTML in sphinx-book-theme. As the solution is just to add target=__blank attribute in a tag. And also all external links, should probably be opened in a new tab as a default? Else it takes the user away from the website. It is considered good practice in the web dev world, as the websites don't want users to leave their site.

AakashGfude avatar Nov 04 '20 00:11 AakashGfude

If we knew the basename of the site (e.g. https://mysite.org/book) then we could always do a "dumb" check for whether a link's target started with this, and if not, add the target=__blank bit to it. Seems like we could do this with like 5 lines of JS code 🤷‍♂️ (or, do it on the Sphinx side before the HTML is written)

choldgraf avatar Nov 10 '20 22:11 choldgraf

@choldgraf I guess we can also do this with beautifulSoup, when we are updating other attributes in sphinx-book-theme or JS one also looks good to me. Want me to give this a try?

AakashGfude avatar Nov 10 '20 22:11 AakashGfude

hey @choldgraf and @AakashGfude can't we just test for http or https and apply to those links. From memory sphinx uses the following html structure for internal links

<a class="reference internal" href="amss.html#equation-ts-gov-wo4">(39.8)</a>

mmcky avatar Nov 10 '20 23:11 mmcky

@mmcky true. or we can also check for an external class. For external links, similarly, sphinx has

<a class="reference external"

AakashGfude avatar Nov 10 '20 23:11 AakashGfude

@AakashGfude ok great.

The link above provides a really simple approach to updating the translator

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)

mmcky avatar Nov 10 '20 23:11 mmcky

@mmcky looks neat. so we are going with making this a part of jupyter-book. Or will it be a part of the theme?

AakashGfude avatar Nov 10 '20 23:11 AakashGfude

I see this as a myst_nb addition or super simple extension. I view jupyter-book as a cli/interface to the executable books software stack.

mmcky avatar Nov 10 '20 23:11 mmcky

ooh sorry, myst_nb vs theme then. I am happy with it being permanent for all software using myst_nb as external links opening in a new tab is a good default approach.

AakashGfude avatar Nov 10 '20 23:11 AakashGfude

as external links opening in a new tab is a good default approach.

I think this is up for debate :-). I think it should be an option (but I don't mind if it is on by default).

mmcky avatar Nov 10 '20 23:11 mmcky

ooh ok, so you are saying, we should activate this translator function based on mystnb_externallink_tabs = False/True ?

app.set_translator('html', PatchedHTMLTranslator)

looking at the code at first, it seemed like you wanted to make it default. but all clear now.

AakashGfude avatar Nov 11 '20 00:11 AakashGfude

👍 but let's call it MySTNBHTMLTranslator and other things can be added (as needed in the future)

mmcky avatar Nov 11 '20 00:11 mmcky

Hmm, don't custom translators break things when we build on read the docs? Also wouldn't this then also not work with any other theme or extension that defined a custom translator? (I think this is also why RTD breaks too, since they use a ReadTheDocs HTML Translator, I seem to remember that)

choldgraf avatar Nov 11 '20 02:11 choldgraf

hey @choldgraf not sure I follow fully. This should update the in-built HTMLTranslator to add a bit of logic around adding the _black for external links. The builder shouldn't change (i.e. html would still be the builder). rtd will use jupyter-book to compile the projects?

mmcky avatar Nov 12 '20 03:11 mmcky

this is a good question though -- will rtd use jupyter-book or myst-nb to build. It will need to use jupyter-book to make use of _config.yml etc. right?

mmcky avatar Nov 12 '20 03:11 mmcky

@choldgraf I think I see what you're saying here. Doesn't it depend on the order in which the app get's initialised.

For example if

app.set_translator('html', MySTNBHTMLTranslator)

get's added before any extensions get loaded by sphinx then updated visit/departs should apply to the MySTNBHTMLTranslator. But perhaps a safer way to go is to setup a little extension?

mmcky avatar Nov 12 '20 03:11 mmcky

yeah - I think this issue described some of the challenges:

https://github.com/readthedocs/readthedocs.org/issues/6690

but maybe translators can stack somehow?

choldgraf avatar Nov 12 '20 15:11 choldgraf

thanks @choldgraf that thread is really informative.

~~Maybe the best (but not shortest) strategy on this is to add it as a feature upstream in sphinx?~~ After doing a bit of research it looks like they have had this conversation and elected not to support opening in new tabs https://groups.google.com/g/sphinx-users/c/NnZVh2X79co?pli=1 with the following opinion:

Hi,
links with an explicit "new window" target are deprecated in HTML; it
should be the choice of the user where to open the link.
cheers,
Georg

They have done it for the htmlhelp builder -- which we could draw some ideas on.

@choldgraf what do you think about the suggestion by @markusritschel to have a small in-line hover icon to could appear when a mouse hovers over the link for opening in a "new-tab"?

mmcky avatar Nov 12 '20 22:11 mmcky

Hmmm - one thing we could do is add use an :after rule and add this little icon: https://fontawesome.com/icons/external-link-alt ? Maybe if it is small enough it wouldn't be too invasive?

Does anyone know of other common patterns that sites use to style external links differently?

I wonder if this is something that @rowanc1 has considered for iooxa?

choldgraf avatar Nov 21 '20 01:11 choldgraf

Hi @choldgraf, I think this is a good idea. And it could be implemented with a simple css rule in the theme's css, for example.? If all external links have the class reference external as @AakashGfude said, something like

a.reference.external:after { /* setting image here */ }

would already do the work. Or, more general (ignoring the own domain, anchors and internal links):

a:not([href*='myjupyterbookdoma.in']):not([href^='#']):not([href^='/']):after { /* setting image here */ }

Another option is using javascript, of course, like they show here: https://css-tricks.com/snippets/jquery/target-only-external-links/. (One could also use favicons of the external web site as pointed out here. But this is then not uniform anymore.)

And in the end, this then linked to a flag in the configuration like mystnb_externallink_tabs = False/True as suggested by @mmcky would be really convenient. But I haven't made myself familiar enough with Sphinx to know how easy this is to (de)activate a css rule.?

markusritschel avatar Nov 23 '20 10:11 markusritschel

Yeah - as one example, the following rule:

a.reference.external:after {
    content: "\f35d";
    font-family: 'Font Awesome 5 Free';
    font-size: .7em;
    vertical-align: text-top;
    margin-left: .1em;
    color: grey;
}

yields:

image

choldgraf avatar Nov 23 '20 20:11 choldgraf

Hi @choldgraf , Thanks for providing the CSS rule above. Could you please tell how to properly import Font Awesome to that your customization works? I tried adding:

@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css');

in a _static/customizations.css file which contains also the code below:

a.reference.external:after {
    content: "\f35d";
    font-family: 'Font Awesome 6 Free';
    font-size: .7em;
    vertical-align: text-top;
    margin-left: .1em;
    color: grey;
}

but this did not work. I still get an empty rectangle instead of the Font Awesome icon.

Thanks!

allanleal avatar Nov 18 '22 14:11 allanleal

couldn't get font-family: 'Font Awesome ...' to work, but plain unicode content: "\2b77" seems to be fine.

casperdcl avatar Sep 26 '23 09:09 casperdcl