157 lines
5.7 KiB
JavaScript
157 lines
5.7 KiB
JavaScript
// MarkLite — Tabs Module
|
|
(function () {
|
|
'use strict';
|
|
var ML = window.MarkLite;
|
|
|
|
// ===== Tab State =====
|
|
ML.tabs = [];
|
|
ML.activeTabId = null;
|
|
var tabIdCounter = 0;
|
|
ML.mruTabStack = [];
|
|
|
|
ML.createTab = function (filePath, content) {
|
|
var id = ++tabIdCounter;
|
|
return { id: id, filePath: filePath || null, content: content || '', isModified: false, scrollTop: 0, scrollLeft: 0, selectionStart: 0, selectionEnd: 0, previewScrollTop: 0 };
|
|
};
|
|
|
|
ML.getActiveTab = function () { return ML.tabs.find(function (t) { return t.id === ML.activeTabId; }) || null; };
|
|
ML.getTabIndex = function (tabId) { return ML.tabs.findIndex(function (t) { return t.id === tabId; }); };
|
|
|
|
ML.saveCurrentTabState = function () {
|
|
var tab = ML.getActiveTab();
|
|
if (!tab) return;
|
|
tab.content = ML.editor.value;
|
|
tab.scrollTop = ML.editor.scrollTop;
|
|
tab.scrollLeft = ML.editor.scrollLeft;
|
|
tab.selectionStart = ML.editor.selectionStart;
|
|
tab.selectionEnd = ML.editor.selectionEnd;
|
|
tab.previewScrollTop = ML.previewPanel.scrollTop;
|
|
};
|
|
|
|
ML.loadTabState = function (tab) {
|
|
ML.editor.focus(); ML.editor.select();
|
|
var inserted = document.execCommand('insertText', false, tab.content);
|
|
if (!inserted) ML.editor.value = tab.content;
|
|
ML.lineCountCache = 0;
|
|
ML.updateLineNumbers();
|
|
ML.renderMarkdown(tab.content);
|
|
ML.updateFileSize();
|
|
ML.cachePreviewPositions();
|
|
requestAnimationFrame(function () {
|
|
ML.editor.scrollTop = tab.scrollTop;
|
|
ML.editor.scrollLeft = tab.scrollLeft;
|
|
ML.lineNumbers.scrollTop = tab.scrollTop;
|
|
ML.editor.setSelectionRange(tab.selectionStart, tab.selectionEnd);
|
|
ML.previewPanel.scrollTop = tab.previewScrollTop;
|
|
ML.updateCursorPosition();
|
|
});
|
|
ML.setModified(tab.isModified);
|
|
if (typeof window.electronAPI !== 'undefined') window.electronAPI.tabSwitched(tab.filePath);
|
|
};
|
|
|
|
ML.setModified = function (modified) {
|
|
var tab = ML.getActiveTab();
|
|
if (tab) {
|
|
tab.isModified = modified;
|
|
var tabEl = ML.tabList.querySelector('.tab-item[data-tab-id="' + tab.id + '"]');
|
|
if (tabEl) tabEl.classList.toggle('modified', modified);
|
|
}
|
|
ML.updateTitle();
|
|
};
|
|
|
|
ML.createNewTab = function (filePath, content) {
|
|
if (filePath) {
|
|
var existing = ML.tabs.find(function (t) { return t.filePath === filePath; });
|
|
if (existing) { ML.switchToTab(existing.id); return existing; }
|
|
}
|
|
var tab = ML.createTab(filePath, content);
|
|
ML.tabs.push(tab);
|
|
ML.switchToTab(tab.id);
|
|
return tab;
|
|
};
|
|
|
|
ML.switchToTab = function (tabId) {
|
|
if (tabId === ML.activeTabId) return;
|
|
ML.saveCurrentTabState();
|
|
if (ML.activeTabId) {
|
|
ML.mruTabStack = ML.mruTabStack.filter(function (id) { return id !== ML.activeTabId; });
|
|
ML.mruTabStack.unshift(ML.activeTabId);
|
|
}
|
|
ML.activeTabId = tabId;
|
|
var tab = ML.getActiveTab();
|
|
if (!tab) return;
|
|
ML.loadTabState(tab);
|
|
ML.renderTabBar();
|
|
ML.updateTitle();
|
|
ML.updateSidebarHighlight();
|
|
ML.welcomeScreen.classList.add('hidden');
|
|
};
|
|
|
|
ML.closeTab = function (tabId) {
|
|
var index = ML.getTabIndex(tabId);
|
|
if (index === -1) return;
|
|
var tab = ML.tabs[index];
|
|
if (tab.isModified) {
|
|
var name = tab.filePath ? ML.getFileName(tab.filePath) : '未命名';
|
|
if (!confirm('"' + name + '" 尚未保存,确定要关闭吗?')) return;
|
|
}
|
|
ML.tabs.splice(index, 1);
|
|
ML.mruTabStack = ML.mruTabStack.filter(function (id) { return id !== tabId; });
|
|
if (tabId === ML.activeTabId) {
|
|
if (ML.tabs.length === 0) {
|
|
ML.activeTabId = null;
|
|
ML.editor.value = '';
|
|
ML.preview.innerHTML = '';
|
|
ML.lineNumbers.innerHTML = '';
|
|
ML.lineCountCache = 0;
|
|
ML.statusSize.textContent = '';
|
|
ML.statusSizeDivider.classList.remove('visible');
|
|
ML.welcomeScreen.classList.remove('hidden');
|
|
document.title = 'MarkLite';
|
|
ML.statusText.textContent = '就绪';
|
|
if (typeof window.electronAPI !== 'undefined') window.electronAPI.tabSwitched(null);
|
|
} else {
|
|
var newIndex = Math.min(index, ML.tabs.length - 1);
|
|
ML.activeTabId = ML.tabs[newIndex].id;
|
|
ML.loadTabState(ML.tabs[newIndex]);
|
|
}
|
|
}
|
|
ML.renderTabBar();
|
|
ML.updateTitle();
|
|
ML.updateSidebarHighlight();
|
|
};
|
|
|
|
ML.renderTabBar = function () {
|
|
ML.tabList.innerHTML = '';
|
|
ML.tabs.forEach(function (tab) {
|
|
var el = document.createElement('div');
|
|
el.className = 'tab-item' + (tab.id === ML.activeTabId ? ' active' : '') + (tab.isModified ? ' modified' : '');
|
|
el.dataset.tabId = tab.id;
|
|
var name = document.createElement('span');
|
|
name.className = 'tab-name';
|
|
name.textContent = tab.filePath ? ML.getFileName(tab.filePath) : '未命名';
|
|
el.appendChild(name);
|
|
var close = document.createElement('button');
|
|
close.className = 'tab-close';
|
|
close.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
|
|
close.addEventListener('click', function (e) { e.stopPropagation(); ML.closeTab(tab.id); });
|
|
el.appendChild(close);
|
|
el.addEventListener('click', function () { ML.switchToTab(tab.id); });
|
|
ML.tabList.appendChild(el);
|
|
});
|
|
};
|
|
|
|
ML.updateTitle = function () {
|
|
var tab = ML.getActiveTab();
|
|
if (tab) {
|
|
var name = tab.filePath ? ML.getFileName(tab.filePath) : '未命名';
|
|
var mod = tab.isModified ? ' *' : '';
|
|
document.title = 'MarkLite - ' + name + mod;
|
|
ML.statusText.textContent = name;
|
|
} else {
|
|
document.title = 'MarkLite';
|
|
ML.statusText.textContent = '就绪';
|
|
}
|
|
};
|
|
})();
|