Files
markdown_editor/static/js/app.js
2025-10-26 08:14:23 +04:00

286 lines
8.7 KiB
JavaScript

/**
* Main Application
* Coordinates all modules and handles user interactions
*/
// Global state
let webdavClient;
let fileTree;
let editor;
let darkMode;
let collectionSelector;
let clipboard = null;
let currentFilePath = null;
// Simple event bus
const eventBus = {
listeners: {},
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
},
dispatch(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => callback(data));
}
}
};
window.eventBus = eventBus;
// Initialize application
document.addEventListener('DOMContentLoaded', async () => {
// Initialize WebDAV client
webdavClient = new WebDAVClient('/fs/');
// Initialize dark mode
darkMode = new DarkMode();
document.getElementById('darkModeBtn').addEventListener('click', () => {
darkMode.toggle();
});
// Initialize file tree
fileTree = new FileTree('fileTree', webdavClient);
fileTree.onFileSelect = async (item) => {
await editor.loadFile(item.path);
};
// Initialize collection selector
collectionSelector = new CollectionSelector('collectionSelect', webdavClient);
collectionSelector.onChange = async (collection) => {
await fileTree.load();
};
await collectionSelector.load();
await fileTree.load();
// Initialize editor
editor = new MarkdownEditor('editor', 'preview', 'filenameInput');
editor.setWebDAVClient(webdavClient);
// Add test content to verify preview works
setTimeout(() => {
if (!editor.editor.getValue()) {
editor.editor.setValue('# Welcome to Markdown Editor\n\nStart typing to see preview...\n');
editor.updatePreview();
}
}, 200);
// Setup editor drop handler
const editorDropHandler = new EditorDropHandler(
document.querySelector('.editor-container'),
async (file) => {
await handleEditorFileDrop(file);
}
);
// Setup button handlers
document.getElementById('newBtn').addEventListener('click', () => {
editor.newFile();
});
document.getElementById('saveBtn').addEventListener('click', async () => {
await editor.save();
});
document.getElementById('deleteBtn').addEventListener('click', async () => {
await editor.deleteFile();
});
// Setup context menu handlers
setupContextMenuHandlers();
// Initialize mermaid
mermaid.initialize({ startOnLoad: true, theme: darkMode.isDark ? 'dark' : 'default' });
// Initialize file tree actions manager
window.fileTreeActions = new FileTreeActions(webdavClient, fileTree, editor);
// Listen for file-saved event to reload file tree
window.eventBus.on('file-saved', async (path) => {
if (fileTree) {
await fileTree.load();
fileTree.selectNode(path);
}
});
});
// Listen for column resize events to refresh editor
window.addEventListener('column-resize', () => {
if (editor && editor.editor) {
editor.editor.refresh();
}
});
/**
* File Operations
*/
/**
* Context Menu Handlers
*/
function setupContextMenuHandlers() {
const menu = document.getElementById('contextMenu');
menu.addEventListener('click', async (e) => {
const item = e.target.closest('.context-menu-item');
if (!item) return;
const action = item.dataset.action;
const targetPath = menu.dataset.targetPath;
const isDir = menu.dataset.targetIsDir === 'true';
hideContextMenu();
await handleContextAction(action, targetPath, isDir);
});
}
async function handleContextAction(action, targetPath, isDir) {
switch (action) {
case 'open':
if (!isDir) {
await editor.loadFile(targetPath);
}
break;
case 'new-file':
if (isDir) {
const filename = prompt('Enter filename:');
if (filename) {
await fileTree.createFile(targetPath, filename);
}
}
break;
case 'new-folder':
if (isDir) {
const foldername = prompt('Enter folder name:');
if (foldername) {
await fileTree.createFolder(targetPath, foldername);
}
}
break;
case 'upload':
if (isDir) {
showFileUploadDialog(targetPath, async (path, file) => {
await fileTree.uploadFile(path, file);
});
}
break;
case 'download':
if (isDir) {
await fileTree.downloadFolder(targetPath);
} else {
await fileTree.downloadFile(targetPath);
}
break;
case 'rename':
const newName = prompt('Enter new name:', targetPath.split('/').pop());
if (newName) {
const parentPath = targetPath.split('/').slice(0, -1).join('/');
const newPath = parentPath ? `${parentPath}/${newName}` : newName;
try {
await webdavClient.move(targetPath, newPath);
await fileTree.load();
showNotification('Renamed', 'success');
} catch (error) {
console.error('Failed to rename:', error);
showNotification('Failed to rename', 'error');
}
}
break;
case 'copy':
clipboard = { path: targetPath, operation: 'copy' };
showNotification('Copied to clipboard', 'info');
updatePasteVisibility();
break;
case 'cut':
clipboard = { path: targetPath, operation: 'cut' };
showNotification('Cut to clipboard', 'info');
updatePasteVisibility();
break;
case 'paste':
if (clipboard && isDir) {
const filename = clipboard.path.split('/').pop();
const destPath = `${targetPath}/${filename}`;
try {
if (clipboard.operation === 'copy') {
await webdavClient.copy(clipboard.path, destPath);
showNotification('Copied', 'success');
} else {
await webdavClient.move(clipboard.path, destPath);
showNotification('Moved', 'success');
clipboard = null;
updatePasteVisibility();
}
await fileTree.load();
} catch (error) {
console.error('Failed to paste:', error);
showNotification('Failed to paste', 'error');
}
}
break;
case 'delete':
if (confirm(`Delete ${targetPath}?`)) {
try {
await webdavClient.delete(targetPath);
await fileTree.load();
showNotification('Deleted', 'success');
} catch (error) {
console.error('Failed to delete:', error);
showNotification('Failed to delete', 'error');
}
}
break;
}
}
function updatePasteVisibility() {
const pasteItem = document.getElementById('pasteMenuItem');
if (pasteItem) {
pasteItem.style.display = clipboard ? 'block' : 'none';
}
}
/**
* Editor File Drop Handler
*/
async function handleEditorFileDrop(file) {
try {
// Get current file's directory
let targetDir = '';
if (currentFilePath) {
const parts = currentFilePath.split('/');
parts.pop(); // Remove filename
targetDir = parts.join('/');
}
// Upload file
const uploadedPath = await fileTree.uploadFile(targetDir, file);
// Insert markdown link at cursor
const isImage = file.type.startsWith('image/');
const link = isImage
? `![${file.name}](/${webdavClient.currentCollection}/${uploadedPath})`
: `[${file.name}](/${webdavClient.currentCollection}/${uploadedPath})`;
editor.insertAtCursor(link);
showNotification(`Uploaded and inserted link`, 'success');
} catch (error) {
console.error('Failed to handle file drop:', error);
showNotification('Failed to upload file', 'error');
}
}
// Make showContextMenu global
window.showContextMenu = showContextMenu;