148 lines
6.5 KiB
JavaScript
148 lines
6.5 KiB
JavaScript
// MarkLite — Editor Module: line numbers, cursor, file size, scroll sync
|
|
(function () {
|
|
'use strict';
|
|
var ML = window.MarkLite;
|
|
|
|
// ===== Line Numbers =====
|
|
function countNewlines(text) {
|
|
var count = 1;
|
|
for (var i = 0, len = text.length; i < len; i++) {
|
|
if (text.charCodeAt(i) === 10) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
ML.updateLineNumbers = function () {
|
|
var count = countNewlines(ML.editor.value);
|
|
if (count === ML.lineCountCache) return;
|
|
ML.lineCountCache = count;
|
|
if (count > 2000) {
|
|
ML.renderVirtualLineNumbers(count);
|
|
} else {
|
|
var arr = new Array(count);
|
|
for (var i = 0; i < count; i++) arr[i] = '<div class="line-num">' + (i + 1) + '</div>';
|
|
ML.lineNumbers.innerHTML = arr.join('');
|
|
}
|
|
};
|
|
|
|
ML.renderVirtualLineNumbers = function (totalLines) {
|
|
var lineHeight = 20.8;
|
|
var scrollTop = ML.editor.scrollTop;
|
|
var viewportHeight = ML.editor.clientHeight;
|
|
var buffer = 20;
|
|
var startLine = Math.max(0, Math.floor(scrollTop / lineHeight) - buffer);
|
|
var endLine = Math.min(totalLines, Math.ceil((scrollTop + viewportHeight) / lineHeight) + buffer);
|
|
var visibleCount = endLine - startLine;
|
|
var topPadding = startLine * lineHeight;
|
|
var bottomPadding = (totalLines - endLine) * lineHeight;
|
|
var arr = new Array(visibleCount + 2);
|
|
arr[0] = '<div style="height:' + topPadding + 'px"></div>';
|
|
for (var i = 0; i < visibleCount; i++) arr[i + 1] = '<div class="line-num">' + (startLine + i + 1) + '</div>';
|
|
arr[visibleCount + 1] = '<div style="height:' + bottomPadding + 'px"></div>';
|
|
ML.lineNumbers.innerHTML = arr.join('');
|
|
};
|
|
|
|
// ===== Cursor Position =====
|
|
ML.updateCursorPosition = function () {
|
|
var text = ML.editor.value;
|
|
var pos = ML.editor.selectionStart;
|
|
var textBefore = text.substring(0, pos);
|
|
var lines = textBefore.split('\n');
|
|
ML.statusCursor.textContent = '行 ' + lines.length + ', 列 ' + (lines[lines.length - 1].length + 1);
|
|
};
|
|
|
|
// ===== File Size =====
|
|
ML.updateFileSize = function () {
|
|
var bytes = new Blob([ML.editor.value]).size;
|
|
ML.statusSize.textContent = ML.formatBytes(bytes);
|
|
ML.statusSizeDivider.classList.add('visible');
|
|
};
|
|
|
|
// ===== Scroll Sync =====
|
|
var previewPositions = null;
|
|
var lineToPreviewMap = null;
|
|
|
|
ML.cachePreviewPositions = function () {
|
|
previewPositions = [];
|
|
for (var i = 0; i < ML.preview.children.length; i++) previewPositions.push(ML.preview.children[i].offsetTop);
|
|
buildLineToPreviewMap();
|
|
};
|
|
|
|
function buildLineToPreviewMap() {
|
|
if (!previewPositions || previewPositions.length === 0) { lineToPreviewMap = null; return; }
|
|
var text = ML.editor.value;
|
|
var lines = text.split('\n');
|
|
var totalLines = lines.length;
|
|
var blockStartLines = [];
|
|
var inCodeBlock = false;
|
|
for (var i = 0; i < totalLines; i++) {
|
|
var trimmed = lines[i].trimStart();
|
|
if (trimmed.startsWith('```')) {
|
|
if (!inCodeBlock) { blockStartLines.push(i); inCodeBlock = true; }
|
|
else inCodeBlock = false;
|
|
continue;
|
|
}
|
|
if (inCodeBlock) continue;
|
|
if (trimmed.startsWith('#') || trimmed.startsWith('- ') || trimmed.startsWith('* ') || trimmed.startsWith('+ ') ||
|
|
/^\d+\.\s/.test(trimmed) || trimmed.startsWith('> ') || trimmed.startsWith('|') ||
|
|
trimmed.startsWith('---') || trimmed.startsWith('***') || trimmed.startsWith('___') ||
|
|
(trimmed === '' && i > 0 && lines[i - 1].trim() === '')) {
|
|
blockStartLines.push(i);
|
|
}
|
|
}
|
|
var uniqueStartsArr = [].concat(_toConsumableArray(new Set(blockStartLines))).sort(function (a, b) { return a - b; });
|
|
var uniqueStartsSet = new Set(uniqueStartsArr);
|
|
lineToPreviewMap = new Float32Array(totalLines);
|
|
if (uniqueStartsArr.length === 0) {
|
|
for (var _i = 0; _i < totalLines; _i++) lineToPreviewMap[_i] = (_i / Math.max(1, totalLines - 1)) * previewPositions[previewPositions.length - 1];
|
|
return;
|
|
}
|
|
var domCount = previewPositions.length;
|
|
for (var _i2 = 0; _i2 < uniqueStartsArr.length; _i2++) {
|
|
var lineIdx = uniqueStartsArr[_i2];
|
|
var domIdx = Math.min(Math.floor((_i2 / uniqueStartsArr.length) * domCount), domCount - 1);
|
|
lineToPreviewMap[lineIdx] = previewPositions[domIdx];
|
|
}
|
|
for (var _i3 = 0; _i3 < totalLines; _i3++) {
|
|
if (lineToPreviewMap[_i3] !== 0 && uniqueStartsSet.has(_i3)) continue;
|
|
var prevLine = -1, nextLine = -1;
|
|
for (var j = _i3 - 1; j >= 0; j--) { if (lineToPreviewMap[j] !== 0 || uniqueStartsSet.has(j)) { prevLine = j; break; } }
|
|
for (var _j = _i3 + 1; _j < totalLines; _j++) { if (lineToPreviewMap[_j] !== 0 || uniqueStartsSet.has(_j)) { nextLine = _j; break; } }
|
|
if (prevLine === -1 && nextLine === -1) lineToPreviewMap[_i3] = 0;
|
|
else if (prevLine === -1) lineToPreviewMap[_i3] = lineToPreviewMap[nextLine];
|
|
else if (nextLine === -1) lineToPreviewMap[_i3] = lineToPreviewMap[prevLine];
|
|
else { var ratio = (_i3 - prevLine) / (nextLine - prevLine); lineToPreviewMap[_i3] = lineToPreviewMap[prevLine] + ratio * (lineToPreviewMap[nextLine] - lineToPreviewMap[prevLine]); }
|
|
}
|
|
}
|
|
|
|
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } return Array.from(arr); }
|
|
|
|
ML.syncScroll = function () {
|
|
if (ML.viewMode !== 'split') return;
|
|
var lineHeight = parseFloat(getComputedStyle(ML.editor).lineHeight) || 20.8;
|
|
var scrollTop = ML.editor.scrollTop;
|
|
var topLine = Math.floor(scrollTop / lineHeight);
|
|
if (!previewPositions || previewPositions.length === 0) ML.cachePreviewPositions();
|
|
if (!previewPositions || previewPositions.length === 0) return;
|
|
if (lineToPreviewMap && topLine >= 0 && topLine < lineToPreviewMap.length) {
|
|
ML.previewPanel.scrollTop = lineToPreviewMap[topLine];
|
|
} else {
|
|
var totalLines = ML.editor.value.split('\n').length;
|
|
var ratio = totalLines > 1 ? topLine / (totalLines - 1) : 0;
|
|
var targetIndex = Math.min(Math.floor(ratio * previewPositions.length), previewPositions.length - 1);
|
|
if (targetIndex >= 0 && previewPositions[targetIndex] !== undefined) ML.previewPanel.scrollTop = previewPositions[targetIndex];
|
|
}
|
|
};
|
|
|
|
// ===== Schedule Update =====
|
|
ML.scheduleUpdate = function () {
|
|
if (ML.updateTimer) clearTimeout(ML.updateTimer);
|
|
ML.updateTimer = setTimeout(function () {
|
|
ML.renderMarkdown(ML.editor.value);
|
|
ML.updateLineNumbers();
|
|
ML.updateFileSize();
|
|
ML.cachePreviewPositions();
|
|
}, 150);
|
|
};
|
|
})();
|