From 90048fcd22df9db7b261ee2e1720bb9d40c2c217 Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Thu, 11 Dec 2025 08:28:28 +0100 Subject: [PATCH 1/3] manual parsing of date to avoid UTC issues --- frontend/app.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/app.js b/frontend/app.js index 308924e..8a09198 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -3419,7 +3419,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', From 2f963edebe8a2951314a340dc352a03a27baca06 Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Thu, 11 Dec 2025 11:00:21 +0100 Subject: [PATCH 2/3] fixed bug renaming note will ovewrite if note has same name of existing --- backend/utils.py | 4 ++++ frontend/app.js | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/backend/utils.py b/backend/utils.py index 4e8e2b4..b1eaf39 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -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: diff --git a/frontend/app.js b/frontend/app.js index 8a09198..531550a 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -2615,6 +2615,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}`, { From 214ffa6d36363a4f2da6b2ba42273aaf2bb1ede7 Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Thu, 11 Dec 2025 11:39:12 +0100 Subject: [PATCH 3/3] use _attachemnts path for images. export as embedded base64 --- frontend/app.js | 55 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index 531550a..92cb3a4 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -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); @@ -2922,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 @@ -3730,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';