Skip to main content
Notice removed Comments only by CommunityBot
Post Unlocked by CommunityBot
Post Locked by rene
Notice added Comments only by rene
adjust for CSS changes
Source Link
user3840170
  • 385
  • 1
  • 10
// ==UserScript==
// @name     Stack Exchange: Faster Answer Links
// @grant    none
// @run-at   document-start
// @match    https://*.stackexchange.com/*
// @match    https://*.superuser.com/*
// @match    https://*.stackoverflow.com/*
// @match    https://*.mathoverflow.net/*
// @match    https://*.serverfault.com/*
// @match    https://*.askubuntu.com/*
// @match    https://stackapps.com/*
// @exclude  https://chat.stackexchange.com/*
// @exclude  https://api.stackexchange.com/*
// @exclude  https://data.stackexchange.com/*
// @exclude  https://openid.stackexchange.com/*
// @exclude  https://contests.stackoverflow.com/*
// @exclude  /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

// attaching to window because jQuery attaches *its* delegated
// event handlers to document, and we need to run *after* those

window.addEventListener('click', ev => {
  if (ev.defaultPrevented)
    return;

  const target = ev.target.closest(':any-link');
  
  // ignore non-link clicks
  if (!target)
    return;
  
  // ignore target="_blank" links (especially on review pages)
  if (target.target)
    return;
  
  // ignore clicks with modifier keys; we presume
  // those would not result in normal navigation
  if (ev.ctrlKey || ev.altKey || ev.shiftKey || ev.metaKey)
    return;

  // ignore href="#" pseudo-links
  // (.getAttribute because .href returns the resolved absolute URL)
  if (target.getAttribute('href') === '#')
    return;

  const parseURL = u => {
    let m;
    
    // catch both HTTPS and older plaintext-HTTP links
    if (u.host !== location.host || !/^https?:$/.test(u.protocol))
      return null;
    
    // /posts/comments/<comment_id>
    if (m = /^\/posts\/comments\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { commentId: m[1], commentPostId: null };
    }
    
    // #comment<comment_id>_<owner_id>#comment<comment_id>_<post_id>
    if (/^\/(q|a|questions)\//u.test(u.pathname)
        && (m = /^#comment(\d+)_(\d+)$/u.exec(u.hash))) {
      return { commentId: m[1], commentPostId: m[2] };
    }

    // /a/<answer_id>
    // /questions/<question_id>/<question_title>/<answer_id>
    if (m = /^\/(?:a|questions\/(\d+)\/[^\/]+)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { questionId: m[1] ?? null, answerId: m[2] };
    }
    
    // /q/<question_id>
    // /questions/<question_id>/<question_title>
    if (m = /^\/(?:q|questions)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      const questionId = m[1];
      if (m = /^#(?:answer-)?(\d+)$/u.exec(u.hash))
        return { questionId, answerId: m[1] };
      if (!u.hash || u.hash === '#question')
        return { questionId, answerId: null };
    }
    
    return null;
  };
  
  const insteadGoTo = target => {
    ev.preventDefault();
    location.href = target;
  };
  
  const isThereQuestion = postId =>
    !!document.querySelector(`#question[data-questionid="${postId}"]`);
  const isThereAnswer = postId =>
    !!document.getElementById(`answer-${postId}`);
  const isTherePost = postId =>
    !!document.querySelector(`#answer-${postId}, #question[data-questionid="${postId}"]`)
  
  const u = new URL(target.href);  
  const linkTarget = parseURL(u);
  
  if (!linkTarget)
    return;
  
  if (linkTarget.commentId != null) {
    if (linkTarget.commentPostId != null) {
      const { commentPostId, commentId } = linkTarget;     
      if (isTherePost(commentPostId))
        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
    } else {
      let node;

      // when rerouting comment links, use the magic fragment id
      // (processed by Stack Exchange JS)  instead of the proper
      // element id (natively supported in the browser)
      // because the latter does not work all that well
      // (probably something to do with 'display: contents;')
      // additionally, a magic fragment id will work for comments
      // that have not been loaded yet

      if (node = document.querySelector(`#comment-${linkTarget.commentId}`)) {
        // happy path: comment already loaded, just navigate to it

        const commentId = node.dataset.commentId;
        const commentPostId = node.closest('.comments[data-post-id]').dataset.postId;

        // use the magic fragment id instead of the proper element id,
        // because the latter does not work all that well
        // (probably something to do with 'display: contents;')

        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
      }
      
      // if there are no hidden comments on the current page, don't bother,
      // just do a full page reload
      if (!document.querySelector('.js-post-comments-containercomponent .js-show-link:not(.dno)')) {
        return;
      }
      
      // prevent default now; if fetching the target fails,
      // we will just redirect manually later
      ev.preventDefault();
      
      (async () => {
        target.style.cursor = 'progress';

        try {
          const response = await fetch(
            new URL(`/posts/comments/${linkTarget.commentId}`, location),
            { method: 'HEAD' }
          );
          const redirectTarget = response.redirected && parseURL(new URL(response.url));

          if (redirectTarget) {
            // the fallbacks are here because response.url of fetch requests
            // made from Greasemonkey for some reason omit the fragment
            // identifier

            const commentId =
                  redirectTarget.commentId ??
                  linkTarget.commentId;
            const commentPostId =
                  redirectTarget.commentPostId ??
                  redirectTarget.answerId ??
                  redirectTarget.questionId;

            if (isTherePost(commentPostId)) {
              // mediocre path: post is present on the page, and navigating
              // to the magic link should trigger loading full comments

              location.href = `#comment${commentId}_${commentPostId}`;
              return;
            }

            // sad path: post not present on the page at all, we have to follow the link

            console.warn(
              `[FAL] Failed to follow ${u.href}: post #${commentPostId} not found on the page`);
          } else {
            console.warn(
              `[FAL] Failed to resolve ${u.href}: response is `, response);
          }

          location.href = u.href;
        } finally {
          target.style.cursor = '';
        }
      })().catch(e => {
        location.href = u.href;
        console.warn(`[FAL] Failed to resolve ${u.href}: error `, e);
      });
    }
  } else if (linkTarget.answerId != null) {
    // checking question id is probably not necessary, and may
    // even be harmful if an answer is moved between questions

    if (isThereAnswer(linkTarget.answerId))
      return insteadGoTo(`#answer-${linkTarget.answerId}`);
  } else if (linkTarget.questionId != null) {
    // a link to the current question is unusual, but possible; we handle this too
    // however, we make an exception for the link in the header,
    // sometimes clicked in lieu of refreshing the page

    if (target.matches('#question-header :any-link'))
      return;

    if (isThereQuestion(linkTarget.questionId))
      return insteadGoTo('#question');
  } else {
    console.warn(`[FAL] URL ${u} parsed, but not handled:`, linkTarget);
  }
}, false);
// ==UserScript==
// @name     Stack Exchange: Faster Answer Links
// @grant    none
// @run-at   document-start
// @match    https://*.stackexchange.com/*
// @match    https://*.superuser.com/*
// @match    https://*.stackoverflow.com/*
// @match    https://*.mathoverflow.net/*
// @match    https://*.serverfault.com/*
// @match    https://*.askubuntu.com/*
// @match    https://stackapps.com/*
// @exclude  https://chat.stackexchange.com/*
// @exclude  https://api.stackexchange.com/*
// @exclude  https://data.stackexchange.com/*
// @exclude  https://openid.stackexchange.com/*
// @exclude  https://contests.stackoverflow.com/*
// @exclude  /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

// attaching to window because jQuery attaches *its* delegated
// event handlers to document, and we need to run *after* those

window.addEventListener('click', ev => {
  if (ev.defaultPrevented)
    return;

  const target = ev.target.closest(':any-link');
  
  // ignore non-link clicks
  if (!target)
    return;
  
  // ignore target="_blank" links (especially on review pages)
  if (target.target)
    return;
  
  // ignore clicks with modifier keys; we presume
  // those would not result in normal navigation
  if (ev.ctrlKey || ev.altKey || ev.shiftKey || ev.metaKey)
    return;

  // ignore href="#" pseudo-links
  // (.getAttribute because .href returns the resolved absolute URL)
  if (target.getAttribute('href') === '#')
    return;

  const parseURL = u => {
    let m;
    
    // catch both HTTPS and older plaintext-HTTP links
    if (u.host !== location.host || !/^https?:$/.test(u.protocol))
      return null;
    
    // /posts/comments/<comment_id>
    if (m = /^\/posts\/comments\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { commentId: m[1], commentPostId: null };
    }
    
    // #comment<comment_id>_<owner_id>
    if (/^\/(q|a|questions)\//u.test(u.pathname)
        && (m = /^#comment(\d+)_(\d+)$/u.exec(u.hash))) {
      return { commentId: m[1], commentPostId: m[2] };
    }

    // /a/<answer_id>
    // /questions/<question_id>/<question_title>/<answer_id>
    if (m = /^\/(?:a|questions\/(\d+)\/[^\/]+)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { questionId: m[1] ?? null, answerId: m[2] };
    }
    
    // /q/<question_id>
    // /questions/<question_id>/<question_title>
    if (m = /^\/(?:q|questions)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      const questionId = m[1];
      if (m = /^#(?:answer-)?(\d+)$/u.exec(u.hash))
        return { questionId, answerId: m[1] };
      if (!u.hash || u.hash === '#question')
        return { questionId, answerId: null };
    }
    
    return null;
  };
  
  const insteadGoTo = target => {
    ev.preventDefault();
    location.href = target;
  };
  
  const isThereQuestion = postId =>
    !!document.querySelector(`#question[data-questionid="${postId}"]`);
  const isThereAnswer = postId =>
    !!document.getElementById(`answer-${postId}`);
  const isTherePost = postId =>
    !!document.querySelector(`#answer-${postId}, #question[data-questionid="${postId}"]`)
  
  const u = new URL(target.href);  
  const linkTarget = parseURL(u);
  
  if (!linkTarget)
    return;
  
  if (linkTarget.commentId != null) {
    if (linkTarget.commentPostId != null) {
      const { commentPostId, commentId } = linkTarget;     
      if (isTherePost(commentPostId))
        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
    } else {
      let node;

      if (node = document.querySelector(`#comment-${linkTarget.commentId}`)) {
        const commentId = node.dataset.commentId;
        const commentPostId = node.closest('.comments[data-post-id]').dataset.postId;

        // use the magic fragment id instead of the proper element id,
        // because the latter does not work all that well
        // (probably something to do with 'display: contents;')

        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
      }
      
      // if there are no hidden comments on the current page, don't bother,
      // just do a full page reload
      if (!document.querySelector('.js-comments-container .js-show-link:not(.dno)')) {
        return;
      }
      
      // prevent default now; if fetching the target fails,
      // we will just redirect manually later
      ev.preventDefault();
      
      (async () => {
        target.style.cursor = 'progress';

        try {
          const response = await fetch(
            new URL(`/posts/comments/${linkTarget.commentId}`, location),
            { method: 'HEAD' }
          );
          const redirectTarget = response.redirected && parseURL(new URL(response.url));

          if (redirectTarget) {
            // the fallbacks are here because response.url of fetch requests
            // made from Greasemonkey for some reason omit the fragment
            // identifier

            const commentId =
                  redirectTarget.commentId ??
                  linkTarget.commentId;
            const commentPostId =
                  redirectTarget.commentPostId ??
                  redirectTarget.answerId ??
                  redirectTarget.questionId;

            if (isTherePost(commentPostId)) {
              location.href = `#comment${commentId}_${commentPostId}`;
              return;
            }

            console.warn(
              `[FAL] Failed to follow ${u.href}: post #${commentPostId} not found on the page`);
          } else {
            console.warn(
              `[FAL] Failed to resolve ${u.href}: response is `, response);
          }

          location.href = u.href;
        } finally {
          target.style.cursor = '';
        }
      })().catch(e => {
        location.href = u.href;
        console.warn(`[FAL] Failed to resolve ${u.href}: error `, e);
      });
    }
  } else if (linkTarget.answerId != null) {
    // checking question id is probably not necessary, and may
    // even be harmful if an answer is moved between questions

    if (isThereAnswer(linkTarget.answerId))
      return insteadGoTo(`#answer-${linkTarget.answerId}`);
  } else if (linkTarget.questionId != null) {
    // a link to the current question is unusual, but possible; we handle this too
    // however, we make an exception for the link in the header,
    // sometimes clicked in lieu of refreshing the page

    if (target.matches('#question-header :any-link'))
      return;

    if (isThereQuestion(linkTarget.questionId))
      return insteadGoTo('#question');
  } else {
    console.warn(`[FAL] URL ${u} parsed, but not handled:`, linkTarget);
  }
}, false);
// ==UserScript==
// @name     Stack Exchange: Faster Answer Links
// @grant    none
// @run-at   document-start
// @match    https://*.stackexchange.com/*
// @match    https://*.superuser.com/*
// @match    https://*.stackoverflow.com/*
// @match    https://*.mathoverflow.net/*
// @match    https://*.serverfault.com/*
// @match    https://*.askubuntu.com/*
// @match    https://stackapps.com/*
// @exclude  https://chat.stackexchange.com/*
// @exclude  https://api.stackexchange.com/*
// @exclude  https://data.stackexchange.com/*
// @exclude  https://openid.stackexchange.com/*
// @exclude  https://contests.stackoverflow.com/*
// @exclude  /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

// attaching to window because jQuery attaches *its* delegated
// event handlers to document, and we need to run *after* those

window.addEventListener('click', ev => {
  if (ev.defaultPrevented)
    return;

  const target = ev.target.closest(':any-link');
  
  // ignore non-link clicks
  if (!target)
    return;
  
  // ignore target="_blank" links (especially on review pages)
  if (target.target)
    return;
  
  // ignore clicks with modifier keys; we presume
  // those would not result in normal navigation
  if (ev.ctrlKey || ev.altKey || ev.shiftKey || ev.metaKey)
    return;

  // ignore href="#" pseudo-links
  // (.getAttribute because .href returns the resolved absolute URL)
  if (target.getAttribute('href') === '#')
    return;

  const parseURL = u => {
    let m;
    
    // catch both HTTPS and older plaintext-HTTP links
    if (u.host !== location.host || !/^https?:$/.test(u.protocol))
      return null;
    
    // /posts/comments/<comment_id>
    if (m = /^\/posts\/comments\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { commentId: m[1], commentPostId: null };
    }
    
    // #comment<comment_id>_<post_id>
    if (/^\/(q|a|questions)\//u.test(u.pathname)
        && (m = /^#comment(\d+)_(\d+)$/u.exec(u.hash))) {
      return { commentId: m[1], commentPostId: m[2] };
    }

    // /a/<answer_id>
    // /questions/<question_id>/<question_title>/<answer_id>
    if (m = /^\/(?:a|questions\/(\d+)\/[^\/]+)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { questionId: m[1] ?? null, answerId: m[2] };
    }
    
    // /q/<question_id>
    // /questions/<question_id>/<question_title>
    if (m = /^\/(?:q|questions)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      const questionId = m[1];
      if (m = /^#(?:answer-)?(\d+)$/u.exec(u.hash))
        return { questionId, answerId: m[1] };
      if (!u.hash || u.hash === '#question')
        return { questionId, answerId: null };
    }
    
    return null;
  };
  
  const insteadGoTo = target => {
    ev.preventDefault();
    location.href = target;
  };
  
  const isThereQuestion = postId =>
    !!document.querySelector(`#question[data-questionid="${postId}"]`);
  const isThereAnswer = postId =>
    !!document.getElementById(`answer-${postId}`);
  const isTherePost = postId =>
    !!document.querySelector(`#answer-${postId}, #question[data-questionid="${postId}"]`)
  
  const u = new URL(target.href);  
  const linkTarget = parseURL(u);
  
  if (!linkTarget)
    return;
  
  if (linkTarget.commentId != null) {
    if (linkTarget.commentPostId != null) {
      const { commentPostId, commentId } = linkTarget;     
      if (isTherePost(commentPostId))
        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
    } else {
      let node;

      // when rerouting comment links, use the magic fragment id
      // (processed by Stack Exchange JS)  instead of the proper
      // element id (natively supported in the browser)
      // because the latter does not work all that well
      // (probably something to do with 'display: contents;')
      // additionally, a magic fragment id will work for comments
      // that have not been loaded yet

      if (node = document.querySelector(`#comment-${linkTarget.commentId}`)) {
        // happy path: comment already loaded, just navigate to it

        const commentId = node.dataset.commentId;
        const commentPostId = node.closest('.comments[data-post-id]').dataset.postId;

        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
      }
      
      // if there are no hidden comments on the current page, don't bother,
      // just do a full page reload
      if (!document.querySelector('.js-post-comments-component .js-show-link:not(.dno)')) {
        return;
      }
      
      // prevent default now; if fetching the target fails,
      // we will just redirect manually later
      ev.preventDefault();
      
      (async () => {
        target.style.cursor = 'progress';

        try {
          const response = await fetch(
            new URL(`/posts/comments/${linkTarget.commentId}`, location),
            { method: 'HEAD' }
          );
          const redirectTarget = response.redirected && parseURL(new URL(response.url));

          if (redirectTarget) {
            // the fallbacks are here because response.url of fetch requests
            // made from Greasemonkey for some reason omit the fragment
            // identifier

            const commentId =
                  redirectTarget.commentId ??
                  linkTarget.commentId;
            const commentPostId =
                  redirectTarget.commentPostId ??
                  redirectTarget.answerId ??
                  redirectTarget.questionId;

            if (isTherePost(commentPostId)) {
              // mediocre path: post is present on the page, and navigating
              // to the magic link should trigger loading full comments

              location.href = `#comment${commentId}_${commentPostId}`;
              return;
            }

            // sad path: post not present on the page at all, we have to follow the link

            console.warn(
              `[FAL] Failed to follow ${u.href}: post #${commentPostId} not found on the page`);
          } else {
            console.warn(
              `[FAL] Failed to resolve ${u.href}: response is `, response);
          }

          location.href = u.href;
        } finally {
          target.style.cursor = '';
        }
      })().catch(e => {
        location.href = u.href;
        console.warn(`[FAL] Failed to resolve ${u.href}: error `, e);
      });
    }
  } else if (linkTarget.answerId != null) {
    // checking question id is probably not necessary, and may
    // even be harmful if an answer is moved between questions

    if (isThereAnswer(linkTarget.answerId))
      return insteadGoTo(`#answer-${linkTarget.answerId}`);
  } else if (linkTarget.questionId != null) {
    // a link to the current question is unusual, but possible; we handle this too
    // however, we make an exception for the link in the header,
    // sometimes clicked in lieu of refreshing the page

    if (target.matches('#question-header :any-link'))
      return;

    if (isThereQuestion(linkTarget.questionId))
      return insteadGoTo('#question');
  } else {
    console.warn(`[FAL] URL ${u} parsed, but not handled:`, linkTarget);
  }
}, false);
added 1132 characters in body
Source Link
user3840170
  • 385
  • 1
  • 10

A tiny warning (perhaps not significant to most people): when following links to comments in the /posts/comments/CommentId form, the script may incur additional HTTP(S) traffic; this happens when following a link to a comment that has not (yet) been loaded on the current page, to check if it’s attached to a post that is present on the current page. Since this is a HEAD request, which consists only of HTTP headers, I estimate it to be no more than 2 kB per click (excluding TLS overhead). In the worst case, when linking to a comment for a post not present on the page, this may trigger the HEAD request, and then perform a full page reload anyway; in the middling case, when the post is present, full comments for that post will be loaded. To avoid this additional traffic, prefer linking to comments in a form that includes the #commentCommentId_PostId anchor, which includes both the comment ID and the post ID to which the comment is attached.

// ==UserScript==
// @name     Stack Exchange: Faster Answer Links
// @grant    none
// @run-at   document-start
// @match    https://*.stackexchange.com/*
// @match    https://*.superuser.com/*
// @match    https://*.stackoverflow.com/*
// @match    https://*.mathoverflow.net/*
// @match    https://*.serverfault.com/*
// @match    https://*.askubuntu.com/*
// @match    https://stackapps.com/*
// @exclude  https://chat.stackexchange.com/*
// @exclude  https://api.stackexchange.com/*
// @exclude  https://data.stackexchange.com/*
// @exclude  https://openid.stackexchange.com/*
// @exclude  https://contests.stackoverflow.com/*
// @exclude  /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

// attaching to window because jQuery attaches *its* delegated
// event handlers to document, and we need to run *after* those

window.addEventListener('click', ev => {
  if (ev.defaultPrevented)
    return;

  const target = ev.target.closest(':any-link');
  
  // ignore non-link clicks
  if (!target)
    return;
  
  // ignore target="_blank" links (especially on review pages)
  if (target.target)
    return;
  
  // ignore clicks with modifier keys; we presume
  // those would not result in normal navigation
  if (ev.ctrlKey || ev.altKey || ev.shiftKey || ev.metaKey)
    return;

  // ignore href="#" pseudo-links
  // (.getAttribute because .href returns the resolved absolute URL)
  if (target.getAttribute('href') === '#')
    return;

  const parseURL = u => {
    let m;
    
    // catch both HTTPS and older plaintext-HTTP links
    if (u.host !== location.host || !/^https?:$/.test(u.protocol))
      return null;
    
    // /posts/comments/<comment_id>
    if (m = /^\/posts\/comments\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { commentId: m[1], commentPostId: null };
    }
    
    // #comment<comment_id>_<owner_id>
    if (/^\/(q|a|questions)\//u.test(u.pathname)
        && (m = /^#comment(\d+)_(\d+)$/u.exec(u.hash))) {
      return { commentId: m[1], commentPostId: m[2] };
    }

    // /a/<answer_id>
    // /questions/<question_id>/<question_title>/<answer_id>
    if (m = /^\/(?:a|questions\/(\d+)\/[^\/]+)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { questionId: m[1] ?? null, answerId: m[2] };
    }
    
    // /q/<question_id>
    // /questions/<question_id>/<question_title>
    if (m = /^\/(?:q|questions)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      const questionId = m[1];
      if (m = /^#(?:answer-)?(\d+)$/u.exec(u.hash))
        return { questionId, answerId: m[1] };
      if (!u.hash || u.hash === '#question')
        return { questionId, answerId: null };
    }
    
    return null;
  };
  
  const insteadGoTo = target => {
    ev.preventDefault();
    location.href = target;
  };
  
  const isThereQuestion = postId =>
    !!document.querySelector(`#question[data-questionid="${postId}"]`);
  const isThereAnswer = postId =>
    !!document.getElementById(`answer-${postId}`);
  const isTherePost = postId =>
    !!document.querySelector(`#answer-${postId}, #question[data-questionid="${postId}"]`)
  
  const u = new URL(target.href);  
  const linkTarget = parseURL(u);
  
  if (!linkTarget)
    return;
  
  if (linkTarget.commentId != null) {
    // for detouring comment links, we use the magic fragment id handled by Stack
    // Exchange's JavaScript, instead of the actual comment element id, because
    // the latter does not work all that well (probably something to do with
    // 'display: contents;')

    if (linkTarget.commentPostId != null) {
      const { commentPostId, commentId } = linkTarget;     
      if (isTherePost(commentPostId))
        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
    } else {
      let node;

      if (node = document.querySelector(`#comment-${linkTarget.commentId}`)) {
        const commentId = node.dataset.commentId;
        const commentPostId = node.closest('.comments[data-post-id]').dataset.postId;

        // use the magic fragment id instead of the proper element id,
        // because the latter does not work all that well
        // (probably something to do with 'display: contents;')

        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
      }
      
      // if there are no hidden comments on the current page, don't bother,
      // just do a full page reload
      if (!document.querySelector('.js-comments-container .js-show-link:not(.dno)')) {
        return;
      }
      
      // prevent default for now; if fetching the target fails,
      // we will just redirect manually later
      ev.preventDefault();
      
      (async () => {
        target.style.cursor = 'progress';

        try {
          const response = await fetch(
            new URL(`/posts/comments/${linkTarget.commentId}`, location),
            { method: 'HEAD' }
          );
          const redirectTarget = response.redirected && parseURL(new URL(response.url));

          if (redirectTarget) {
            // the fallbacks are here because response.url of fetch requests
            // made from Greasemonkey for some reason omit the fragment
            // identifier

            const commentId =
                  redirectTarget.commentId ??
                  linkTarget.commentId;
            const commentPostId =
                  redirectTarget.commentPostId ??
                  redirectTarget.answerId ??
                  redirectTarget.questionId;

            if (isTherePost(commentPostId)) {
              location.href = `#comment${commentId}_${commentPostId}`;
              return;
            }

            console.warn(
              `[FAL] Failed to follow ${u.href}: post #${commentPostId} not found on the page`);
          } else {
            console.warn(
              `[FAL] Failed to resolve ${u.href}: response is `, response);
          }

          location.href = u.href;
        } finally {
          target.style.cursor = '';
        }
      })().catch(e => {
        location.href = u.href;
        console.warn(`[FAL] Failed to resolve ${u.href}: error `, e);
      });
    }
  } else if (linkTarget.answerId != null) {
    // checking question id is probably not necessary, and may
    // even be harmful if an answer is moved between questions

    if (isThereAnswer(linkTarget.answerId))
      return insteadGoTo(`#answer-${linkTarget.answerId}`);
  } else if (linkTarget.questionId != null) {
    // a link to the current question is unusual, but possible; we handle this too
    // however, we make an exception for the link in the header, for which I have
    // something of a habit ofsometimes clickingclicked in lieu of refreshing the page

    if (target.matches('#question-header :any-link'))
      return;

    if (isThereQuestion(linkTarget.questionId))
      return insteadGoTo('#question');
  } else {
    console.warn(`[FAL] URL ${u} parsed, but not handled:`, linkTarget);
  }
}, false);
// ==UserScript==
// @name     Stack Exchange: Faster Answer Links
// @grant    none
// @run-at   document-start
// @match    https://*.stackexchange.com/*
// @match    https://*.superuser.com/*
// @match    https://*.stackoverflow.com/*
// @match    https://*.mathoverflow.net/*
// @match    https://*.serverfault.com/*
// @match    https://*.askubuntu.com/*
// @match    https://stackapps.com/*
// @exclude  https://chat.stackexchange.com/*
// @exclude  https://api.stackexchange.com/*
// @exclude  https://data.stackexchange.com/*
// @exclude  https://openid.stackexchange.com/*
// @exclude  https://contests.stackoverflow.com/*
// @exclude  /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

// attaching to window because jQuery attaches *its* delegated
// event handlers to document, and we need to run *after* those

window.addEventListener('click', ev => {
  if (ev.defaultPrevented)
    return;

  const target = ev.target.closest(':any-link');
  
  // ignore non-link clicks
  if (!target)
    return;
  
  // ignore target="_blank" links (especially on review pages)
  if (target.target)
    return;
  
  // ignore clicks with modifier keys; we presume
  // those would not result in normal navigation
  if (ev.ctrlKey || ev.altKey || ev.shiftKey || ev.metaKey)
    return;

  // ignore href="#" pseudo-links
  // (.getAttribute because .href returns the resolved absolute URL)
  if (target.getAttribute('href') === '#')
    return;

  const parseURL = u => {
    let m;
    
    // catch both HTTPS and older plaintext-HTTP links
    if (u.host !== location.host || !/^https?:$/.test(u.protocol))
      return null;
    
    // /posts/comments/<comment_id>
    if (m = /^\/posts\/comments\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { commentId: m[1], commentPostId: null };
    }
    
    // #comment<comment_id>_<owner_id>
    if (/^\/(q|a|questions)\//u.test(u.pathname)
        && (m = /^#comment(\d+)_(\d+)$/u.exec(u.hash))) {
      return { commentId: m[1], commentPostId: m[2] };
    }

    // /a/<answer_id>
    // /questions/<question_id>/<question_title>/<answer_id>
    if (m = /^\/(?:a|questions\/(\d+)\/[^\/]+)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { questionId: m[1] ?? null, answerId: m[2] };
    }
    
    // /q/<question_id>
    // /questions/<question_id>/<question_title>
    if (m = /^\/(?:q|questions)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      const questionId = m[1];
      if (m = /^#(?:answer-)?(\d+)$/u.exec(u.hash))
        return { questionId, answerId: m[1] };
      if (!u.hash || u.hash === '#question')
        return { questionId, answerId: null };
    }
    
    return null;
  };
  
  const insteadGoTo = target => {
    ev.preventDefault();
    location.href = target;
  };
  
  const isThereQuestion = postId =>
    !!document.querySelector(`#question[data-questionid="${postId}"]`);
  const isThereAnswer = postId =>
    !!document.getElementById(`answer-${postId}`);
  const isTherePost = postId =>
    !!document.querySelector(`#answer-${postId}, #question[data-questionid="${postId}"]`)
  
  const u = new URL(target.href);  
  const linkTarget = parseURL(u);
  
  if (!linkTarget)
    return;
  
  if (linkTarget.commentId != null) {
    // for detouring comment links, we use the magic fragment id handled by Stack
    // Exchange's JavaScript, instead of the actual comment element id, because
    // the latter does not work all that well (probably something to do with
    // 'display: contents;')

    if (linkTarget.commentPostId != null) {
      const { commentPostId, commentId } = linkTarget;     
      if (isTherePost(commentPostId))
        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
    } else {
      let node;

      if (node = document.querySelector(`#comment-${linkTarget.commentId}`)) {
        const commentId = node.dataset.commentId;
        const commentPostId = node.closest('.comments[data-post-id]').dataset.postId;

        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
      }
      
      // prevent default for now; if fetching the target fails,
      // we will just redirect manually later
      ev.preventDefault();
      
      (async () => {
        target.style.cursor = 'progress';

        try {
          const response = await fetch(
            new URL(`/posts/comments/${linkTarget.commentId}`, location),
            { method: 'HEAD' }
          );
          const redirectTarget = response.redirected && parseURL(new URL(response.url));

          if (redirectTarget) {
            // the fallbacks are here because response.url of fetch requests
            // made from Greasemonkey for some reason omit the fragment
            // identifier

            const commentId =
                  redirectTarget.commentId ??
                  linkTarget.commentId;
            const commentPostId =
                  redirectTarget.commentPostId ??
                  redirectTarget.answerId ??
                  redirectTarget.questionId;

            if (isTherePost(commentPostId)) {
              location.href = `#comment${commentId}_${commentPostId}`;
              return;
            }

            console.warn(
              `[FAL] Failed to follow ${u.href}: post #${commentPostId} not found on the page`);
          } else {
            console.warn(
              `[FAL] Failed to resolve ${u.href}: response is `, response);
          }

          location.href = u.href;
        } finally {
          target.style.cursor = '';
        }
      })().catch(e => {
        location.href = u.href;
        console.warn(`[FAL] Failed to resolve ${u.href}: error `, e);
      });
    }
  } else if (linkTarget.answerId != null) {
    // checking question id is probably not necessary, and may
    // even be harmful if an answer is moved between questions

    if (isThereAnswer(linkTarget.answerId))
      return insteadGoTo(`#answer-${linkTarget.answerId}`);
  } else if (linkTarget.questionId != null) {
    // a link to the current question is unusual, but possible; we handle this too
    // however, we make an exception for the link in the header, for which I have
    // something of a habit of clicking in lieu of refreshing the page

    if (target.matches('#question-header :any-link'))
      return;

    if (isThereQuestion(linkTarget.questionId))
      return insteadGoTo('#question');
  } else {
    console.warn(`[FAL] URL ${u} parsed, but not handled:`, linkTarget);
  }
}, false);

A tiny warning (perhaps not significant to most people): when following links to comments in the /posts/comments/CommentId form, the script may incur additional HTTP(S) traffic; this happens when following a link to a comment that has not (yet) been loaded on the current page, to check if it’s attached to a post that is present on the current page. Since this is a HEAD request, which consists only of HTTP headers, I estimate it to be no more than 2 kB per click (excluding TLS overhead). In the worst case, when linking to a comment for a post not present on the page, this may trigger the HEAD request, and then perform a full page reload anyway; in the middling case, when the post is present, full comments for that post will be loaded. To avoid this additional traffic, prefer linking to comments in a form that includes the #commentCommentId_PostId anchor, which includes both the comment ID and the post ID to which the comment is attached.

// ==UserScript==
// @name     Stack Exchange: Faster Answer Links
// @grant    none
// @run-at   document-start
// @match    https://*.stackexchange.com/*
// @match    https://*.superuser.com/*
// @match    https://*.stackoverflow.com/*
// @match    https://*.mathoverflow.net/*
// @match    https://*.serverfault.com/*
// @match    https://*.askubuntu.com/*
// @match    https://stackapps.com/*
// @exclude  https://chat.stackexchange.com/*
// @exclude  https://api.stackexchange.com/*
// @exclude  https://data.stackexchange.com/*
// @exclude  https://openid.stackexchange.com/*
// @exclude  https://contests.stackoverflow.com/*
// @exclude  /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

// attaching to window because jQuery attaches *its* delegated
// event handlers to document, and we need to run *after* those

window.addEventListener('click', ev => {
  if (ev.defaultPrevented)
    return;

  const target = ev.target.closest(':any-link');
  
  // ignore non-link clicks
  if (!target)
    return;
  
  // ignore target="_blank" links (especially on review pages)
  if (target.target)
    return;
  
  // ignore clicks with modifier keys; we presume
  // those would not result in normal navigation
  if (ev.ctrlKey || ev.altKey || ev.shiftKey || ev.metaKey)
    return;

  // ignore href="#" pseudo-links
  // (.getAttribute because .href returns the resolved absolute URL)
  if (target.getAttribute('href') === '#')
    return;

  const parseURL = u => {
    let m;
    
    // catch both HTTPS and older plaintext-HTTP links
    if (u.host !== location.host || !/^https?:$/.test(u.protocol))
      return null;
    
    // /posts/comments/<comment_id>
    if (m = /^\/posts\/comments\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { commentId: m[1], commentPostId: null };
    }
    
    // #comment<comment_id>_<owner_id>
    if (/^\/(q|a|questions)\//u.test(u.pathname)
        && (m = /^#comment(\d+)_(\d+)$/u.exec(u.hash))) {
      return { commentId: m[1], commentPostId: m[2] };
    }

    // /a/<answer_id>
    // /questions/<question_id>/<question_title>/<answer_id>
    if (m = /^\/(?:a|questions\/(\d+)\/[^\/]+)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { questionId: m[1] ?? null, answerId: m[2] };
    }
    
    // /q/<question_id>
    // /questions/<question_id>/<question_title>
    if (m = /^\/(?:q|questions)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      const questionId = m[1];
      if (m = /^#(?:answer-)?(\d+)$/u.exec(u.hash))
        return { questionId, answerId: m[1] };
      if (!u.hash || u.hash === '#question')
        return { questionId, answerId: null };
    }
    
    return null;
  };
  
  const insteadGoTo = target => {
    ev.preventDefault();
    location.href = target;
  };
  
  const isThereQuestion = postId =>
    !!document.querySelector(`#question[data-questionid="${postId}"]`);
  const isThereAnswer = postId =>
    !!document.getElementById(`answer-${postId}`);
  const isTherePost = postId =>
    !!document.querySelector(`#answer-${postId}, #question[data-questionid="${postId}"]`)
  
  const u = new URL(target.href);  
  const linkTarget = parseURL(u);
  
  if (!linkTarget)
    return;
  
  if (linkTarget.commentId != null) {
    if (linkTarget.commentPostId != null) {
      const { commentPostId, commentId } = linkTarget;     
      if (isTherePost(commentPostId))
        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
    } else {
      let node;

      if (node = document.querySelector(`#comment-${linkTarget.commentId}`)) {
        const commentId = node.dataset.commentId;
        const commentPostId = node.closest('.comments[data-post-id]').dataset.postId;

        // use the magic fragment id instead of the proper element id,
        // because the latter does not work all that well
        // (probably something to do with 'display: contents;')

        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
      }
      
      // if there are no hidden comments on the current page, don't bother,
      // just do a full page reload
      if (!document.querySelector('.js-comments-container .js-show-link:not(.dno)')) {
        return;
      }
      
      // prevent default now; if fetching the target fails,
      // we will just redirect manually later
      ev.preventDefault();
      
      (async () => {
        target.style.cursor = 'progress';

        try {
          const response = await fetch(
            new URL(`/posts/comments/${linkTarget.commentId}`, location),
            { method: 'HEAD' }
          );
          const redirectTarget = response.redirected && parseURL(new URL(response.url));

          if (redirectTarget) {
            // the fallbacks are here because response.url of fetch requests
            // made from Greasemonkey for some reason omit the fragment
            // identifier

            const commentId =
                  redirectTarget.commentId ??
                  linkTarget.commentId;
            const commentPostId =
                  redirectTarget.commentPostId ??
                  redirectTarget.answerId ??
                  redirectTarget.questionId;

            if (isTherePost(commentPostId)) {
              location.href = `#comment${commentId}_${commentPostId}`;
              return;
            }

            console.warn(
              `[FAL] Failed to follow ${u.href}: post #${commentPostId} not found on the page`);
          } else {
            console.warn(
              `[FAL] Failed to resolve ${u.href}: response is `, response);
          }

          location.href = u.href;
        } finally {
          target.style.cursor = '';
        }
      })().catch(e => {
        location.href = u.href;
        console.warn(`[FAL] Failed to resolve ${u.href}: error `, e);
      });
    }
  } else if (linkTarget.answerId != null) {
    // checking question id is probably not necessary, and may
    // even be harmful if an answer is moved between questions

    if (isThereAnswer(linkTarget.answerId))
      return insteadGoTo(`#answer-${linkTarget.answerId}`);
  } else if (linkTarget.questionId != null) {
    // a link to the current question is unusual, but possible; we handle this too
    // however, we make an exception for the link in the header,
    // sometimes clicked in lieu of refreshing the page

    if (target.matches('#question-header :any-link'))
      return;

    if (isThereQuestion(linkTarget.questionId))
      return insteadGoTo('#question');
  } else {
    console.warn(`[FAL] URL ${u} parsed, but not handled:`, linkTarget);
  }
}, false);
added 621 characters in body
Source Link
user3840170
  • 385
  • 1
  • 10
// ==UserScript==
// @name     Stack Exchange: Faster Answer Links
// @grant    none
// @run-at   document-start
// @match    https://*.stackexchange.com/*
// @match    https://*.superuser.com/*
// @match    https://*.stackoverflow.com/*
// @match    https://*.mathoverflow.net/*
// @match    https://*.serverfault.com/*
// @match    https://*.askubuntu.com/*
// @match    https://stackapps.com/*
// @exclude  https://chat.stackexchange.com/*
// @exclude  https://api.stackexchange.com/*
// @exclude  https://data.stackexchange.com/*
// @exclude  https://openid.stackexchange.com/*
// @exclude  https://contests.stackoverflow.com/*
// @exclude  /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

// attaching to window because jQuery attaches *its* delegated
// event handlers to document, and we need to run *after* those

window.addEventListener('click', ev => {
  if (ev.defaultPrevented)
    return;

  const target = ev.target.closest(':any-link');
  
  // ignore non-link clicks
  if (!target)
    return;
  
  // ignore target="_blank" links (especially on review pages)
  if (target.target)
    return;
  
  // ignore clicks with modifier keys; we presume
  // those would not result in normal navigation
  if (ev.ctrlKey || ev.altKey || ev.shiftKey || ev.metaKey)
    return;

  // ignore href="#" pseudo-links
  // (.getAttribute because .href returns the resolved absolute URL)
  if (target.getAttribute('href') === '#')
    return;

  const parseURL = u => {
    let m;
    
    // catch both HTTPS and older plaintext-HTTP links
    if (u.originhost !== location.originhost || !/^https?:$/.test(u.protocol))
      return null;
    
    // /posts/comments/<comment_id>
    if (m = /^\/posts\/comments\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { commentId: m[1], commentPostId: null };
    }
    
    // #comment<comment_id>_<owner_id>
    if (/^\/(q|a|questions)\//u.test(u.pathname)
        && (m = /^#comment(\d+)_(\d+)$/u.exec(u.hash))) {
      return { commentId: m[1], commentPostId: m[2] };
    }

    // /a/<answer_id>
    // /questions/<question_id>/<question_title>/<answer_id>
    if (m = /^\/(?:a|questions\/(\d+)\/[^\/]+)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
            return { questionId: m[1] ?? null, answerId: m[2] };
    }
    
    // /q/<question_id>
    // /questions/<question_id>/<question_title>
    if (m = /^\/(?:q|questions)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      const questionId = m[1];
      if (m = /^#(?:answer-)?(\d+)$/u.exec(u.hash))
        return { questionId, answerId: m[1] };
        if (!u.hash || u.hash === '#question')
        return { questionId, answerId: null };
    }
    
    return null;
  };
  
  const insteadGoTo = target => {
    ev.preventDefault();
    location.href = target;
  };
  
  const isThereQuestion = postId =>
    !!document.querySelector(`#question[data-questionid="${postId}"]`);
  const isThereAnswer = postId =>
    !!document.getElementById(`answer-${postId}`);
    const isTherePost = postId =>
    !!document.querySelector(`#answer-${postId}, #question[data-questionid="${postId}"]`)
  
  const u = new URL(target.href);  
  const linkTarget = parseURL(u);
  
  if (!linkTarget)
    return;
  
  if (linkTarget.commentId != null) {
    // for detouring comment links, we use the magic fragment id handled by Stack
    // Exchange's JavaScript, instead of the actual comment element id, because
    // the latter does not work all that well (probably something to do with
    // 'display: contents;')

    if (linkTarget.commentPostId != null) {
      const { commentPostId, commentId } = linkTarget;     
      if (isTherePost(commentPostId))
        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
    } else {
      let node;

      if (node = document.querySelector(`#comment-${linkTarget.commentId}`)) {
        const commentId = node.dataset.commentId;
        const commentPostId = node.closest('.comments[data-post-id]').dataset.postId;

        // use the magic fragment id instead of the proper element id,
        // because the latter does not work all that well
        // (probably something to do with 'display: contents;')

        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
      }
      
      // prevent default for now; if fetching the target fails,
      // we will just redirect manually later
      ev.preventDefault();
      
      fetch(
        newasync URL(`/posts/comments/${linkTarget.commentId}`, location),
        { method: 'HEAD' }
      ).then(async response => {
        const redirectTarget = response.redirected && parseURL(new URL(responsetarget.url));
        
        if (redirectTarget) {
          // the fallbacks are here because responsestyle.url of fetch requests
          // made from Greasemonkey for some reason omit the fragment
         cursor //= identifier'progress';

          const commentIdtry ={
             const response = redirectTarget.commentIdawait ??fetch(
               new URL(`/posts/comments/${linkTarget.commentId;
          const commentPostIdcommentId}`, =location),
              { method: redirectTarget.commentPostId'HEAD' ??}
                redirectTarget.answerId ??);
          const redirectTarget = response.redirected && parseURL(new redirectTargetURL(response.questionId;url));

          if (redirectTarget) {
            // the fallbacks are here because response.url of fetch requests
            // made from Greasemonkey for some reason omit the fragment
            // identifier

            const commentId =
                  redirectTarget.commentId ??
                  linkTarget.commentId;
            const commentPostId =
                  redirectTarget.commentPostId ??
                  redirectTarget.answerId ??
                  redirectTarget.questionId;

            if (isTherePost(commentPostId)) {
              location.href = `#comment${commentId}_${commentPostId}`;
              return;
            }

            console.warn(
              `[FAL] Failed to follow ${u.href}: post #${commentPostId} not found on the page`);
          } else {
            console.warn(
              `[FAL] Failed to resolve ${u.href}: response is `, response);
          }

            consolelocation.warn(`[FAL] Failed tohref follow= ${u.href}: post #${commentPostId} not found on the page`);href;
        } elsefinally {
            consoletarget.warn(`[FAL] Failed to resolve ${ustyle.href}: response iscursor `,= response);'';
        }

        location.href = u.href;
      })().catch(e => {
        location.href = u.href;
        console.warn(`[FAL] Failed to resolve ${u.href}: error `, e);
      });
    }
  } else if (linkTarget.answerId != null) {
    // checking question id is probably not necessary, and may
    // even be harmful if an answer is moved between questions

    if (isThereAnswer(linkTarget.answerId))
      return insteadGoTo(`#answer-${linkTarget.answerId}`);
  } else if (linkTarget.questionId != null) {
    // a link to the current question is unusual, but possiblepossible; we handle this too
    // nohowever, reasonwe notmake toan handleexception thisfor the link in the header, for which I have
    // something of a habit of clicking in lieu of refreshing the page

    if (target.matches('#question-header :any-link'))
      return;

    if (isThereQuestion(linkTarget.questionId))
      return insteadGoTo('#question');
  } else {
    console.warn(`[FAL] URL ${u} parsed, but not handled:`, linkTarget);
  }
}, false);
// ==UserScript==
// @name     Stack Exchange: Faster Answer Links
// @grant    none
// @run-at   document-start
// @match    https://*.stackexchange.com/*
// @match    https://*.superuser.com/*
// @match    https://*.stackoverflow.com/*
// @match    https://*.mathoverflow.net/*
// @match    https://*.serverfault.com/*
// @match    https://*.askubuntu.com/*
// @match    https://stackapps.com/*
// @exclude  https://chat.stackexchange.com/*
// @exclude  https://api.stackexchange.com/*
// @exclude  https://data.stackexchange.com/*
// @exclude  https://openid.stackexchange.com/*
// @exclude  https://contests.stackoverflow.com/*
// @exclude  /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

// attaching to window because jQuery attaches *its* delegated
// event handlers to document, and we need to run *after* those

window.addEventListener('click', ev => {
  if (ev.defaultPrevented)
    return;

  const target = ev.target.closest(':any-link');
  
  // ignore non-link clicks
  if (!target)
    return;
  
  // ignore target="_blank" links (especially on review pages)
  if (target.target)
    return;
  
  // ignore clicks with modifier keys; we presume
  // those would not result in normal navigation
  if (ev.ctrlKey || ev.altKey || ev.shiftKey || ev.metaKey)
    return;

  // ignore href="#" pseudo-links
  // (.getAttribute because .href returns the resolved absolute URL)
  if (target.getAttribute('href') === '#')
    return;

  const parseURL = u => {
    let m;
    
    if (u.origin !== location.origin)
      return null;
    
    // /posts/comments/<comment_id>
    if (m = /^\/posts\/comments\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { commentId: m[1], commentPostId: null };
    }
    
    // #comment<comment_id>_<owner_id>
    if (/^\/(q|a|questions)\//u.test(u.pathname)
        && (m = /^#comment(\d+)_(\d+)$/u.exec(u.hash))) {
      return { commentId: m[1], commentPostId: m[2] };
    }

    // /a/<answer_id>
    // /questions/<question_id>/<question_title>/<answer_id>
    if (m = /^\/(?:a|questions\/(\d+)\/[^\/]+)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
            return { questionId: m[1] ?? null, answerId: m[2] };
    }
    
    // /q/<question_id>
    // /questions/<question_id>/<question_title>
    if (m = /^\/(?:q|questions)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      const questionId = m[1];
      if (m = /^#(?:answer-)?(\d+)$/u.exec(u.hash))
        return { questionId, answerId: m[1] };
        if (!u.hash || u.hash === '#question')
        return { questionId, answerId: null };
    }
    
    return null;
  };
  
  const insteadGoTo = target => {
    ev.preventDefault();
    location.href = target;
  };
  
  const isThereQuestion = postId =>
    !!document.querySelector(`#question[data-questionid="${postId}"]`);
  const isThereAnswer = postId =>
    !!document.getElementById(`answer-${postId}`);
    const isTherePost = postId =>
    !!document.querySelector(`#answer-${postId}, #question[data-questionid="${postId}"]`)
  
  const u = new URL(target.href);  
  const linkTarget = parseURL(u);
  
  if (!linkTarget)
    return;
  
  if (linkTarget.commentId != null) {
    if (linkTarget.commentPostId != null) {
      const { commentPostId, commentId } = linkTarget;     
      if (isTherePost(commentPostId))
        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
    } else {
      let node;

      if (node = document.querySelector(`#comment-${linkTarget.commentId}`)) {
        const commentId = node.dataset.commentId;
        const commentPostId = node.closest('.comments[data-post-id]').dataset.postId;

        // use the magic fragment id instead of the proper element id,
        // because the latter does not work all that well
        // (probably something to do with 'display: contents;')

        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
      }
      
      // prevent default now; if fetching the target fails,
      // we will just redirect manually later
      ev.preventDefault();
      
      fetch(
        new URL(`/posts/comments/${linkTarget.commentId}`, location),
        { method: 'HEAD' }
      ).then(async response => {
        const redirectTarget = response.redirected && parseURL(new URL(response.url));
        
        if (redirectTarget) {
          // the fallbacks are here because response.url of fetch requests
          // made from Greasemonkey for some reason omit the fragment
          // identifier

          const commentId =
                redirectTarget.commentId ??
                linkTarget.commentId;
          const commentPostId =
                redirectTarget.commentPostId ??
                redirectTarget.answerId ??
                redirectTarget.questionId;

          if (isTherePost(commentPostId)) {
            location.href = `#comment${commentId}_${commentPostId}`;
            return;
          }

            console.warn(`[FAL] Failed to follow ${u.href}: post #${commentPostId} not found on the page`);
        } else {
            console.warn(`[FAL] Failed to resolve ${u.href}: response is `, response);
        }

        location.href = u.href;
      }).catch(e => {
        location.href = u.href;
        console.warn(`[FAL] Failed to resolve ${u.href}: error `, e);
      });
    }
  } else if (linkTarget.answerId != null) {
    // checking question id is probably not necessary, and may
    // even be harmful if an answer is moved between questions

    if (isThereAnswer(linkTarget.answerId))
      return insteadGoTo(`#answer-${linkTarget.answerId}`);
  } else if (linkTarget.questionId != null) {
    // a link to the current question is unusual, but possible
    // no reason not to handle this

    if (isThereQuestion(linkTarget.questionId))
      return insteadGoTo('#question');
  } else {
    console.warn(`[FAL] URL ${u} parsed, but not handled:`, linkTarget);
  }
}, false);
// ==UserScript==
// @name     Stack Exchange: Faster Answer Links
// @grant    none
// @run-at   document-start
// @match    https://*.stackexchange.com/*
// @match    https://*.superuser.com/*
// @match    https://*.stackoverflow.com/*
// @match    https://*.mathoverflow.net/*
// @match    https://*.serverfault.com/*
// @match    https://*.askubuntu.com/*
// @match    https://stackapps.com/*
// @exclude  https://chat.stackexchange.com/*
// @exclude  https://api.stackexchange.com/*
// @exclude  https://data.stackexchange.com/*
// @exclude  https://openid.stackexchange.com/*
// @exclude  https://contests.stackoverflow.com/*
// @exclude  /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

// attaching to window because jQuery attaches *its* delegated
// event handlers to document, and we need to run *after* those

window.addEventListener('click', ev => {
  if (ev.defaultPrevented)
    return;

  const target = ev.target.closest(':any-link');
  
  // ignore non-link clicks
  if (!target)
    return;
  
  // ignore target="_blank" links (especially on review pages)
  if (target.target)
    return;
  
  // ignore clicks with modifier keys; we presume
  // those would not result in normal navigation
  if (ev.ctrlKey || ev.altKey || ev.shiftKey || ev.metaKey)
    return;

  // ignore href="#" pseudo-links
  // (.getAttribute because .href returns the resolved absolute URL)
  if (target.getAttribute('href') === '#')
    return;

  const parseURL = u => {
    let m;
    
    // catch both HTTPS and older plaintext-HTTP links
    if (u.host !== location.host || !/^https?:$/.test(u.protocol))
      return null;
    
    // /posts/comments/<comment_id>
    if (m = /^\/posts\/comments\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { commentId: m[1], commentPostId: null };
    }
    
    // #comment<comment_id>_<owner_id>
    if (/^\/(q|a|questions)\//u.test(u.pathname)
        && (m = /^#comment(\d+)_(\d+)$/u.exec(u.hash))) {
      return { commentId: m[1], commentPostId: m[2] };
    }

    // /a/<answer_id>
    // /questions/<question_id>/<question_title>/<answer_id>
    if (m = /^\/(?:a|questions\/(\d+)\/[^\/]+)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      return { questionId: m[1] ?? null, answerId: m[2] };
    }
    
    // /q/<question_id>
    // /questions/<question_id>/<question_title>
    if (m = /^\/(?:q|questions)\/(\d+)(?=\/|$)/u.exec(u.pathname)) {
      const questionId = m[1];
      if (m = /^#(?:answer-)?(\d+)$/u.exec(u.hash))
        return { questionId, answerId: m[1] };
      if (!u.hash || u.hash === '#question')
        return { questionId, answerId: null };
    }
    
    return null;
  };
  
  const insteadGoTo = target => {
    ev.preventDefault();
    location.href = target;
  };
  
  const isThereQuestion = postId =>
    !!document.querySelector(`#question[data-questionid="${postId}"]`);
  const isThereAnswer = postId =>
    !!document.getElementById(`answer-${postId}`);
  const isTherePost = postId =>
    !!document.querySelector(`#answer-${postId}, #question[data-questionid="${postId}"]`)
  
  const u = new URL(target.href);  
  const linkTarget = parseURL(u);
  
  if (!linkTarget)
    return;
  
  if (linkTarget.commentId != null) {
    // for detouring comment links, we use the magic fragment id handled by Stack
    // Exchange's JavaScript, instead of the actual comment element id, because
    // the latter does not work all that well (probably something to do with
    // 'display: contents;')

    if (linkTarget.commentPostId != null) {
      const { commentPostId, commentId } = linkTarget;     
      if (isTherePost(commentPostId))
        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
    } else {
      let node;

      if (node = document.querySelector(`#comment-${linkTarget.commentId}`)) {
        const commentId = node.dataset.commentId;
        const commentPostId = node.closest('.comments[data-post-id]').dataset.postId;

        return insteadGoTo(`#comment${commentId}_${commentPostId}`);
      }
      
      // prevent default for now; if fetching the target fails,
      // we will just redirect manually later
      ev.preventDefault();
      
      (async () => {
        target.style.cursor = 'progress';

        try {
          const response = await fetch(
            new URL(`/posts/comments/${linkTarget.commentId}`, location),
            { method: 'HEAD' }
          );
          const redirectTarget = response.redirected && parseURL(new URL(response.url));

          if (redirectTarget) {
            // the fallbacks are here because response.url of fetch requests
            // made from Greasemonkey for some reason omit the fragment
            // identifier

            const commentId =
                  redirectTarget.commentId ??
                  linkTarget.commentId;
            const commentPostId =
                  redirectTarget.commentPostId ??
                  redirectTarget.answerId ??
                  redirectTarget.questionId;

            if (isTherePost(commentPostId)) {
              location.href = `#comment${commentId}_${commentPostId}`;
              return;
            }

            console.warn(
              `[FAL] Failed to follow ${u.href}: post #${commentPostId} not found on the page`);
          } else {
            console.warn(
              `[FAL] Failed to resolve ${u.href}: response is `, response);
          }

          location.href = u.href;
        } finally {
          target.style.cursor = '';
        }
      })().catch(e => {
        location.href = u.href;
        console.warn(`[FAL] Failed to resolve ${u.href}: error `, e);
      });
    }
  } else if (linkTarget.answerId != null) {
    // checking question id is probably not necessary, and may
    // even be harmful if an answer is moved between questions

    if (isThereAnswer(linkTarget.answerId))
      return insteadGoTo(`#answer-${linkTarget.answerId}`);
  } else if (linkTarget.questionId != null) {
    // a link to the current question is unusual, but possible; we handle this too
    // however, we make an exception for the link in the header, for which I have
    // something of a habit of clicking in lieu of refreshing the page

    if (target.matches('#question-header :any-link'))
      return;

    if (isThereQuestion(linkTarget.questionId))
      return insteadGoTo('#question');
  } else {
    console.warn(`[FAL] URL ${u} parsed, but not handled:`, linkTarget);
  }
}, false);
added 3822 characters in body
Source Link
user3840170
  • 385
  • 1
  • 10
Loading
additional href="#" pseudo-link guard
Source Link
user3840170
  • 385
  • 1
  • 10
Loading
fix for an elusive bug when clicking href="#" pseudo-links'
Source Link
user3840170
  • 385
  • 1
  • 10
Loading
added 378 characters in body
Source Link
user3840170
  • 385
  • 1
  • 10
Loading
handle comment links too
Source Link
user3840170
  • 385
  • 1
  • 10
Loading
Source Link
user3840170
  • 385
  • 1
  • 10
Loading