Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Make VLink component that wraps around both external and internal links #879

Merged
merged 23 commits into from
Feb 21, 2022

Conversation

obulat
Copy link
Contributor

@obulat obulat commented Feb 16, 2022

Fixes

Fixes #468 by @obulat

Description

This PR supercedes #468, based on @krysal 's comment about wrapping internal links, too.

Now, VLink is a wrapper component that should be used for all links. It determines whether the link is internal or external by checking if the href attr starts with /.

External links:

  • use a element.
  • add target='_blank' that allows the links to open in another tab, instead of causing errors in an iframe.
  • adds rel='noopener noreferrer' to prevent some referrer vulnerabilities in older browsers (target='_blank'` is enough for modern browsers)

Internal links:

  • use NuxtLink component.
  • wrap the href in app.localePath to ensure that the correct locale is used, and pass it as the to prop to NuxtLink.

This PR moves the warning for missing href from VButton to VLink, so VButton doesn't have to handle the lack of href property.

Testing Instructions

Run the app and test out internal links and external links. Also check that the links in the navbar (pages) work as expected. External links should always open in a new tab.

Checklist

  • My pull request has a descriptive title (not a vague title like Update index.md).
  • My pull request targets the default branch of the repository (main) or a parent feature branch.
  • My commit messages follow best practices.
  • My code follows the established code style of the repository.
  • I added or updated tests for the changes I made (if applicable).
  • I added or updated documentation (if applicable).
  • I tried running the project locally and verified that there are no visible errors.

Developer Certificate of Origin

Developer Certificate of Origin
Developer Certificate of Origin
Version 1.1

Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.


Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.

@obulat obulat added 🟥 priority: critical Must be addressed ASAP 🛠 goal: fix Bug fix 🕹 aspect: interface Concerns end-users' experience with the software labels Feb 16, 2022
@obulat obulat requested a review from a team as a code owner February 16, 2022 09:45
@obulat obulat self-assigned this Feb 16, 2022
@obulat obulat requested review from zackkrida and stacimc February 16, 2022 09:45
Copy link
Contributor

@sarayourfriend sarayourfriend left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Still need to test this locally but I'm still excited for this 🎉 The only blocker right now is the attrs destructuring... I wish there was an eslint rule to warn against that, I feel like 99% of the time it's probably not the right thing to do. Maybe that's wrong though.

src/components/VLink.vue Outdated Show resolved Hide resolved
const { app } = useContext()
const isInternal = computed(() => attrs.href?.startsWith('/'))
const linkComponent = computed(() => (isInternal.value ? 'NuxtLink' : 'a'))
const { href, ...otherProperties } = attrs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Destructuring these from attrs will make their values non-reactive in the computed for linkProperties below. To maintain reactivity they need to stay as properties on props or attrs.

test/unit/specs/components/v-link.spec.js Outdated Show resolved Hide resolved
@sarayourfriend
Copy link
Contributor

I noticed three more <a>'s in the app that could use this:

  1. DownloadButton (It's still using the DropdownButton component which doesn't use VButton underneath)
  2. FooterSection: link to 4v licenses
  3. FooterSection: link to FontAwesome

Copy link
Contributor

@sarayourfriend sarayourfriend left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MediaTag changes seem unrelated. Was it a faulty merge perhaps? 🤔

src/components/VLink.vue Outdated Show resolved Hide resolved
{ immediate: true }
let linkProperties = computed(() =>
isInternal.value
? { to: app?.localePath(props.href) ?? props.href }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what case is app null? 😱

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When testing 😆 That was probably a lazy hack to ensure tests pass without mocking the nuxt context :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh dear! I wish it was easier to set up a globally enabled mock of the nuxt stuff 🤔

Co-authored-by: sarayourfriend <24264157+sarayourfriend@users.noreply.github.com>
@obulat
Copy link
Contributor Author

obulat commented Feb 17, 2022

The MediaTag changes seem unrelated. Was it a faulty merge perhaps? 🤔

Do you mean src/components/VMediaTag/meta/VMediaTag.stories.mdx changes, @sarayourfriend ?

This PR is kind of very small: adding one component, but it touches so many files!

@krysal krysal added 🟨 priority: medium Not blocking but should be addressed soon and removed 🟥 priority: critical Must be addressed ASAP labels Feb 17, 2022
@krysal
Copy link
Member

krysal commented Feb 17, 2022

Relabelling as "priority: medium" as the linked issue is actually fixed in the main branch and this is more of an improvement.

@sarayourfriend
Copy link
Contributor

Oh sorry, I was looking specifically at the merge commit, not the full diff for the PR 🤦

Anyway, it looks like making props.href required breaks client-side navigation of the result page 🎉 I guess in the interest of not trying to fix too many things at once here we could just make the links wait until href is defined to render?

…nverse-frontend into add/bigger_vlink_component
@obulat
Copy link
Contributor Author

obulat commented Feb 18, 2022

Relabelling as "priority: medium" as the linked issue is actually fixed in the main branch and this is more of an improvement.

Thank you for relabelling, I was thinking about making it a priority: high, actually.
Just want to note that the issue was about all the external <a> links, not just the homepage licenses. Try clicking on 'Go to source', or links in the pages menu on search-staging. The links will be opened in the same tab, and with the iframe on w.org, this will break.

@obulat
Copy link
Contributor Author

obulat commented Feb 18, 2022

In the single media item detail pages we have some links that are created before the href values are available (while the image/audio is being fetched).

To make VLink component handle links which set href after loading the data, I set it use a span element if href is not available (is undefined, '', or '#'). So, VLink can actually behave incorrectly if you forget to add an href to it.

Another solution could be to use v-if for such links, but I think it would be more difficult to remember that.
I see a great advantage in using the VLink as in this PR:

  • we won't forget to set target="_blank" and stop a lot of errors for users who want to open an external link.
  • we won't forget to add the localePath to NuxtLinks.

What do you think, @WordPress/openverse-frontend ?

Copy link
Contributor

@sarayourfriend sarayourfriend left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Things no longer crash because of this (there are still other crashes but unrelated to this change 👍)

@@ -21,19 +23,26 @@ export default defineComponent({
props: {
href: {
type: String,
required: true,
validator: (v) => !['', '#'].includes(v),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we can keep the validator, otherwise accidental long-lived invalid href values won't ever get caught.

@sarayourfriend
Copy link
Contributor

sarayourfriend commented Feb 18, 2022

@obulat a solution I've used in past projects to easily identify "bad links" is to add some kind of horrific inline-styles to them that make them obviously stand out. Something like style="background-color: hotpink; border: 2rem solid green;" (restricted to dev environments of course).

Copy link
Member

@zackkrida zackkrida left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm! I've clicked around pretty extensively and text searched for raw <a> tags, in the repo, and didn't find anything that shouldn't be there.

Question for @WordPress/openverse-frontend: Does anyone know of an eslint rule we could introduce to warn on raw <a> tag usage in our Vue templates?

@sarayourfriend
Copy link
Contributor

@zackkrida The no-restricted-syntax rule can probably do it.

Copy link
Member

@krysal krysal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@obulat You were right! I forgot about the untouched links on internal pages. It's great we can now be safe about external links with these changes 🚀

@zackkrida
Copy link
Member

@obulat I made a small companion PR with an eslint rule to enforce VLink usage:

#916

@obulat obulat merged commit 69e096f into main Feb 21, 2022
@obulat obulat deleted the add/bigger_vlink_component branch February 21, 2022 02:24
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
🕹 aspect: interface Concerns end-users' experience with the software 🛠 goal: fix Bug fix 🟨 priority: medium Not blocking but should be addressed soon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

External links NOT opening in the Openverse iframe
4 participants