Improve table editing
This commit is contained in:
@@ -23,9 +23,30 @@
|
||||
};
|
||||
}
|
||||
|
||||
// Enter/Tab cell navigation must yield to an open autocomplete popup so those
|
||||
// keys still accept a suggestion (e.g. a [[wiki-link]] typed inside a cell).
|
||||
// The table keymap sits at Prec.highest, so without this guard it would
|
||||
// swallow the key before the completion keymap ever sees it. Detect the popup
|
||||
// via its tooltip element rather than exporting completionStatus from the
|
||||
// vendored bundle (which would force a bundle rebuild).
|
||||
function completionOpen() {
|
||||
return !!document.querySelector('.cm-tooltip-autocomplete');
|
||||
}
|
||||
|
||||
function tableNavKey(fn) {
|
||||
var handler = tableKey(fn);
|
||||
return function (view) {
|
||||
if (completionOpen()) return false;
|
||||
return handler(view);
|
||||
};
|
||||
}
|
||||
|
||||
var tableKeymap = [
|
||||
{ key: 'Shift-Enter', run: tableKey(T.insertRowBelow) },
|
||||
{ key: 'Shift-Delete', run: tableKey(T.deleteRow) },
|
||||
{ key: 'Enter', run: tableNavKey(T.nextRowSameColumn) },
|
||||
{ key: 'Tab', run: tableNavKey(T.nextCell) },
|
||||
{ key: 'Shift-Tab', run: tableNavKey(T.prevCell) },
|
||||
];
|
||||
|
||||
var state = CM.EditorState.create({
|
||||
|
||||
@@ -262,8 +262,94 @@ window.EditorTables = (function () {
|
||||
return formatTableText(newLines.join('\n'), Math.min(newCursor, newLines.join('\n').length));
|
||||
}
|
||||
|
||||
// Absolute cursor offset at the start of the `col`-th cell's content on
|
||||
// `lineIdx` of a *formatted* table (i.e. just past the "| " / " | " that
|
||||
// opens the cell). Clamps to the last cell if col overflows the row.
|
||||
function cellCursor(text, lineIdx, col) {
|
||||
var lines = text.split('\n');
|
||||
var offset = 0;
|
||||
for (var i = 0; i < lineIdx; i++) offset += lines[i].length + 1;
|
||||
var line = lines[lineIdx] || '';
|
||||
var pipes = [];
|
||||
for (var c = 0; c < line.length; c++) if (line.charAt(c) === '|') pipes.push(c);
|
||||
if (pipes.length === 0) return Math.min(offset, text.length);
|
||||
var p = pipes[Math.min(col, pipes.length - 1)];
|
||||
return Math.min(offset + p + 2, text.length);
|
||||
}
|
||||
|
||||
// Shared engine for the Enter / Tab / Shift-Tab cell navigation. Reformats
|
||||
// the table and drops the cursor into the target cell, appending an empty
|
||||
// row when navigation runs past the last row. Returns null when the cursor
|
||||
// is not in a table (so the caller can fall back to the editor default).
|
||||
function moveInTable(text, cursorPos, mode) {
|
||||
var range = findTableRange(text, cursorPos);
|
||||
if (!range) return null;
|
||||
var colIdx = getCursorColumn(text, cursorPos);
|
||||
if (colIdx === null) return null;
|
||||
|
||||
var sepRel = -1, colCount = 0;
|
||||
for (var i = range.start; i <= range.end; i++) {
|
||||
var cells = parseTableRow(range.lines[i]);
|
||||
if (cells.length > colCount) colCount = cells.length;
|
||||
if (sepRel === -1 && isSeparatorRow(cells)) sepRel = i - range.start;
|
||||
}
|
||||
if (sepRel === -1) return null;
|
||||
|
||||
var lastRel = range.end - range.start;
|
||||
var rowRel = range.cursorLine - range.start;
|
||||
var targetRow = rowRel, targetCol = colIdx;
|
||||
|
||||
if (mode === 'enter') {
|
||||
targetRow = rowRel + 1;
|
||||
if (targetRow === sepRel) targetRow++;
|
||||
} else if (mode === 'tab') {
|
||||
targetCol = colIdx + 1;
|
||||
if (targetCol >= colCount) {
|
||||
targetCol = 0;
|
||||
targetRow = rowRel + 1;
|
||||
if (targetRow === sepRel) targetRow++;
|
||||
}
|
||||
} else if (mode === 'shifttab') {
|
||||
targetCol = colIdx - 1;
|
||||
if (targetCol < 0) {
|
||||
targetCol = colCount - 1;
|
||||
targetRow = rowRel - 1;
|
||||
if (targetRow === sepRel) targetRow--;
|
||||
}
|
||||
if (targetRow < 0) return null;
|
||||
}
|
||||
|
||||
var lines = range.lines.slice();
|
||||
if (targetRow > lastRel) {
|
||||
var emptyCells = [];
|
||||
for (var c = 0; c < colCount; c++) emptyCells.push('');
|
||||
var emptyLine = '| ' + emptyCells.join(' | ') + ' |';
|
||||
while (lastRel < targetRow) {
|
||||
lines.splice(range.start + lastRel + 1, 0, emptyLine);
|
||||
lastRel++;
|
||||
}
|
||||
}
|
||||
|
||||
var tableStartOffset = 0;
|
||||
for (var i = 0; i < range.start; i++) tableStartOffset += lines[i].length + 1;
|
||||
var formatted = formatTableText(lines.join('\n'), tableStartOffset);
|
||||
if (!formatted) return null;
|
||||
|
||||
return {
|
||||
text: formatted.text,
|
||||
cursor: cellCursor(formatted.text, range.start + targetRow, targetCol),
|
||||
};
|
||||
}
|
||||
|
||||
function nextRowSameColumn(text, cursorPos) { return moveInTable(text, cursorPos, 'enter'); }
|
||||
function nextCell(text, cursorPos) { return moveInTable(text, cursorPos, 'tab'); }
|
||||
function prevCell(text, cursorPos) { return moveInTable(text, cursorPos, 'shifttab'); }
|
||||
|
||||
return {
|
||||
formatTableText: formatTableText,
|
||||
nextRowSameColumn: nextRowSameColumn,
|
||||
nextCell: nextCell,
|
||||
prevCell: prevCell,
|
||||
setColumnAlignment: setColumnAlignment,
|
||||
insertColumn: insertColumn,
|
||||
deleteColumn: deleteColumn,
|
||||
|
||||
Reference in New Issue
Block a user