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

Navigating Issues/PRs with keyboard shortcuts #1270

Closed
jamiebuilds opened this issue Apr 20, 2018 · 14 comments · Fixed by #3242
Closed

Navigating Issues/PRs with keyboard shortcuts #1270

jamiebuilds opened this issue Apr 20, 2018 · 14 comments · Fixed by #3242

Comments

@jamiebuilds
Copy link
Contributor

jamiebuilds commented Apr 20, 2018

  • Up/down comments - j/k (not up and down arrows for a11y reasons)
  • New comment - n
  • When comment focused:
    • Resolve comment - r
    • Delete comment - d
    • Edit comment - e
    • Reactions?
      • 👍 + (=)
      • 👎 -
@fregante
Copy link
Member

It doesn't look like any of those are taken already, which is a great start.

We can probably reuse the yellow :target style to navigate up and down.

I think this would work this way:

  1. If any comment is :target-ed and in view, start from there
  2. If there are no :target elements in view, start from topmost comment in viewport
  3. Use location.replace() to change/set the comment URL hash, like #issue-316090404, to move the selection

@sindresorhus
Copy link
Member

Accepted. PR welcome.


For anyone that wants this, please also send this feature request to GitHub: support@github.com

@yakov116
Copy link
Member

yakov116 commented Jun 15, 2020

I had fun trying this out.

The j k was beyond me, but I think the code from cycle-lists-with-keyboard-shortcuts should be able to help ou.

import select from 'select-dom';
import * as pageDetect from 'github-url-detection';

import features from '.';

const shortcutKeys = new Set(['e', 'd', 'h']);
const shortcutClass: Record<string, string> = {
	e: '.rgh-edit-comment, [aria-label="Edit comment"]',
	d: '[aria-label="Delete comment"]',
	h: '[aria-label="Hide comment"]'
};

function triggerShortcut(shortcut: string) {
	const selectedComment = select(location.hash)!;
	select<HTMLButtonElement>(shortcutClass[shortcut], selectedComment)?.click();
}

function init(): void {
	document.addEventListener('keypress', event => {
		if (!shortcutKeys.has(event.key) || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLInputElement) {
			return;
		}
		event.preventDefault();
		if (location.hash) {
			triggerShortcut(event.key);
		}
	});
}

void features.add({
	id: __filebasename,
	description: '',
	screenshot: false
}, {
	include: [
		pageDetect.hasComments
	],
	repeatOnAjax: false,
	init
});

@notlmn
Copy link
Contributor

notlmn commented Jun 15, 2020

but I think the code from cycle-lists-with-keyboard-shortcuts should be able to help ou.

Why do we need cycling in this. The major problem I could think of is figuring out where to start, and "what" can be considered a comment and focused (eg: load more comments button, review comments, etc).

@yakov116
Copy link
Member

yakov116 commented Jun 15, 2020

Any thing that has a div.timeline-comment-header a.js-timestamp

Why do we need cycling in this. The major problem I could think of is figuring out where to start, and "what" can be considered a comment and focused (eg: load more comments button, review comments, etc).

The cycle code does exactly that

@notlmn
Copy link
Contributor

notlmn commented Jun 15, 2020

The cycle code does exactly that

That feature is specifically for "popover lists". We probably do not need cycling over comments.

@yakov116
Copy link
Member

yakov116 commented Jun 15, 2020

Up/down comments - j/k (not up and down arrows for a11y reasons)

We need to find the index of the one we are on and go back and forth.

Where do we start? Get the position of all the elements .getBoundingClientRect() and find the one closest to the top of the screen.

		if (['j', 'k'].includes(event.key)) {
			if (!location.hash) {
				const closestComment = select.all<HTMLAnchorElement>('div.timeline-comment-header a.js-timestamp')
					.find(element => element.getBoundingClientRect().top > 0);

				if (!closestComment) {
					return;
				}

				location.hash = closestComment.hash;
			}
		}

@notlmn
Copy link
Contributor

notlmn commented Jun 15, 2020

				location.hash = closestComment.hash;

location.hash pushes into history API, we should avoid that behavior if possible.


We need to find the index of the one we are on and go back and forth.

select.all gives you an array, we can do highlighting by adding a class (.active), find active comment in array, and changing to other comments would just be a matter of figuring out direction to move in (using el.scrollIntoView()).

One disadvantage (I hope) would be querying the DOM on each keypress (for only j and k of course).

@yakov116
Copy link
Member

But that is beyond my scope of javascript.

@fregante
Copy link
Member

fregante commented Jun 15, 2020

Any thing that has a div.timeline-comment-header a.js-timestamp

Could work; just ensure those items have :target style.

  • If there are no :target elements in view, start from topmost comment in viewport

We can skip this (for now?) and the rest is easier: just start from current :target if any, or 0.

This way, the navigation part is essentially:

const items = select.all('navigable items')

// Find current
const currentIndex = items.indexOf(select(':target'));
const verifiedCurrentIndex = currentIndex < 0 ? 0 : currentIndex; // :target might not be navigable, therefore `-1`

// Find chosen
const chosen = items[verifiedCurrentIndex + direction]; // TODO: clamp it so -1 means "the last item"

// Focus comment
history.replace({}, document.title, chosen.hash);
chosen.scrollIntoView();

@fregante
Copy link
Member

we can do highlighting by adding a class (.active

No need. GitHub already has style for :target. If it doesn't, then we likely shouldn't "focus" it.

@yakov116
Copy link
Member

Any thing that has a div.timeline-comment-header a.js-timestamp

Could work; just ensure those items have :target style.

  • If there are no :target elements in view, start from topmost comment in viewport

We can skip this (for now?) and the rest is easier: just start from current :target if any, or 0.

This way, the navigation part is essentially:

const items = select.all('navigable items')

// Find current
const currentIndex = items.indexOf(select(':target'));
const verifiedCurrentIndex = currentIndex < 0 ? 0 : currentIndex; // :target might not be navigable, therefore `-1`

// Find chosen
const chosen = items[verifiedCurrentIndex + direction]; // TODO: clamp it so -1 means "the last item"

// Focus comment
history.replace({}, document.title, chosen.hash);
chosen.scrollIntoView();

Thanks I figured it out!!!

@yakov116
Copy link
Member

@fregante If you give the ok I will send a pr. (If not its ok I had loads of fun doing it)

https://github.com/yakov116/refined-github/tree/comment-shortcuts

@fregante
Copy link
Member

Looks reasonable. Let's see a PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

5 participants