// 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] = '
' + (i + 1) + '
';
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] = '';
for (var i = 0; i < visibleCount; i++) arr[i + 1] = '' + (startLine + i + 1) + '
';
arr[visibleCount + 1] = '';
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);
};
})();