utterance / utterances

:crystal_ball: A lightweight comments widget built on GitHub issues
https://utteranc.es
MIT License
9.01k stars 574 forks source link

Scroll position after GitHub authentication redirect #140

Open benhoneywill opened 5 years ago

benhoneywill commented 5 years ago

Hi! Thanks for you work on Utterances, from playing around it seems really great. Using GitHub Issues as comments is a really interesting idea and this is my favourite of all the open source implementations I've found!

What's my issue?

(Apologies if there is already a solution for this that I have missed)

When a user is redirected to a blog post after authentication with GitHub they are obviously taken back to the top of the page. This is not a great user experience if they have just read a long article and have to scroll all the way back to the bottom of the page to leave their comment.

What's a solution?

The solution that seems like the most obvious choice to me would be for Utterances to redirect back to your website with a query string parameter in the url indicating that the user is being redirected back to your site after authentication (perhaps utterances-redirect=true). You could then use this to scroll down to your comments if the param is present (may have other use cases too?) There may be better solutions though 😅


What do you think? If this is something that other people are in to as well then I am happy to help implement with a prod in the right direction.

benhoneywill commented 5 years ago

So I've come up with a work around for this which is working for me for now. It's a bit of a hack but it does the trick. What I've done is momentarily set the query param using history.replaceState while the utterances script initialises, and then remove it once the script has loaded. Thought I'd leave the code for my React component here in case it helps anyone else who finds this issue.

import React, { useEffect, useRef } from "react"

const Comments = ({ title }) => {
  let container = useRef()

  useEffect(() => {
    if (!container.current) return

    const config = {
      src: "https://utteranc.es/client.js",
      repo: "username/repo",
      async: true,
      "issue-term": title,
      crossorigin: "anonymous",
    }

    const script = document.createElement("script")

    Object.keys(config).forEach(key => {
      script.setAttribute(key, config[key])
    })

    const params = new URLSearchParams(window.location.search)

    // If the 'commenting' URL param is present then scroll to the comments
    if (params.has("commenting")) {
      const commentsScroll = container.current.getBoundingClientRect().top
      const scroll = commentsScroll + window.innerHeight
      window.scroll(0, scroll)
    }

    // Briefly set the 'commenting' URL param while the utterances script is created
    // This ensures the script is initialized with the correct redirect_uri for GitHub OAuth
    params.set("commenting", true)
    const redirectUrl = `${window.location.pathname}?${params.toString()}`
    window.history.replaceState(null, "", redirectUrl)

    // Append the Utterances script to the container
    container.current.appendChild(script)

    // Once the script has loaded remove the 'commenting' URL param again
    script.onload = () => {
      params.delete("commenting")
      const search = params.toString()
      const originalUrl = window.location.pathname + (search && `?${search}`)
      window.history.replaceState(null, "", originalUrl)
    }
  }, [container.current, title])

  return (
    <div ref={container}></div>
  )
}

export default Comments

Being able to get the url param straight from the GitHub redirect would still be better but this is working fine 👍

alexandru commented 4 years ago

The workaround by @benhoneywill no longer works, because the redirect_uri is set to the Page's URL without any query parameters, as specified in deparam.ts.

Which is another problem IMO, because the URL's query parameters can dictate the page to render.

bzaar commented 2 years ago

Has anyone found a solution? It's a real bummer, this!

nmonastyrskyi commented 1 week ago

As a simple workaround, you can save to the local storage the current position of the scroll before the page is unloaded, and retrieve the scroll position on DOMContentLoaded.

It adds a small but rather positive side effect - after the page refresh user will be scrolled to the position he used to be before the page refresh.

Simple realization for one-page site would look like this:

document.addEventListener('DOMContentLoaded', function () {
    const scrollpos = localStorage.getItem('scrollpos');
    if (scrollpos) {
        window.scrollTo(0, scrollpos);
    }
});

window.onbeforeunload = function () {
    localStorage.setItem('scrollpos', window.scrollY);
};

If your website has multiple pages, you can extend it like this:

document.addEventListener('DOMContentLoaded', function () {
    const scrollpos = localStorage.getItem('scrollpos');
    if (scrollpos) {
        const {url, position} = JSON.parse(scrollpos);
        if (url === window.location.href) {
            window.scrollTo(0, position);
        }
    }
});

window.onbeforeunload = function () {
    localStorage.setItem('scrollpos', JSON.stringify({url: window.location.href, position: window.scrollY}));
};

Then just add this script to your HTML pages and voila :)