summaryrefslogtreecommitdiff
path: root/source/features/comment-fields-keyboard-shortcuts.tsx
blob: e81ff0bebd32ac9b1ebf55eb6b318e68e08a27c8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import React from 'dom-chef';
import {$, $$} from 'select-dom';
import {DelegateEvent} from 'delegate-it';
import * as pageDetect from 'github-url-detection';
import filterAlteredClicks from 'filter-altered-clicks';

import features from '../feature-manager.js';
import {onCommentFieldKeydown} from '../github-events/on-field-keydown.js';

function handleEscapeKey(event: DelegateEvent<KeyboardEvent, HTMLTextAreaElement>, targetField: HTMLTextAreaElement): void {
	// Cancel buttons have different classes for inline comments and editable comments
	const cancelButton = $(`
		button.js-hide-inline-comment-form,
		button.js-comment-cancel-button
	`, targetField.form!);

	// Cancel if there is a button, else blur the field
	if (cancelButton) {
		cancelButton.click();
	} else {
		targetField.blur();
	}

	event.stopImmediatePropagation();
	event.preventDefault();
}

function handleArrowUpKey(targetField: HTMLTextAreaElement): void {
	const currentConversationContainer = targetField.closest([
		'.js-inline-comments-container', // Current review thread container
		'#discussion_bucket', // Or just ALL the comments in issues
		'#all_commit_comments', // Single commit comments at the bottom
	])!;

	const lastOwnComment
		= $$('.js-comment.current-user', currentConversationContainer)
			.reverse()
			.find(comment => {
				const collapsible = comment.closest('details');
				return !collapsible || collapsible.open;
			});

	if (!lastOwnComment) {
		return;
	}

	// Make the comment editable (the native edit button might not be available yet)
	const editButton = <button hidden type="button" className="js-comment-edit-button"/>;
	lastOwnComment.append(editButton);
	editButton.click();
	editButton.remove();
	targetField
		.closest('form')!
		.querySelector('button.js-hide-inline-comment-form')
		?.click();

	// Move caret to end of the field
	requestAnimationFrame(() => {
		$('textarea.js-comment-field', lastOwnComment)!.selectionStart = Number.MAX_SAFE_INTEGER;
	});
}

const eventHandler = filterAlteredClicks((event: DelegateEvent<KeyboardEvent, HTMLTextAreaElement>): void => {
	const field = event.delegateTarget;

	if (event.key === 'Escape') {
		handleEscapeKey(event, field);
	} else if (event.key === 'ArrowUp' && field.value === '') {
		handleArrowUpKey(field);
	}
});

function init(signal: AbortSignal): void {
	onCommentFieldKeydown(eventHandler, signal);
}

void features.add(import.meta.url, {
	shortcuts: {
		'↑': 'Edit your last comment',
		esc: 'Unfocuses comment field',
	},
	include: [
		pageDetect.hasRichTextEditor,
	],
	init,
});