Improve markdown editor
This commit is contained in:
274
assets/editor/tables.js
Normal file
274
assets/editor/tables.js
Normal file
@@ -0,0 +1,274 @@
|
||||
window.EditorTables = (function () {
|
||||
|
||||
function repeat(ch, n) {
|
||||
var s = '';
|
||||
for (var i = 0; i < n; i++) s += ch;
|
||||
return s;
|
||||
}
|
||||
|
||||
function padLeft(s, w) {
|
||||
while (s.length < w) s = ' ' + s;
|
||||
return s;
|
||||
}
|
||||
|
||||
function padRight(s, w) {
|
||||
while (s.length < w) s += ' ';
|
||||
return s;
|
||||
}
|
||||
|
||||
function padCenter(s, w) {
|
||||
while (s.length < w) {
|
||||
s = s + ' ';
|
||||
if (s.length < w) s = ' ' + s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function parseTableRow(line) {
|
||||
var trimmed = line.trim();
|
||||
if (trimmed.charAt(0) === '|') trimmed = trimmed.slice(1);
|
||||
if (trimmed.charAt(trimmed.length - 1) === '|') trimmed = trimmed.slice(0, -1);
|
||||
return trimmed.split('|').map(function (c) { return c.trim(); });
|
||||
}
|
||||
|
||||
function isSeparatorRow(cells) {
|
||||
return cells.every(function (c) { return /^:?-+:?$/.test(c); });
|
||||
}
|
||||
|
||||
function parseAlignment(cell) {
|
||||
var left = cell.charAt(0) === ':';
|
||||
var right = cell.charAt(cell.length - 1) === ':';
|
||||
if (left && right) return 'center';
|
||||
if (left) return 'left';
|
||||
if (right) return 'right';
|
||||
return null;
|
||||
}
|
||||
|
||||
function makeSepCell(width, align) {
|
||||
if (align === 'left') return ':' + repeat('-', width - 1);
|
||||
if (align === 'center') return ':' + repeat('-', width - 2) + ':';
|
||||
if (align === 'right') return repeat('-', width - 1) + ':';
|
||||
return repeat('-', width);
|
||||
}
|
||||
|
||||
function findTableRange(text, cursorPos) {
|
||||
var lines = text.split('\n');
|
||||
var charCount = 0;
|
||||
var cursorLine = 0;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (charCount + lines[i].length >= cursorPos && charCount <= cursorPos) {
|
||||
cursorLine = i;
|
||||
}
|
||||
charCount += lines[i].length + 1;
|
||||
}
|
||||
if (!/^\|.*\|$/.test(lines[cursorLine].trim())) return null;
|
||||
var start = cursorLine;
|
||||
while (start > 0 && /^\|.*\|$/.test(lines[start - 1].trim())) start--;
|
||||
var end = cursorLine;
|
||||
while (end < lines.length - 1 && /^\|.*\|$/.test(lines[end + 1].trim())) end++;
|
||||
if (end - start < 1) return null;
|
||||
return { start: start, end: end, lines: lines, cursorLine: cursorLine };
|
||||
}
|
||||
|
||||
function formatTableText(text, cursorPos) {
|
||||
var range = findTableRange(text, cursorPos);
|
||||
if (!range) return null;
|
||||
|
||||
var rows = [];
|
||||
var sepIndex = -1;
|
||||
var alignments = [];
|
||||
for (var i = range.start; i <= range.end; i++) {
|
||||
var cells = parseTableRow(range.lines[i]);
|
||||
if (sepIndex === -1 && isSeparatorRow(cells)) {
|
||||
sepIndex = rows.length;
|
||||
alignments = cells.map(function (c) { return parseAlignment(c); });
|
||||
}
|
||||
rows.push(cells);
|
||||
}
|
||||
if (sepIndex === -1) return null;
|
||||
|
||||
var colCount = 0;
|
||||
rows.forEach(function (r) { if (r.length > colCount) colCount = r.length; });
|
||||
while (alignments.length < colCount) alignments.push(null);
|
||||
|
||||
rows = rows.map(function (r) {
|
||||
while (r.length < colCount) r.push('');
|
||||
return r;
|
||||
});
|
||||
|
||||
var widths = [];
|
||||
for (var c = 0; c < colCount; c++) {
|
||||
var max = 3;
|
||||
for (var r = 0; r < rows.length; r++) {
|
||||
if (r === sepIndex) continue;
|
||||
if (rows[r][c].length > max) max = rows[r][c].length;
|
||||
}
|
||||
widths.push(max);
|
||||
}
|
||||
|
||||
var formatted = rows.map(function (row, ri) {
|
||||
var cells = row.map(function (cell, ci) {
|
||||
var w = widths[ci];
|
||||
if (ri === sepIndex) return makeSepCell(w, alignments[ci]);
|
||||
var align = alignments[ci];
|
||||
if (align === 'right') return padLeft(cell, w);
|
||||
if (align === 'center') return padCenter(cell, w);
|
||||
return padRight(cell, w);
|
||||
});
|
||||
return '| ' + cells.join(' | ') + ' |';
|
||||
});
|
||||
|
||||
var beforeTable = range.lines.slice(0, range.start).join('\n');
|
||||
var afterTable = range.lines.slice(range.end + 1).join('\n');
|
||||
var parts = [];
|
||||
if (beforeTable) parts.push(beforeTable);
|
||||
parts.push(formatted.join('\n'));
|
||||
if (afterTable) parts.push(afterTable);
|
||||
var newText = parts.join('\n');
|
||||
|
||||
var oldBeforeLen = 0;
|
||||
for (var i = 0; i < range.start; i++) oldBeforeLen += range.lines[i].length + 1;
|
||||
var cursorInTable = cursorPos - oldBeforeLen;
|
||||
var newTableText = formatted.join('\n');
|
||||
var newCursor = (beforeTable ? beforeTable.length + 1 : 0) + Math.min(cursorInTable, newTableText.length);
|
||||
|
||||
return { text: newText, cursor: newCursor };
|
||||
}
|
||||
|
||||
function getCursorColumn(text, cursorPos) {
|
||||
var range = findTableRange(text, cursorPos);
|
||||
if (!range) return null;
|
||||
var lines = text.split('\n');
|
||||
var charCount = 0;
|
||||
for (var i = 0; i < range.cursorLine; i++) charCount += lines[i].length + 1;
|
||||
var lineOffset = cursorPos - charCount;
|
||||
var line = lines[range.cursorLine];
|
||||
var col = -1;
|
||||
for (var c = 0; c < line.length; c++) {
|
||||
if (line.charAt(c) === '|') {
|
||||
col++;
|
||||
if (c >= lineOffset) return Math.max(col - 1, 0);
|
||||
}
|
||||
}
|
||||
return Math.max(col, 0);
|
||||
}
|
||||
|
||||
function setColumnAlignment(text, cursorPos, align) {
|
||||
var range = findTableRange(text, cursorPos);
|
||||
if (!range) return null;
|
||||
var colIdx = getCursorColumn(text, cursorPos);
|
||||
if (colIdx === null) return null;
|
||||
|
||||
var rows = [];
|
||||
var sepIndex = -1;
|
||||
for (var i = range.start; i <= range.end; i++) {
|
||||
var cells = parseTableRow(range.lines[i]);
|
||||
if (sepIndex === -1 && isSeparatorRow(cells)) sepIndex = rows.length;
|
||||
rows.push(cells);
|
||||
}
|
||||
if (sepIndex === -1 || colIdx >= rows[sepIndex].length) return null;
|
||||
|
||||
var cell = rows[sepIndex][colIdx].replace(/:/g, '-');
|
||||
if (align === 'left') cell = ':' + cell.slice(1);
|
||||
else if (align === 'center') cell = ':' + cell.slice(1, -1) + ':';
|
||||
else if (align === 'right') cell = cell.slice(0, -1) + ':';
|
||||
rows[sepIndex][colIdx] = cell;
|
||||
|
||||
var newLines = range.lines.slice();
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
newLines[range.start + i] = '| ' + rows[i].join(' | ') + ' |';
|
||||
}
|
||||
return formatTableText(newLines.join('\n'), cursorPos);
|
||||
}
|
||||
|
||||
function insertColumn(text, cursorPos) {
|
||||
var range = findTableRange(text, cursorPos);
|
||||
if (!range) return null;
|
||||
var colIdx = getCursorColumn(text, cursorPos);
|
||||
if (colIdx === null) return null;
|
||||
|
||||
var newLines = range.lines.slice();
|
||||
var sepIndex = -1;
|
||||
for (var i = range.start; i <= range.end; i++) {
|
||||
var cells = parseTableRow(range.lines[i]);
|
||||
var isSep = sepIndex === -1 && isSeparatorRow(cells);
|
||||
if (isSep) sepIndex = i;
|
||||
cells.splice(colIdx, 0, isSep ? '---' : '');
|
||||
newLines[i] = '| ' + cells.join(' | ') + ' |';
|
||||
}
|
||||
return formatTableText(newLines.join('\n'), cursorPos);
|
||||
}
|
||||
|
||||
function deleteColumn(text, cursorPos) {
|
||||
var range = findTableRange(text, cursorPos);
|
||||
if (!range) return null;
|
||||
var colIdx = getCursorColumn(text, cursorPos);
|
||||
if (colIdx === null) return null;
|
||||
|
||||
var newLines = range.lines.slice();
|
||||
for (var i = range.start; i <= range.end; i++) {
|
||||
var cells = parseTableRow(range.lines[i]);
|
||||
if (cells.length <= 1) return null;
|
||||
cells.splice(colIdx, 1);
|
||||
newLines[i] = '| ' + cells.join(' | ') + ' |';
|
||||
}
|
||||
return formatTableText(newLines.join('\n'), cursorPos);
|
||||
}
|
||||
|
||||
function insertRow(text, cursorPos) {
|
||||
var range = findTableRange(text, cursorPos);
|
||||
if (!range) return null;
|
||||
var colCount = 0;
|
||||
for (var i = range.start; i <= range.end; i++) {
|
||||
var cells = parseTableRow(range.lines[i]);
|
||||
if (cells.length > colCount) colCount = cells.length;
|
||||
}
|
||||
var emptyCells = [];
|
||||
for (var c = 0; c < colCount; c++) emptyCells.push('');
|
||||
var newLines = range.lines.slice();
|
||||
newLines.splice(range.cursorLine + 1, 0, '| ' + emptyCells.join(' | ') + ' |');
|
||||
return formatTableText(newLines.join('\n'), cursorPos);
|
||||
}
|
||||
|
||||
function insertRowBelow(text, cursorPos) {
|
||||
var range = findTableRange(text, cursorPos);
|
||||
if (!range) return null;
|
||||
var result = insertRow(text, cursorPos);
|
||||
if (!result) return null;
|
||||
var lines = result.text.split('\n');
|
||||
var cursor = 0;
|
||||
for (var i = 0; i <= range.cursorLine; i++) cursor += lines[i].length + 1;
|
||||
cursor += 2; // skip leading '| '
|
||||
return { text: result.text, cursor: cursor };
|
||||
}
|
||||
|
||||
function deleteRow(text, cursorPos) {
|
||||
var range = findTableRange(text, cursorPos);
|
||||
if (!range) return null;
|
||||
var sepIndex = -1;
|
||||
for (var i = range.start; i <= range.end; i++) {
|
||||
if (isSeparatorRow(parseTableRow(range.lines[i]))) { sepIndex = i; break; }
|
||||
}
|
||||
if (range.cursorLine === range.start || range.cursorLine === sepIndex) return null;
|
||||
|
||||
var newLines = range.lines.slice();
|
||||
newLines.splice(range.cursorLine, 1);
|
||||
var newCursor = cursorPos;
|
||||
if (range.cursorLine < newLines.length) {
|
||||
var charCount = 0;
|
||||
for (var i = 0; i < range.cursorLine; i++) charCount += newLines[i].length + 1;
|
||||
newCursor = charCount;
|
||||
}
|
||||
return formatTableText(newLines.join('\n'), Math.min(newCursor, newLines.join('\n').length));
|
||||
}
|
||||
|
||||
return {
|
||||
formatTableText: formatTableText,
|
||||
setColumnAlignment: setColumnAlignment,
|
||||
insertColumn: insertColumn,
|
||||
deleteColumn: deleteColumn,
|
||||
insertRow: insertRow,
|
||||
insertRowBelow: insertRowBelow,
|
||||
deleteRow: deleteRow,
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user