File Explorer: CodeMirror + EasyMDE statt Preview
This commit is contained in:
1 parent
b4521a95ef
commit
bba82aad7e
18 files changed
+410
-87
No files matched your search
+140
-48
@@ -59,6 +59,7 @@ function openFileExplorer(opts = {}) {
|
||||
}
|
||||
|
||||
function closeFileExplorer() {
|
||||
feDestroyEditor();
|
||||
const overlay = document.getElementById('feOverlay');
|
||||
if (overlay) overlay.remove();
|
||||
if (_feState && _feState._keyHandler) {
|
||||
@@ -83,6 +84,7 @@ async function feLoadDir(path) {
|
||||
sidebar.innerHTML = '<div class="fe-loading">Laden...</div>';
|
||||
|
||||
// Preview zurücksetzen
|
||||
feDestroyEditor();
|
||||
const main = document.getElementById('feMain');
|
||||
main.innerHTML = '<div class="fe-placeholder">Datei auswählen</div>';
|
||||
document.getElementById('feFooter').hidden = true;
|
||||
@@ -163,6 +165,9 @@ async function feSelectFile(entry, itemEl) {
|
||||
const footer = document.getElementById('feFooter');
|
||||
const info = document.getElementById('feInfo');
|
||||
|
||||
// Vorherige Editor-Instanz aufräumen
|
||||
feDestroyEditor();
|
||||
|
||||
if (!entry.isText) {
|
||||
main.innerHTML = `<div class="fe-placeholder">${getFileIcon(entry.ext)} ${entry.name}<br><small>${formatSize(entry.size)} - Vorschau nicht verfügbar</small></div>`;
|
||||
footer.hidden = true;
|
||||
@@ -182,30 +187,15 @@ async function feSelectFile(entry, itemEl) {
|
||||
const data = await resp.json();
|
||||
_feState.selectedFile = data;
|
||||
|
||||
// Textarea mit Zeilennummern
|
||||
main.innerHTML = `
|
||||
<div class="fe-preview">
|
||||
<div class="fe-line-numbers" id="feLineNums"></div>
|
||||
<textarea class="fe-textarea" id="feTextarea" readonly spellcheck="false">${escHtml(data.content)}</textarea>
|
||||
</div>
|
||||
`;
|
||||
const isMarkdown = entry.ext === '.md' || entry.ext === '.markdown';
|
||||
|
||||
const textarea = document.getElementById('feTextarea');
|
||||
const lineNums = document.getElementById('feLineNums');
|
||||
if (isMarkdown) {
|
||||
feInitEasyMDE(main, data);
|
||||
} else {
|
||||
feInitCodeMirror(main, data, entry.ext);
|
||||
}
|
||||
|
||||
// Zeilennummern generieren
|
||||
renderLineNumbers(data.content, lineNums);
|
||||
|
||||
// Sync scroll
|
||||
textarea.addEventListener('scroll', () => {
|
||||
lineNums.scrollTop = textarea.scrollTop;
|
||||
});
|
||||
|
||||
// Selection-Tracking für Zeilen-Info
|
||||
textarea.addEventListener('mouseup', () => updateSelectionInfo());
|
||||
textarea.addEventListener('keyup', () => updateSelectionInfo());
|
||||
|
||||
info.textContent = `${data.path} - ${data.lineCount} Zeilen, ${formatSize(data.size)}`;
|
||||
info.textContent = `${data.path} — ${data.lineCount} Zeilen, ${formatSize(data.size)}`;
|
||||
footer.hidden = false;
|
||||
|
||||
} catch (e) {
|
||||
@@ -213,42 +203,144 @@ async function feSelectFile(entry, itemEl) {
|
||||
}
|
||||
}
|
||||
|
||||
function renderLineNumbers(content, container) {
|
||||
const lines = content.split('\n');
|
||||
container.innerHTML = lines.map((_, i) => `<div>${i + 1}</div>`).join('');
|
||||
}
|
||||
|
||||
function updateSelectionInfo() {
|
||||
if (!_feState || !_feState.selectedFile) return;
|
||||
|
||||
const textarea = document.getElementById('feTextarea');
|
||||
const info = document.getElementById('feInfo');
|
||||
const data = _feState.selectedFile;
|
||||
|
||||
if (textarea.selectionStart !== textarea.selectionEnd) {
|
||||
const from = getLineNumber(textarea.value, textarea.selectionStart);
|
||||
const to = getLineNumber(textarea.value, textarea.selectionEnd);
|
||||
info.textContent = `${data.path}:${from}-${to} (${to - from + 1} Zeilen ausgewählt)`;
|
||||
} else {
|
||||
info.textContent = `${data.path} - ${data.lineCount} Zeilen, ${formatSize(data.size)}`;
|
||||
function feDestroyEditor() {
|
||||
if (_feState && _feState._editor) {
|
||||
if (_feState._editorType === 'easymde') {
|
||||
try { _feState._editor.toTextArea(); } catch (_) {}
|
||||
}
|
||||
// CodeMirror wird durch innerHTML-Reset automatisch verworfen
|
||||
_feState._editor = null;
|
||||
_feState._editorType = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getLineNumber(text, pos) {
|
||||
return text.substring(0, pos).split('\n').length;
|
||||
function feInitCodeMirror(container, data, ext) {
|
||||
container.innerHTML = '<div class="fe-cm-wrap"><textarea id="feCmTextarea"></textarea></div>';
|
||||
|
||||
const textarea = document.getElementById('feCmTextarea');
|
||||
textarea.value = data.content;
|
||||
|
||||
const mode = getCmMode(ext);
|
||||
|
||||
const cm = CodeMirror.fromTextArea(textarea, {
|
||||
mode: mode,
|
||||
theme: 'default',
|
||||
lineNumbers: true,
|
||||
readOnly: true,
|
||||
lineWrapping: false,
|
||||
tabSize: 4,
|
||||
indentWithTabs: false,
|
||||
extraKeys: {}
|
||||
});
|
||||
|
||||
// Selektion-Tracking für Zeilen-Info
|
||||
cm.on('cursorActivity', () => {
|
||||
if (!_feState || !_feState.selectedFile) return;
|
||||
feUpdateSelectionInfo(cm, data);
|
||||
});
|
||||
|
||||
_feState._editor = cm;
|
||||
_feState._editorType = 'codemirror';
|
||||
}
|
||||
|
||||
function feInitEasyMDE(container, data) {
|
||||
container.innerHTML = '<div class="fe-mde-wrap"><textarea id="feMdeTextarea"></textarea></div>';
|
||||
|
||||
const textarea = document.getElementById('feMdeTextarea');
|
||||
|
||||
const mde = new EasyMDE({
|
||||
element: textarea,
|
||||
initialValue: data.content,
|
||||
toolbar: false,
|
||||
status: false,
|
||||
autofocus: false,
|
||||
spellChecker: false,
|
||||
autoDownloadFontAwesome: false,
|
||||
minHeight: '100%',
|
||||
maxHeight: '100%'
|
||||
});
|
||||
|
||||
// Readonly via CodeMirror-Instanz
|
||||
mde.codemirror.setOption('readOnly', true);
|
||||
mde.codemirror.setOption('theme', 'default');
|
||||
|
||||
mde.codemirror.on('cursorActivity', () => {
|
||||
if (!_feState || !_feState.selectedFile) return;
|
||||
feUpdateSelectionInfo(mde.codemirror, data);
|
||||
});
|
||||
|
||||
_feState._editor = mde;
|
||||
_feState._editorType = 'easymde';
|
||||
}
|
||||
|
||||
function getCmMode(ext) {
|
||||
const map = {
|
||||
'.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
|
||||
'.ts': 'javascript', '.jsx': 'javascript', '.tsx': 'javascript',
|
||||
'.json': 'javascript',
|
||||
'.css': 'css', '.scss': 'css', '.less': 'css',
|
||||
'.html': 'htmlmixed', '.htm': 'htmlmixed',
|
||||
'.xml': 'xml', '.svg': 'xml', '.csproj': 'xml', '.sln': 'xml',
|
||||
'.py': 'python',
|
||||
'.sh': 'shell', '.bash': 'shell', '.zsh': 'shell',
|
||||
'.yaml': 'yaml', '.yml': 'yaml',
|
||||
'.cs': 'clike', '.java': 'clike', '.cpp': 'clike', '.c': 'clike', '.h': 'clike'
|
||||
};
|
||||
return map[ext?.toLowerCase()] || 'null';
|
||||
}
|
||||
|
||||
|
||||
|
||||
function feGetCm() {
|
||||
if (!_feState || !_feState._editor) return null;
|
||||
if (_feState._editorType === 'easymde') return _feState._editor.codemirror;
|
||||
if (_feState._editorType === 'codemirror') return _feState._editor;
|
||||
return null;
|
||||
}
|
||||
|
||||
function feGetSelection(cm) {
|
||||
const sel = cm.listSelections()[0];
|
||||
const anchorLine = sel.anchor.line;
|
||||
const headLine = sel.head.line;
|
||||
const anchorCh = sel.anchor.ch;
|
||||
const headCh = sel.head.ch;
|
||||
|
||||
// Echte Selektion: unterschiedliche Position (line oder ch)
|
||||
const hasSelection = anchorLine !== headLine || anchorCh !== headCh;
|
||||
if (!hasSelection) return null;
|
||||
|
||||
const fromLine = Math.min(anchorLine, headLine) + 1;
|
||||
const toLine = Math.max(anchorLine, headLine) + 1;
|
||||
return { from: fromLine, to: toLine };
|
||||
}
|
||||
|
||||
function feUpdateSelectionInfo(cm, data) {
|
||||
const info = document.getElementById('feInfo');
|
||||
if (!info) return;
|
||||
const sel = feGetSelection(cm);
|
||||
if (sel && sel.from !== sel.to) {
|
||||
info.textContent = `${data.path}:${sel.from}-${sel.to} (${sel.to - sel.from + 1} Zeilen ausgewählt)`;
|
||||
} else if (sel) {
|
||||
info.textContent = `${data.path}:${sel.from} (1 Zeile ausgewählt)`;
|
||||
} else {
|
||||
info.textContent = `${data.path} — ${data.lineCount} Zeilen, ${formatSize(data.size)}`;
|
||||
}
|
||||
}
|
||||
|
||||
function feDoSelect() {
|
||||
if (!_feState || !_feState.selectedFile) return;
|
||||
|
||||
const data = _feState.selectedFile;
|
||||
const textarea = document.getElementById('feTextarea');
|
||||
|
||||
let ref = data.path;
|
||||
if (textarea && textarea.selectionStart !== textarea.selectionEnd) {
|
||||
const from = getLineNumber(textarea.value, textarea.selectionStart);
|
||||
const to = getLineNumber(textarea.value, textarea.selectionEnd);
|
||||
ref = `${data.path}:${from}-${to}`;
|
||||
|
||||
const cm = feGetCm();
|
||||
if (cm) {
|
||||
const sel = feGetSelection(cm);
|
||||
if (sel) {
|
||||
ref = sel.from === sel.to
|
||||
? `${data.path}:${sel.from}`
|
||||
: `${data.path}:${sel.from}-${sel.to}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (_feState.onSelect) {
|
||||
|
||||
Reference in new issue
Block a user