Merge pull request #81 from gamosoft/features/bugfixes

Features/bugfixes
This commit is contained in:
Guillermo Villar 2025-12-11 11:58:47 +01:00 committed by GitHub
commit 1a5e7d74cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 68 additions and 9 deletions

View File

@ -86,6 +86,10 @@ def move_note(notes_dir: str, old_path: str, new_path: str) -> bool:
if not old_full_path.exists():
return False
# Check if target already exists (prevent overwriting)
if new_full_path.exists():
return False
# Invalidate cache for old path
old_key = str(old_full_path)
if old_key in _tag_cache:

View File

@ -1419,9 +1419,8 @@ function noteApp() {
if (isImage) {
// For images, insert image markdown
const filename = notePath.split('/').pop().replace(/\.[^/.]+$/, ''); // Remove extension
// URL-encode the path to handle spaces and special characters
const encodedPath = notePath.split('/').map(segment => encodeURIComponent(segment)).join('/');
link = `![${filename}](/api/images/${encodedPath})`;
// Use relative path (not /api/images/) for portability
link = `![${filename}](${notePath})`;
} else {
// For notes, insert note link
const noteName = notePath.split('/').pop().replace('.md', '');
@ -1511,9 +1510,8 @@ function noteApp() {
// Insert image markdown at cursor position
insertImageMarkdown(imagePath, altText, cursorPos) {
const filename = altText.replace(/\.[^/.]+$/, ''); // Remove extension
// URL-encode the path to handle spaces and special characters
const encodedPath = imagePath.split('/').map(segment => encodeURIComponent(segment)).join('/');
const markdown = `![${filename}](/api/images/${encodedPath})`;
// Use relative path (not /api/images/) for portability
const markdown = `![${filename}](${imagePath})`;
const textBefore = this.noteContent.substring(0, cursorPos);
const textAfter = this.noteContent.substring(cursorPos);
@ -2615,6 +2613,15 @@ function noteApp() {
if (oldPath === newPath) return;
// Check if a note with the new name already exists
const existingNote = this.notes.find(n => n.path.toLowerCase() === newPath.toLowerCase());
if (existingNote) {
alert(`A note named "${newName}" already exists in this folder.`);
// Reset the name in the UI
this.currentNoteName = oldPath.split('/').pop().replace('.md', '');
return;
}
// Create new note with same content
try {
const response = await fetch(`/api/notes/${newPath}`, {
@ -2913,9 +2920,22 @@ function noteApp() {
}
});
// Find all images and add title attribute from alt text for hover tooltips
// Find all images and transform paths for display
const images = tempDiv.querySelectorAll('img');
images.forEach(img => {
const src = img.getAttribute('src');
if (src) {
// Transform relative paths to /api/images/ for serving
// Skip external URLs and already-transformed paths
if (!src.startsWith('http://') && !src.startsWith('https://') &&
!src.startsWith('//') && !src.startsWith('/api/images/') &&
!src.startsWith('data:')) {
// URL-encode path segments to handle spaces and special characters
const encodedPath = src.split('/').map(segment => encodeURIComponent(segment)).join('/');
img.setAttribute('src', `/api/images/${encodedPath}`);
}
}
const altText = img.getAttribute('alt');
if (altText) {
// Set title attribute to show alt text on hover
@ -3419,7 +3439,14 @@ function noteApp() {
// Format dates nicely
if (key === 'date' || key === 'created' || key === 'modified' || key === 'updated') {
const date = new Date(value);
let date;
// Parse date-only strings (YYYY-MM-DD) as local dates to avoid timezone issues
if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
const [year, month, day] = value.split('-').map(Number);
date = new Date(year, month - 1, day); // month is 0-indexed
} else {
date = new Date(value);
}
if (!isNaN(date.getTime())) {
return date.toLocaleDateString('en-US', {
year: 'numeric',
@ -3714,7 +3741,35 @@ function noteApp() {
const noteName = this.currentNoteName || 'note';
// Get current rendered HTML (this already has markdown converted and will have LaTeX delimiters)
const renderedHTML = this.renderedMarkdown;
let renderedHTML = this.renderedMarkdown;
// Embed local images as base64 for fully self-contained HTML
const imgRegex = /src="\/api\/images\/([^"]+)"/g;
const imgMatches = [...renderedHTML.matchAll(imgRegex)];
for (const match of imgMatches) {
const encodedPath = match[1];
try {
// Fetch the image
const imgResponse = await fetch(`/api/images/${encodedPath}`);
if (imgResponse.ok) {
const blob = await imgResponse.blob();
// Convert to base64 data URL
const base64 = await new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
// Replace the src with base64 data URL
renderedHTML = renderedHTML.replace(match[0], `src="${base64}"`);
}
} catch (e) {
console.warn(`Failed to embed image: ${encodedPath}`, e);
// Fall back to relative path
const decodedPath = decodeURIComponent(encodedPath);
renderedHTML = renderedHTML.replace(match[0], `src="${decodedPath}"`);
}
}
// Get current theme CSS
const currentTheme = this.currentTheme || 'light';