...
This commit is contained in:
@@ -19,6 +19,8 @@ class MarkdownEditorApp:
|
|||||||
"""Main application that wraps WsgiDAV and adds custom endpoints"""
|
"""Main application that wraps WsgiDAV and adds custom endpoints"""
|
||||||
|
|
||||||
def __init__(self, config_path="config.yaml"):
|
def __init__(self, config_path="config.yaml"):
|
||||||
|
self.root_path = Path(__file__).parent.resolve()
|
||||||
|
os.chdir(self.root_path)
|
||||||
self.config = self.load_config(config_path)
|
self.config = self.load_config(config_path)
|
||||||
self.collections = self.config.get('collections', {})
|
self.collections = self.config.get('collections', {})
|
||||||
self.setup_collections()
|
self.setup_collections()
|
||||||
@@ -72,21 +74,26 @@ class MarkdownEditorApp:
|
|||||||
"""WSGI application entry point"""
|
"""WSGI application entry point"""
|
||||||
path = environ.get('PATH_INFO', '')
|
path = environ.get('PATH_INFO', '')
|
||||||
method = environ.get('REQUEST_METHOD', '')
|
method = environ.get('REQUEST_METHOD', '')
|
||||||
|
|
||||||
# Handle collection list endpoint
|
# Root and index.html
|
||||||
if path == '/fs/' and method == 'GET':
|
|
||||||
return self.handle_collections_list(environ, start_response)
|
|
||||||
|
|
||||||
# Handle static files
|
|
||||||
if path.startswith('/static/'):
|
|
||||||
return self.handle_static(environ, start_response)
|
|
||||||
|
|
||||||
# Handle root - serve index.html
|
|
||||||
if path == '/' or path == '/index.html':
|
if path == '/' or path == '/index.html':
|
||||||
return self.handle_index(environ, start_response)
|
return self.handle_index(environ, start_response)
|
||||||
|
|
||||||
# All other requests go to WebDAV
|
# Static files
|
||||||
return self.webdav_app(environ, start_response)
|
if path.startswith('/static/'):
|
||||||
|
return self.handle_static(environ, start_response)
|
||||||
|
|
||||||
|
# API for collections
|
||||||
|
if path == '/fs/' and method == 'GET':
|
||||||
|
return self.handle_collections_list(environ, start_response)
|
||||||
|
|
||||||
|
# All other /fs/ requests go to WebDAV
|
||||||
|
if path.startswith('/fs/'):
|
||||||
|
return self.webdav_app(environ, start_response)
|
||||||
|
|
||||||
|
# Fallback for anything else (shouldn't happen with correct linking)
|
||||||
|
start_response('404 Not Found', [('Content-Type', 'text/plain')])
|
||||||
|
return [b'Not Found']
|
||||||
|
|
||||||
def handle_collections_list(self, environ, start_response):
|
def handle_collections_list(self, environ, start_response):
|
||||||
"""Return list of available collections"""
|
"""Return list of available collections"""
|
||||||
@@ -104,9 +111,9 @@ class MarkdownEditorApp:
|
|||||||
def handle_static(self, environ, start_response):
|
def handle_static(self, environ, start_response):
|
||||||
"""Serve static files"""
|
"""Serve static files"""
|
||||||
path = environ.get('PATH_INFO', '')[1:] # Remove leading /
|
path = environ.get('PATH_INFO', '')[1:] # Remove leading /
|
||||||
file_path = Path(path)
|
file_path = self.root_path / path
|
||||||
|
|
||||||
if not file_path.exists() or not file_path.is_file():
|
if not file_path.is_file():
|
||||||
start_response('404 Not Found', [('Content-Type', 'text/plain')])
|
start_response('404 Not Found', [('Content-Type', 'text/plain')])
|
||||||
return [b'File not found']
|
return [b'File not found']
|
||||||
|
|
||||||
@@ -139,9 +146,9 @@ class MarkdownEditorApp:
|
|||||||
|
|
||||||
def handle_index(self, environ, start_response):
|
def handle_index(self, environ, start_response):
|
||||||
"""Serve index.html"""
|
"""Serve index.html"""
|
||||||
index_path = Path('templates/index.html')
|
index_path = self.root_path / 'templates' / 'index.html'
|
||||||
|
|
||||||
if not index_path.exists():
|
if not index_path.is_file():
|
||||||
start_response('404 Not Found', [('Content-Type', 'text/plain')])
|
start_response('404 Not Found', [('Content-Type', 'text/plain')])
|
||||||
return [b'index.html not found']
|
return [b'index.html not found']
|
||||||
|
|
||||||
|
|||||||
6
start.sh
6
start.sh
@@ -1,5 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# Change to the script's directory to ensure relative paths work
|
||||||
|
cd "$(dirname "$0")"
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
echo "Markdown Editor v3.0 - WebDAV Server"
|
echo "Markdown Editor v3.0 - WebDAV Server"
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
@@ -16,5 +19,8 @@ echo "Activating virtual environment..."
|
|||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
echo "Installing dependencies..."
|
echo "Installing dependencies..."
|
||||||
uv pip install wsgidav cheroot pyyaml
|
uv pip install wsgidav cheroot pyyaml
|
||||||
|
PORT=8004
|
||||||
|
echo "Checking for process on port $PORT..."
|
||||||
|
lsof -ti:$PORT | xargs -r kill -9
|
||||||
echo "Starting WebDAV server..."
|
echo "Starting WebDAV server..."
|
||||||
python server_webdav.py
|
python server_webdav.py
|
||||||
|
|||||||
@@ -132,3 +132,31 @@ html, body {
|
|||||||
background: var(--text-secondary);
|
background: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Preview Pane Styling */
|
||||||
|
#previewPane {
|
||||||
|
flex: 1 1 40%;
|
||||||
|
min-width: 250px;
|
||||||
|
max-width: 70%;
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
border-left: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview {
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 100%;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview > p:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview > h1:first-child,
|
||||||
|
#preview > h2:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
103
static/js/app.js
103
static/js/app.js
@@ -12,6 +12,23 @@ let collectionSelector;
|
|||||||
let clipboard = null;
|
let clipboard = null;
|
||||||
let currentFilePath = 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
|
// Initialize application
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
// Initialize WebDAV client
|
// Initialize WebDAV client
|
||||||
@@ -26,7 +43,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
// Initialize file tree
|
// Initialize file tree
|
||||||
fileTree = new FileTree('fileTree', webdavClient);
|
fileTree = new FileTree('fileTree', webdavClient);
|
||||||
fileTree.onFileSelect = async (item) => {
|
fileTree.onFileSelect = async (item) => {
|
||||||
await loadFile(item.path);
|
await editor.loadFile(item.path);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize collection selector
|
// Initialize collection selector
|
||||||
@@ -39,6 +56,15 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
|
|
||||||
// Initialize editor
|
// Initialize editor
|
||||||
editor = new MarkdownEditor('editor', 'preview', 'filenameInput');
|
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
|
// Setup editor drop handler
|
||||||
const editorDropHandler = new EditorDropHandler(
|
const editorDropHandler = new EditorDropHandler(
|
||||||
@@ -50,15 +76,15 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
|
|
||||||
// Setup button handlers
|
// Setup button handlers
|
||||||
document.getElementById('newBtn').addEventListener('click', () => {
|
document.getElementById('newBtn').addEventListener('click', () => {
|
||||||
newFile();
|
editor.newFile();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('saveBtn').addEventListener('click', async () => {
|
document.getElementById('saveBtn').addEventListener('click', async () => {
|
||||||
await saveFile();
|
await editor.save();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('deleteBtn').addEventListener('click', async () => {
|
document.getElementById('deleteBtn').addEventListener('click', async () => {
|
||||||
await deleteCurrentFile();
|
await editor.deleteFile();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup context menu handlers
|
// Setup context menu handlers
|
||||||
@@ -69,6 +95,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
|
|
||||||
// Initialize file tree actions manager
|
// Initialize file tree actions manager
|
||||||
window.fileTreeActions = new FileTreeActions(webdavClient, fileTree, editor);
|
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
|
// Listen for column resize events to refresh editor
|
||||||
@@ -81,66 +114,6 @@ window.addEventListener('column-resize', () => {
|
|||||||
/**
|
/**
|
||||||
* File Operations
|
* File Operations
|
||||||
*/
|
*/
|
||||||
async function loadFile(path) {
|
|
||||||
try {
|
|
||||||
const content = await webdavClient.get(path);
|
|
||||||
editor.setValue(content);
|
|
||||||
document.getElementById('filenameInput').value = path;
|
|
||||||
currentFilePath = path;
|
|
||||||
showNotification('File loaded', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load file:', error);
|
|
||||||
showNotification('Failed to load file', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function newFile() {
|
|
||||||
editor.setValue('# New File\n\nStart typing...\n');
|
|
||||||
document.getElementById('filenameInput').value = '';
|
|
||||||
document.getElementById('filenameInput').focus();
|
|
||||||
currentFilePath = null;
|
|
||||||
showNotification('New file', 'info');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveFile() {
|
|
||||||
const filename = document.getElementById('filenameInput').value.trim();
|
|
||||||
if (!filename) {
|
|
||||||
showNotification('Please enter a filename', 'warning');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const content = editor.getValue();
|
|
||||||
await webdavClient.put(filename, content);
|
|
||||||
currentFilePath = filename;
|
|
||||||
await fileTree.load();
|
|
||||||
showNotification('Saved', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to save file:', error);
|
|
||||||
showNotification('Failed to save file', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteCurrentFile() {
|
|
||||||
if (!currentFilePath) {
|
|
||||||
showNotification('No file selected', 'warning');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!confirm(`Delete ${currentFilePath}?`)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await webdavClient.delete(currentFilePath);
|
|
||||||
await fileTree.load();
|
|
||||||
newFile();
|
|
||||||
showNotification('Deleted', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to delete file:', error);
|
|
||||||
showNotification('Failed to delete file', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context Menu Handlers
|
* Context Menu Handlers
|
||||||
@@ -166,7 +139,7 @@ async function handleContextAction(action, targetPath, isDir) {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case 'open':
|
case 'open':
|
||||||
if (!isDir) {
|
if (!isDir) {
|
||||||
await loadFile(targetPath);
|
await editor.loadFile(targetPath);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -32,10 +32,15 @@ class MarkdownEditor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update preview on change
|
// Update preview on change with debouncing
|
||||||
this.editor.on('change', () => {
|
this.editor.on('change', this.debounce(() => {
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
});
|
}, 300));
|
||||||
|
|
||||||
|
// Initial preview render
|
||||||
|
setTimeout(() => {
|
||||||
|
this.updatePreview();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// Sync scroll
|
// Sync scroll
|
||||||
this.editor.on('scroll', () => {
|
this.editor.on('scroll', () => {
|
||||||
@@ -47,17 +52,21 @@ class MarkdownEditor {
|
|||||||
* Initialize markdown parser
|
* Initialize markdown parser
|
||||||
*/
|
*/
|
||||||
initMarkdown() {
|
initMarkdown() {
|
||||||
this.marked = window.marked;
|
if (window.marked) {
|
||||||
this.marked.setOptions({
|
this.marked = window.marked;
|
||||||
breaks: true,
|
this.marked.setOptions({
|
||||||
gfm: true,
|
breaks: true,
|
||||||
highlight: (code, lang) => {
|
gfm: true,
|
||||||
if (lang && window.Prism.languages[lang]) {
|
highlight: (code, lang) => {
|
||||||
return window.Prism.highlight(code, window.Prism.languages[lang], lang);
|
if (lang && window.Prism.languages[lang]) {
|
||||||
|
return window.Prism.highlight(code, window.Prism.languages[lang], lang);
|
||||||
|
}
|
||||||
|
return code;
|
||||||
}
|
}
|
||||||
return code;
|
});
|
||||||
}
|
} else {
|
||||||
});
|
console.error('Marked library not found.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,10 +132,9 @@ class MarkdownEditor {
|
|||||||
window.showNotification('✅ Saved', 'success');
|
window.showNotification('✅ Saved', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger file tree reload
|
// Dispatch event to reload file tree
|
||||||
if (window.fileTree) {
|
if (window.eventBus) {
|
||||||
await window.fileTree.load();
|
window.eventBus.dispatch('file-saved', path);
|
||||||
window.fileTree.selectNode(path);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save file:', error);
|
console.error('Failed to save file:', error);
|
||||||
@@ -192,24 +200,66 @@ class MarkdownEditor {
|
|||||||
*/
|
*/
|
||||||
updatePreview() {
|
updatePreview() {
|
||||||
const markdown = this.editor.getValue();
|
const markdown = this.editor.getValue();
|
||||||
let html = window.marked.parse(markdown);
|
const previewDiv = this.previewElement;
|
||||||
|
|
||||||
// Process mermaid diagrams
|
if (!markdown || !markdown.trim()) {
|
||||||
html = html.replace(/<pre><code class="language-mermaid">([\s\S]*?)<\/code><\/pre>/g, (match, code) => {
|
previewDiv.innerHTML = `
|
||||||
const id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
|
<div class="text-muted text-center mt-5">
|
||||||
return `<div class="mermaid" id="${id}">${code}</div>`;
|
<p>Start typing to see preview...</p>
|
||||||
});
|
</div>
|
||||||
|
`;
|
||||||
this.previewElement.innerHTML = html;
|
return;
|
||||||
|
|
||||||
// Render mermaid diagrams
|
|
||||||
if (window.mermaid) {
|
|
||||||
window.mermaid.init(undefined, this.previewElement.querySelectorAll('.mermaid'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight code blocks
|
try {
|
||||||
if (window.Prism) {
|
// Parse markdown to HTML
|
||||||
window.Prism.highlightAllUnder(this.previewElement);
|
if (!this.marked) {
|
||||||
|
console.error("Markdown parser (marked) not initialized.");
|
||||||
|
previewDiv.innerHTML = `<div class="alert alert-danger">Preview engine not loaded.</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let html = this.marked.parse(markdown);
|
||||||
|
|
||||||
|
// Replace mermaid code blocks with div containers
|
||||||
|
html = html.replace(
|
||||||
|
/<pre><code class="language-mermaid">([\s\S]*?)<\/code><\/pre>/g,
|
||||||
|
(match, code) => {
|
||||||
|
const id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
|
||||||
|
return `<div class="mermaid" id="${id}">${code.trim()}</div>`;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
previewDiv.innerHTML = html;
|
||||||
|
|
||||||
|
// Apply syntax highlighting to code blocks
|
||||||
|
const codeBlocks = previewDiv.querySelectorAll('pre code');
|
||||||
|
codeBlocks.forEach(block => {
|
||||||
|
const languageClass = Array.from(block.classList)
|
||||||
|
.find(cls => cls.startsWith('language-'));
|
||||||
|
if (languageClass && languageClass !== 'language-mermaid') {
|
||||||
|
if (window.Prism) {
|
||||||
|
window.Prism.highlightElement(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render mermaid diagrams
|
||||||
|
const mermaidElements = previewDiv.querySelectorAll('.mermaid');
|
||||||
|
if (mermaidElements.length > 0 && window.mermaid) {
|
||||||
|
try {
|
||||||
|
window.mermaid.contentLoaded();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Mermaid rendering error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Preview rendering error:', error);
|
||||||
|
previewDiv.innerHTML = `
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<strong>Error rendering preview:</strong><br>
|
||||||
|
${error.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +316,21 @@ class MarkdownEditor {
|
|||||||
setValue(content) {
|
setValue(content) {
|
||||||
this.editor.setValue(content);
|
this.editor.setValue(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounce function
|
||||||
|
*/
|
||||||
|
debounce(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function executedFunction(...args) {
|
||||||
|
const later = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
func(...args);
|
||||||
|
};
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export for use in other modules
|
// Export for use in other modules
|
||||||
|
|||||||
@@ -92,29 +92,24 @@ function hideContextMenu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context menu item click handler
|
// Combined click handler for context menu and outside clicks
|
||||||
document.addEventListener('click', async (e) => {
|
document.addEventListener('click', async (e) => {
|
||||||
const menuItem = e.target.closest('.context-menu-item');
|
const menuItem = e.target.closest('.context-menu-item');
|
||||||
if (!menuItem) {
|
|
||||||
hideContextMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const action = menuItem.dataset.action;
|
if (menuItem) {
|
||||||
const menu = document.getElementById('contextMenu');
|
// Handle context menu item click
|
||||||
const targetPath = menu.dataset.targetPath;
|
const action = menuItem.dataset.action;
|
||||||
const isDir = menu.dataset.targetIsDir === 'true';
|
const menu = document.getElementById('contextMenu');
|
||||||
|
const targetPath = menu.dataset.targetPath;
|
||||||
hideContextMenu();
|
const isDir = menu.dataset.targetIsDir === 'true';
|
||||||
|
|
||||||
if (window.fileTreeActions) {
|
|
||||||
await window.fileTreeActions.execute(action, targetPath, isDir);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Hide on outside click
|
hideContextMenu();
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
if (!e.target.closest('#contextMenu') && !e.target.closest('.tree-node')) {
|
if (window.fileTreeActions) {
|
||||||
|
await window.fileTreeActions.execute(action, targetPath, isDir);
|
||||||
|
}
|
||||||
|
} else if (!e.target.closest('#contextMenu') && !e.target.closest('.tree-node')) {
|
||||||
|
// Hide on outside click
|
||||||
hideContextMenu();
|
hideContextMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -80,7 +80,6 @@
|
|||||||
|
|
||||||
<!-- Preview Pane -->
|
<!-- Preview Pane -->
|
||||||
<div class="col preview-pane" id="previewPane">
|
<div class="col preview-pane" id="previewPane">
|
||||||
<h3>Preview</h3>
|
|
||||||
<div id="preview">
|
<div id="preview">
|
||||||
<p class="text-muted">Start typing in the editor to see the preview</p>
|
<p class="text-muted">Start typing in the editor to see the preview</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,13 +157,13 @@
|
|||||||
<!-- Mermaid for diagrams -->
|
<!-- Mermaid for diagrams -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
||||||
|
|
||||||
<script src="/static/js/file-tree-actions.js"></script>
|
<script src="/static/js/webdav-client.js" defer></script>
|
||||||
<script src="/static/js/webdav-client.js"></script>
|
<script src="/static/js/file-tree.js" defer></script>
|
||||||
<script src="/static/js/file-tree.js"></script>
|
<script src="/static/js/editor.js" defer></script>
|
||||||
<script src="/static/js/editor.js"></script>
|
<script src="/static/js/ui-utils.js" defer></script>
|
||||||
<script src="/static/js/ui-utils.js"></script>
|
<script src="/static/js/file-tree-actions.js" defer></script>
|
||||||
<script src="/static/js/app.js"></script>
|
<script src="/static/js/column-resizer.js" defer></script>
|
||||||
<script src="/static/js/column-resizer.js"></script>
|
<script src="/static/js/app.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user