addons/isl/src/ComparisonView/SplitDiffView/copyFromSelectedColumn.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import {useState} from 'react';
b69ab319
b69ab3110import './copyFromSelectedColumn.css';
b69ab3111
b69ab3112function copyFromSelectedColumn(e: React.ClipboardEvent<HTMLTableElement>) {
b69ab3113 const clipboard = e.clipboardData;
b69ab3114 const text = getSelectedColumnText();
b69ab3115 if (!text) {
b69ab3116 return;
b69ab3117 }
b69ab3118 e.preventDefault();
b69ab3119 clipboard.setData('text/plain', text);
b69ab3120}
b69ab3121
b69ab3122function getSelectedColumnText(numColumns = 4): string | undefined {
b69ab3123 const sel = window.getSelection();
b69ab3124 if (sel == null) {
b69ab3125 return;
b69ab3126 }
b69ab3127 const range = sel.getRangeAt(0);
b69ab3128 const doc = range.cloneContents();
b69ab3129 const nodes = doc.querySelectorAll('tr');
b69ab3130
b69ab3131 if (nodes.length === 0) {
b69ab3132 return doc.textContent ?? '';
b69ab3133 }
b69ab3134
b69ab3135 let text = '';
b69ab3136
b69ab3137 // We know how many columns are in the table, so each tr should contain ${numColumns} tds.
b69ab3138 // But the selection may not be aligned at the start or end:
b69ab3139 // start drag here
b69ab3140 // v
b69ab3141 // CC DDD
b69ab3142 // AAA BBB CCC DDD
b69ab3143 // AAA BBB CCC DDD
b69ab3144 // AAA B
b69ab3145 // ^
b69ab3146 // end drag here
b69ab3147
b69ab3148 // We can compute what side we started dragging from by comparing
b69ab3149 // the first row's length to the expected length of 4:
b69ab3150 const idx = numColumns - nodes[0].children.length;
b69ab3151
b69ab3152 nodes.forEach((tr, i) => {
b69ab3153 // The first row will be missing columns before the dragged point,
b69ab3154 // so idx will always be 0 for that column.
b69ab3155 // The last row also has the wrong number of columns, but idx is still correct
b69ab3156 // as long as it's within bounds.
b69ab3157 const newIdx = i === 0 ? 0 : idx;
b69ab3158 if (newIdx >= 0) {
b69ab3159 const td = tr.cells[newIdx];
b69ab3160 if (td) {
b69ab3161 text += (i > 0 ? '\n' : '') + td.textContent;
b69ab3162 }
b69ab3163 }
b69ab3164 });
b69ab3165
b69ab3166 return text;
b69ab3167}
b69ab3168
b69ab3169/**
b69ab3170 * When using a <table> to render columns of text, add support
b69ab3171 * for selecting ( & copying) within any one column.
b69ab3172 * Returns props to be forwarded to the table.
b69ab3173 *
b69ab3174 * Note: you need to add data-column={3} prop (replace 3 with appropriate column index) to your <td>s to get the
b69ab3175 * selection to only appear in one column.
b69ab3176 */
b69ab3177export function useTableColumnSelection(): React.TableHTMLAttributes<HTMLTableElement> {
b69ab3178 const [selectedColumn, setSelectedColumn] = useState(0);
b69ab3179
b69ab3180 return {
b69ab3181 onCopy: copyFromSelectedColumn,
b69ab3182 className: `single-column-selection-table selected-column-${selectedColumn}`,
b69ab3183 onMouseDown: e => {
b69ab3184 const td = findParent('TD', e.target as HTMLElement) as HTMLTableCellElement;
b69ab3185 if (td) {
b69ab3186 const col = td.cellIndex;
b69ab3187 setSelectedColumn(col);
b69ab3188 }
b69ab3189 },
b69ab3190 };
b69ab3191}
b69ab3192
b69ab3193function findParent(type: string, start: HTMLElement): HTMLElement {
b69ab3194 let el = start;
b69ab3195 while (el.tagName !== type) {
b69ab3196 if (el.parentElement == null) {
b69ab3197 return el;
b69ab3198 }
b69ab3199 el = el.parentElement;
b69ab31100 }
b69ab31101 return el;
b69ab31102}