Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Icon for external links #398

Closed
mathieutu opened this issue Aug 19, 2020 · 8 comments
Closed

Icon for external links #398

mathieutu opened this issue Aug 19, 2020 · 8 comments
Labels
question Further information is requested

Comments

@mathieutu
Copy link
Contributor

Hi,
Does Nuxt content provide a way to show a icon after each external link, like Vuepress does?

Exemple:
Screenshot 2020-08-19 at 16 33 10

Thanks.
Mathieu.

@mathieutu mathieutu added the question Further information is requested label Aug 19, 2020
@davydnorris
Copy link

davydnorris commented Aug 25, 2020

You could do this pretty easily right now by using a custom Vue component instead of the markdown link, but I agree, that would be a cool addition. You would want to make it able to be turned on and off as typically it's only used for external links as you said. Alternatively you could make the link interpreter smart so that when it detects an external link in the markdown it adds the icon.

@benjamincanac benjamincanac added theme-v1 Concerns @nuxt/content-theme-docs and removed theme-v1 Concerns @nuxt/content-theme-docs labels Aug 25, 2020
@azrikahar
Copy link
Contributor

Did some digging through Vuepress and how Nuxt Content works, so here are the general findings I have which may help to make it easier to add this feature.

First, Vuepress has a markdown-it plugin that checks links whether they are external links using the regex: /^https?:/ in this line 20.

The same plugin then later on adds the <OutBoundLink /> component right before the closing tag of an external link in this line 83 with line 82 as explanatory comment.

We can see the OutBoundLink file here and find its a functional component showing the SVG for the external link icon. A check on the official Vuepress confirms the correct SVG as seen below:

chrome_CS3sDYJehT


With that said, seems like Nuxt Content uses remark-external-links as stated in the docs to add target=_blank and rel="nofollow noopener noreferrer" to all external links. It is set as one of the default remark plugin over here:

remarkPlugins: [
'remark-squeeze-paragraphs',
'remark-slug',
'remark-autolink-headings',
'remark-external-links',
'remark-footnotes'
],

I believe we can add the logic for inserting the icon in this plugin. The question now would be whether to replace remark-external-links flat out with a custom plugin directly in the default Nuxt Content theme, or provide the custom plugin separately with a guide to follow this section in the docs to override it on an opt-in basis.

Sorry if this ended up being long winded than necessary. Just hope I can help to facilitate this particular feature :)

@davydnorris
Copy link

GREAT detective work!!!

@azrikahar
Copy link
Contributor

azrikahar commented Aug 26, 2020

@davydnorris thank you!

Somehow I was able to find a way to achieve this WITH remark-external-links plugin out of the box yesterday before going to bed. According to the docs, we are able to provide options to the plugins like so:

export default {
  content: {
    markdown: {
      // https://github.com/remarkjs/remark-external-links#options
      remarkExternalLinks: {
        target: '_self',
        rel: 'nofollow'
      }
    }
  }
}

The commented line piqued my interest on what are the other options we have, and apparently it has the content option that does what we want! (the content will be inside a <span> element as mentioned)

I've also noticed that the current theme already has an icon for external link, albeit only used for Edit this page on GitHub links as seen here:

chrome_jjMOpHtAEL

It is the IconExternalLink.vue component in the default Nuxt Content Docs theme here:

<template>
<svg
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</template>


With all the pieces of the puzzle, I was able to make it work with the following single step.

Add External Link Icon

Add the content and contentProperties options to the remark-external-links plugin in the nuxt.config.js file, like so:

content: {
        markdown: {
            remarkExternalLinks: {
                content: {
                    type: "element",
                    tagName: "svg",
                    properties: {
                        className: ["w-4", "h-4", "ml-1", "-mt-1", "stroke-2"],
                        style: ["stroke-linecap: round;", "stroke-linejoin: round;"],
                        fill: "none",
                        viewBox: "0 0 24 24",
                        stroke: "currentColor",
                    },
                    children: [
                        {
                            type: "element",
                            tagName: "path",
                            properties: {
                                d:
                                    "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14",
                            },
                        },
                    ],
                },
                contentProperties: {
                    className: [
                        "inline-block",
                        "align-middle",
                        "text-gray-600",
                        "dark:text-gray-400",
                    ],
                },
            },
        },
    },

The above SVG is basically taken from the IconExternalLink component with minor adjustments, such as contentProperties classes to add to the <span> element wrapping our SVG. I am adding this to the default theme, thus we can use the dark: modifer as well.

Screenshots

Light mode Dark mode
chrome_2NnbnJ3Uig chrome_Jxyb5xQBJT

CodeSandbox Link

https://codesandbox.io/s/distracted-hypatia-z2p7b?file=/nuxt.config.js

Might seem a little hacky, but on the other hand we are using the existing options provided by the remark-external-links so I'm not sure what's the verdict here. At least this approach can easily be opt-in as I planned. Hope it helps!

EDIT: Refer my follow up comment below for a simplified version of this approach (Link)

@davydnorris
Copy link

davydnorris commented Aug 26, 2020

I don't know when the content is parsed vs. when it is rendered, but you can insert normal Vue components directly into your markdown, so I wonder if you couldn't just save IconExternalLink.vue as your own global component, including any styling, and then specify the Vue component in options.content:

content: {
  markdown: {
    remarkExternalLinks: {
      content: {
        type: "element",
        tagName: "icon-external-link",
      },
    },
  },
},

@azrikahar
Copy link
Contributor

azrikahar commented Aug 26, 2020

Just tried it out and you are absolutely correct! The best thing is we can just use icon-external-link without adding it ourselves since it will directly use the one included in the default theme.

We can then simplify the nuxt.config.js file into:

  content: {
    markdown: {
      remarkExternalLinks: {
        content: {
          type: "element",
          tagName: "icon-external-link",
          properties: {
            className: ["w-4", "h-4", "ml-1", "-mt-1"],
          },
        },
        contentProperties: {
          className: ["inline-block", "align-middle", "text-gray-600", "dark:text-gray-400"],
        },
      },
    },
  },

Still had to include the classes for styling since the iconExternalLink is not styled by default, but this is wayyy simpler than before. Definitely could be simpler by still having our own component and throwing the classes into the component directly like you mentioned. @davydnorris thank you for the truly great idea :)

@mathieutu
Copy link
Contributor Author

mathieutu commented Aug 26, 2020

Hey.

Sorry guys to not let you know where I was about that. I'm currently in hollydays, cycling in mountains. 

I actually came to the exact same way of doing last week before leaving, and was planing to make a PR at my return, at the end of the week 😅.

I however used a .link.link-outbound class on the element to let users style it as they want.

Thanks anyway for the work, I hope you at least had fun 😛. 

Matt'

@davydnorris
Copy link

@azrikahar - that's awesome!

If you did make your own component, you could also include a lot, if not all, of the styling in the .vue file itself, but this is a great outcome. I actually use Vuetify in all my projects so I may end up rolling my own component and using the Material Design icons and Vuetify styles, since I have added the default styling into the content (see #379)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants