Files
nahakubuilde 3e4355d20d Fix issue with picture upload and rendering based on 4 modes how Obsidian can store images.
Add there option to view other files and uplod/download files from main view
2025-06-28 12:41:34 +01:00

262 lines
9.4 KiB
HTML

{% extends 'base.html' %}
{% block title %}{{ app_name }} New Note{% endblock %}
{% block head %}
{% include 'base_css_js_imports.html' %}
{% endblock %}
{% block content %}
<form method="post">
<div class="mb-3 position-relative">
<label for="note-path" class="form-label">Note path, Ending with file name</label>
<div class="input-group">
<input type="text" id="note-path" class="form-control" placeholder="folder/subfolder/title"
autocomplete="off" autocapitalize="off" spellcheck="false"
value="{{ current_folder + '/' if current_folder else '' }}">
<input type="hidden" id="title" name="title">
<input type="hidden" id="path" name="path">
</div>
<div id="folder-suggestions" class="dropdown-menu" style="width: 100%; max-height: 300px; overflow-y: auto;"></div>
<div class="form-text" style="color: aqua;">Type '/' for folder autocomplete. The last segment will be the note title.</div>
</div>
<div class="mb-3"
data-note-dir=""
data-storage-mode="{{ storage_mode }}"
data-storage-base="{{ storage_base }}">
<label for="content" class="form-label">Content</label>
<textarea id="content" name="content" class="form-control" rows="10"></textarea>
</div>
<button type="submit" class="btn btn-primary">Create</button>
<a href="{{ url_for('md_viewer.index') }}" class="btn btn-secondary">Cancel</a>
</form>
{% endblock %}
{% block scripts %}
<script>
window.attatchedImageUrlTemplate = "{{ url_for('md_viewer.serve_attatched_image', image_path='__IMAGE_PATH__') }}";
</script>
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
<script src="{{ url_for('md_viewer.static', filename='js/app_custom_js.js') }}"></script>
<script>
// Make folders data available to JavaScript
window.availableFolders = {{ available_folders|tojson|safe }};
// Initialize highlight.js
window.hljs = hljs;
// Configure highlight.js with secure options
hljs.configure({
ignoreUnescapedHTML: true,
throwUnescapedHTML: false,
languages: [
'python',
'javascript',
'typescript',
'bash',
'shell',
'powershell',
'json',
'yaml',
'markdown',
'css',
'sql',
'xml',
'ini',
'dockerfile'
]
});
const easyMDE = new EasyMDE({
element: document.getElementById('content'),
renderingConfig: {
codeSyntaxHighlighting: true,
markedOptions: {
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(code, { language: lang }).value;
} catch (e) {}
}
try {
return hljs.highlightAuto(code).value;
} catch (e) {}
return code;
}
}
},
uploadImage: true,
imageUploadFunction: async function(file, onSuccess, onError) {
try {
// Get the current path from the path input
const pathInput = document.getElementById('path');
let noteDir = pathInput ? pathInput.value : "";
const formData = new FormData();
formData.append('image', file);
const response = await fetch(`/upload_image/${noteDir}`, {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.error) throw new Error(data.error);
// Use the markdown link returned from the server
const cm = easyMDE.codemirror;
cm.replaceSelection(data.markdown);
// Do NOT call onSuccess here to avoid double insertion
} catch (error) {
onError(error.message || 'Failed to upload image');
}
},
insertTexts: {
image: ["![[", "]]"]
},
previewRender: function(plainText) {
// First, escape the markdown using EasyMDE's default renderer
let preview = this.parent.markdown(plainText);
// After the markdown is rendered, initialize highlighting on any code blocks
setTimeout(() => {
const previewPane = document.querySelector('.editor-preview');
if (previewPane) {
// Get current folder path from the input
const pathInput = document.getElementById('note-path');
const currentPath = pathInput ? pathInput.value : '';
const folderPath = currentPath.split('/').slice(0, -1).join('/');
// Copy storage settings from parent container
const editorContainer = document.querySelector('.mb-3[data-storage-mode]');
if (editorContainer) {
previewPane.setAttribute('data-note-dir', folderPath);
previewPane.setAttribute('data-storage-mode', editorContainer.getAttribute('data-storage-mode'));
previewPane.setAttribute('data-storage-base', editorContainer.getAttribute('data-storage-base'));
}
window.renderObsidianImages(previewPane);
}
document.querySelectorAll('pre code').forEach((block) => {
// Skip if already highlighted
if (block.getAttribute('data-highlighted') === 'yes') {
return;
}
// Get the language if specified in the class
const language = Array.from(block.classList)
.find(cls => cls.startsWith('language-'))
?.substring(9);
try {
// Apply highlighting using the current API
hljs.highlightElement(block);
} catch (e) {
// Silently handle errors without console output
}
});
}, 0);
return preview;
}
});
// GitHub-style path input handler
document.addEventListener('DOMContentLoaded', function() {
const pathInput = document.getElementById('note-path');
const titleInput = document.getElementById('title');
const pathHiddenInput = document.getElementById('path');
const suggestionsDiv = document.getElementById('folder-suggestions');
const folders = availableFolders;
// Focus the path input and move cursor to end
pathInput.focus();
pathInput.setSelectionRange(pathInput.value.length, pathInput.value.length);
let currentPath = '';
let lastKeyWasSlash = false;
function showSuggestions(searchPath) {
const parts = searchPath.split('/');
const searchTerm = parts[parts.length - 1].toLowerCase();
const parentPath = parts.slice(0, -1).join('/');
// Filter folders that match the current path and search term
const matches = folders.filter(folder => {
if (parentPath) {
return folder.path.startsWith(parentPath + '/') &&
folder.name.toLowerCase().includes(searchTerm);
}
return folder.name.toLowerCase().includes(searchTerm);
});
if (matches.length > 0) {
suggestionsDiv.innerHTML = matches.map(folder => `
<div class="folder-suggestion" data-path="${folder.path}">
<i class="fa fa-folder text-warning"></i>
<span>${folder.name}</span>
<span class="folder-path">/${folder.path}</span>
</div>
`).join('');
suggestionsDiv.classList.add('show');
} else {
suggestionsDiv.classList.remove('show');
}
}
function hideSuggestions() {
suggestionsDiv.classList.remove('show');
}
function updateInputs(value) {
const parts = value.split('/');
const title = parts.pop() || '';
const path = parts.join('/');
titleInput.value = title;
pathHiddenInput.value = path;
}
pathInput.addEventListener('input', function(e) {
const value = e.target.value;
updateInputs(value);
if (lastKeyWasSlash || value.includes('/')) {
showSuggestions(value);
} else {
hideSuggestions();
}
lastKeyWasSlash = value.endsWith('/');
});
pathInput.addEventListener('keydown', function(e) {
if (e.key === '/') {
lastKeyWasSlash = true;
} else {
lastKeyWasSlash = false;
}
});
// Handle suggestion clicks
suggestionsDiv.addEventListener('click', function(e) {
const suggestion = e.target.closest('.folder-suggestion');
if (suggestion) {
const folderPath = suggestion.dataset.path;
pathInput.value = folderPath + '/';
updateInputs(pathInput.value);
hideSuggestions();
pathInput.focus();
}
});
// Close suggestions when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('#note-path') && !e.target.closest('#folder-suggestions')) {
hideSuggestions();
}
});
// Handle initial value if any
if (pathInput.value) {
updateInputs(pathInput.value);
}
});
</script>
{% endblock %}