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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
import './table-input.css';
import React from 'dom-chef';
import {TableIcon} from '@primer/octicons-react';
import * as pageDetect from 'github-url-detection';
import {insertTextIntoField} from 'text-field-edit';
import delegate, {DelegateEvent} from 'delegate-it';
import {elementExists} from 'select-dom';
import features from '../feature-manager.js';
import smartBlockWrap from '../helpers/smart-block-wrap.js';
import observe from '../helpers/selector-observer.js';
import {isHasSelectorSupported} from '../helpers/select-has.js';
function addTable({delegateTarget: square}: DelegateEvent<MouseEvent, HTMLButtonElement>): void {
/* There's only one rich-text editor even when multiple fields are visible; the class targets it #5303 */
const field = square.form!.querySelector('textarea.js-comment-field')!;
const cursorPosition = field.selectionStart;
const columns = Number(square.dataset.x);
const rows = Number(square.dataset.y);
const row = columns === 1
// One HTML line per row
? '<tr><td>\n'
// <tr> on its own line
// "1 space" indents without causing unwanted Markdown code blocks that 4 spaces would cause
: '<tr>\n' + ' <td>\n'.repeat(columns);
field.focus();
const table = '<table>\n' + row.repeat(rows) + '</table>';
insertTextIntoField(field, smartBlockWrap(table, field));
// Move caret to first cell
field.selectionEnd = field.value.indexOf('<td>', cursorPosition) + '<td>'.length;
}
function highlightSquares({delegateTarget: hover}: DelegateEvent<MouseEvent, HTMLElement>): void {
for (const cell of hover.parentElement!.children as HTMLCollectionOf<HTMLButtonElement>) {
cell.classList.toggle('selected', cell.dataset.x! <= hover.dataset.x! && cell.dataset.y! <= hover.dataset.y!);
}
}
function add(anchor: HTMLElement): void {
const wrapperClasses = [
'details-reset',
'details-overlay',
'flex-auto',
'select-menu',
'select-menu-modal-right',
'hx_rsm',
];
if (elementExists('md-ref', anchor)) {
wrapperClasses.push(
'toolbar-item',
'btn-octicon',
'mx-1',
);
}
const buttonClasses
= elementExists('md-ref', anchor)
? [
'text-center',
'menu-target',
'p-2',
'p-md-1',
'hx_rsm-trigger',
]
: [
'Button',
'Button--iconOnly',
'Button--invisible',
'Button--medium',
];
anchor.after(
<details className={wrapperClasses.join(' ')}>
<summary
className={buttonClasses.join(' ')}
role="button"
aria-label="Add a table"
aria-haspopup="menu"
>
<div
className="tooltipped tooltipped-sw"
aria-label="Add a table"
>
<TableIcon/>
</div>
</summary>
<details-menu className="select-menu-modal position-absolute left-0 hx_rsm-modal rgh-table-input" role="menu">
{Array.from({length: 25}).map((_, index) => (
<button
type="button"
role="menuitem"
className="rgh-tic btn-link"
data-x={(index % 5) + 1}
data-y={Math.floor(index / 5) + 1}
>
<div/>
</button>
))}
</details-menu>
</details>,
);
}
function init(signal: AbortSignal): void {
observe([
'md-ref', // TODO: Drop in June 2024, cleanup button JSX above too
'.ActionBar-item:has([data-md-button=\'ref\'])',
], add, {signal});
delegate('.rgh-tic', 'click', addTable, {signal});
if (!isHasSelectorSupported()) {
delegate('.rgh-tic', 'mouseenter', highlightSquares, {capture: true, signal});
}
}
void features.add(import.meta.url, {
include: [
pageDetect.hasRichTextEditor,
],
init,
});
/*
Test URLs:
- Any issue or PR
*/
|