Id: Open links in OSM notes in new tab

Created on 6 Aug 2020  路  9Comments  路  Source: openstreetmap/iD

For example notes from StreetComplete often have a linked image. These links should be opened in a new tab instead of the current one.

good first issue usability

Most helpful comment

I've made a pull request (#7893) for this based on previous discussion. Solution now uses a much simpler function for determining internal links, and provided d3 functions.

All 9 comments

I've been looking into this briefly but I can't find where link tags are constructed. I'm guessing there's some sort of user-input parsing library that's being used to clean any comment data, that also detects and wraps links? Maybe this is coming from the API. I think I might need to use a CSS selector and manually append a target=_blank attr for any link tags pointing away from OSM hosts (something that would move your view within the window).

Any ideas? :)

Okay, so I've gone on the assumption and written a helper function to preprocess any comments for links, adding target=_blank attribute to any which point to 'external domains'.

At the moment, I'm considering any link whose hostname does not include "osm.org" or "openstreetmap.org" as an external domain.

There are 3 things to note with this:

  1. There may be more domains than just these two I've considered so far
  2. A domain such as foosm.org would be considered non-external, due to containing "osm.org" in the host
  3. I don't have a way of differentiating between within-map and within-site links, such as user profile vs. map node. It would probably be preferable to open a within-site but out-of-map link in a new tab. I'd need a full list of all endpoints of either category, based on how I'm implementing it now.

Here's a preview: https://www.youtube.com/watch?v=MFpImKkY9gA


Here's what I've written for it (wary of making a PR just yet):

// This is all in note_comments.js
mainEnter
  .append('div')
  .attr('class', 'comment-text')
  .html((d) => preprocessLinkTags(d.html));

/**
 * Add target="_blank" to any comment links pointing away from the site
 * That is, any link with domain other than those in ignoredDomains.
 */ 
const preprocessLinkTags = (commentTextHtml) => {
  if (typeof commentTextHtml !== 'string') return commentTextHtml;
  try {
    const template = document.createElement('template');
    const trimmedCommentTextHtml = commentTextHtml.trim();
    template.innerHTML = trimmedCommentTextHtml;

    const links = Array.from(template.content.querySelectorAll('a'));
    const ignoredDomains = ['openstreetmap.org', 'osm.org']; // Possibly more?

    // Filter links based on their hostname not including domains in ignoredDomains
    links
      .filter((link) => {
        const hostname = new URL(link).hostname;

        // Find if any ignoredDomains are found in the hostname (e.g. "api06.dev.osm.org" will be matched)
        return ignoredDomains.findIndex(d => hostname.includes(d)) === -1; 
      })
      .forEach((link) => link.setAttribute('target', '_blank'));

    return template.content.firstChild.innerHTML;
  } catch (error) {
    // Bail without changing if something goes wrong
    return commentTextHtml; 
  }
};

You could use d3 to select all the a elements in the comment-text div and add the attribute to them all like so:

mainEnter
  .append('div')
    .attr('class', 'comment-text')
    .html(d => d.html)
  .selectAll('a')
    .attr('rel', 'noopener')
    .attr('target', '_blank');

You could use d3 to select all the a elements in the comment-text div and add the attribute to them all like so:

Ah! I'm super unfamiliar with d3. I'll still want to filter which tags open, something like:

mainEnter
  .append('div')
    .attr('class', 'comment-text')
    .html((d) => d.html)
  .selectAll('a')
    .filter(isExternalLink)
      .attr('rel', 'noopener')
      .attr('rel', 'nofollow')
      .attr('target', '_blank');

Does this look reasonable?

To answer your question about where the links are actually generated I had a look.

Looks like the note information returned by the API is being parsed here: https://github.com/openstreetmap/iD/blob/248b29983d273aa28877c47729af936644044481/modules/services/osm.js#L328-L367

Which involves the parseComments function in the same file on L150. However, that all just seems to be parsing the json response and not doing anything with the note content. The parsed properties are all fed into the osmNote object constructor in https://github.com/openstreetmap/iD/blob/248b29983d273aa28877c47729af936644044481/modules/osm/note.js#L25-L44 which again doesn't seem to be doing anything with the contents. So I think the content must be coming in pre-linked from the API.

Ah! I'm super unfamiliar with d3. I'll still want to filter which tags open, something like:

Does this look reasonable?

Hey, yes that looks good to me! I'm not the most well versed in d3 myself, but have played with it enough to know when it can make life easier 馃槃

Hey, yes that looks good to me! I'm not the most well versed in d3 myself, but have played with it enough to know when it can make life easier 馃槃

Great, I'll give a test and see how it goes with this new format. Thanks so much for all this!

I've made a pull request (#7893) for this based on previous discussion. Solution now uses a much simpler function for determining internal links, and provided d3 functions.

Done by @JeeZeh in #7893.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jidanni picture jidanni  路  3Comments

rivermont picture rivermont  路  3Comments

thibaultmol picture thibaultmol  路  3Comments

mvl22 picture mvl22  路  3Comments

tordans picture tordans  路  3Comments