Merge pull request #81 from gamosoft/features/bugfixes
Features/bugfixes
This commit is contained in:
commit
1a5e7d74cb
|
|
@ -86,6 +86,10 @@ def move_note(notes_dir: str, old_path: str, new_path: str) -> bool:
|
||||||
if not old_full_path.exists():
|
if not old_full_path.exists():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Check if target already exists (prevent overwriting)
|
||||||
|
if new_full_path.exists():
|
||||||
|
return False
|
||||||
|
|
||||||
# Invalidate cache for old path
|
# Invalidate cache for old path
|
||||||
old_key = str(old_full_path)
|
old_key = str(old_full_path)
|
||||||
if old_key in _tag_cache:
|
if old_key in _tag_cache:
|
||||||
|
|
|
||||||
|
|
@ -1419,9 +1419,8 @@ function noteApp() {
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
// For images, insert image markdown
|
// For images, insert image markdown
|
||||||
const filename = notePath.split('/').pop().replace(/\.[^/.]+$/, ''); // Remove extension
|
const filename = notePath.split('/').pop().replace(/\.[^/.]+$/, ''); // Remove extension
|
||||||
// URL-encode the path to handle spaces and special characters
|
// Use relative path (not /api/images/) for portability
|
||||||
const encodedPath = notePath.split('/').map(segment => encodeURIComponent(segment)).join('/');
|
link = ``;
|
||||||
link = ``;
|
|
||||||
} else {
|
} else {
|
||||||
// For notes, insert note link
|
// For notes, insert note link
|
||||||
const noteName = notePath.split('/').pop().replace('.md', '');
|
const noteName = notePath.split('/').pop().replace('.md', '');
|
||||||
|
|
@ -1511,9 +1510,8 @@ function noteApp() {
|
||||||
// Insert image markdown at cursor position
|
// Insert image markdown at cursor position
|
||||||
insertImageMarkdown(imagePath, altText, cursorPos) {
|
insertImageMarkdown(imagePath, altText, cursorPos) {
|
||||||
const filename = altText.replace(/\.[^/.]+$/, ''); // Remove extension
|
const filename = altText.replace(/\.[^/.]+$/, ''); // Remove extension
|
||||||
// URL-encode the path to handle spaces and special characters
|
// Use relative path (not /api/images/) for portability
|
||||||
const encodedPath = imagePath.split('/').map(segment => encodeURIComponent(segment)).join('/');
|
const markdown = ``;
|
||||||
const markdown = ``;
|
|
||||||
|
|
||||||
const textBefore = this.noteContent.substring(0, cursorPos);
|
const textBefore = this.noteContent.substring(0, cursorPos);
|
||||||
const textAfter = this.noteContent.substring(cursorPos);
|
const textAfter = this.noteContent.substring(cursorPos);
|
||||||
|
|
@ -2615,6 +2613,15 @@ function noteApp() {
|
||||||
|
|
||||||
if (oldPath === newPath) return;
|
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
|
// Create new note with same content
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/notes/${newPath}`, {
|
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');
|
const images = tempDiv.querySelectorAll('img');
|
||||||
images.forEach(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');
|
const altText = img.getAttribute('alt');
|
||||||
if (altText) {
|
if (altText) {
|
||||||
// Set title attribute to show alt text on hover
|
// Set title attribute to show alt text on hover
|
||||||
|
|
@ -3419,7 +3439,14 @@ function noteApp() {
|
||||||
|
|
||||||
// Format dates nicely
|
// Format dates nicely
|
||||||
if (key === 'date' || key === 'created' || key === 'modified' || key === 'updated') {
|
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())) {
|
if (!isNaN(date.getTime())) {
|
||||||
return date.toLocaleDateString('en-US', {
|
return date.toLocaleDateString('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
|
|
@ -3714,7 +3741,35 @@ function noteApp() {
|
||||||
const noteName = this.currentNoteName || 'note';
|
const noteName = this.currentNoteName || 'note';
|
||||||
|
|
||||||
// Get current rendered HTML (this already has markdown converted and will have LaTeX delimiters)
|
// 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
|
// Get current theme CSS
|
||||||
const currentTheme = this.currentTheme || 'light';
|
const currentTheme = this.currentTheme || 'light';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue