diff --git a/src/main/engine/MediaEngine.ts b/src/main/engine/MediaEngine.ts index b27a888..a2cf048 100644 --- a/src/main/engine/MediaEngine.ts +++ b/src/main/engine/MediaEngine.ts @@ -958,6 +958,7 @@ export class MediaEngine extends EventEmitter { size: stats.size, width: metadata.width, height: metadata.height, + title: metadata.title, alt: metadata.alt, caption: metadata.caption, filePath: mediaFilePath, @@ -973,6 +974,7 @@ export class MediaEngine extends EventEmitter { id: metadata.id, projectId: this.currentProjectId, originalName: metadata.originalName, + title: metadata.title, alt: metadata.alt, caption: metadata.caption, tags: metadata.tags, diff --git a/src/renderer/components/Editor/Editor.tsx b/src/renderer/components/Editor/Editor.tsx index d939103..698cc18 100644 --- a/src/renderer/components/Editor/Editor.tsx +++ b/src/renderer/components/Editor/Editor.tsx @@ -18,6 +18,11 @@ import { parseMacros, getMacro } from '../../macros/registry'; import { InsertModal } from '../InsertModal'; import './Editor.css'; +/** Get display name for media: prefer title over originalName */ +function getMediaDisplayName(media: { title?: string; originalName: string }): string { + return media.title || media.originalName; +} + // Module-level AutoSaveManager for idle-time based auto-saving const autoSaveManager = new AutoSaveManager({ idleTimeMs: 3000, // Save after 3 seconds of idle time @@ -207,7 +212,7 @@ const hydrateGalleries = async ( ${link.media.alt || link.media.originalName} `).join(''); @@ -558,7 +563,7 @@ function buildMonthGallery( ${img.alt || img.originalName} `).join('')} @@ -976,7 +981,7 @@ const PostEditor: React.FC = ({ postId }) => { if (mediaItem) { references.push({ id: mediaItem.id, - title: mediaItem.originalName, + title: getMediaDisplayName(mediaItem), type: 'media', }); } @@ -1642,7 +1647,7 @@ const MediaEditor: React.FC<{ mediaId: string }> = ({ mediaId }) => { // Show confirmation modal showConfirmDeleteModal({ itemType: 'media', - itemTitle: item.originalName, + itemTitle: getMediaDisplayName(item), references, onConfirm: async () => { try { @@ -1676,7 +1681,7 @@ const MediaEditor: React.FC<{ mediaId: string }> = ({ mediaId }) => {
- {item.originalName} + {getMediaDisplayName(item)}
diff --git a/tests/engine/MediaEngine.test.ts b/tests/engine/MediaEngine.test.ts index 22c1891..b7f5458 100644 --- a/tests/engine/MediaEngine.test.ts +++ b/tests/engine/MediaEngine.test.ts @@ -774,6 +774,43 @@ linkedPostIds: ["post-a", "post-b", "post-c"]`; expect(postMediaInserts[1].sortOrder).toBe(1); expect(postMediaInserts[2].sortOrder).toBe(2); }); + + it('should restore title from sidecar metadata during rebuild', async () => { + const fs = await import('fs/promises'); + + vi.mocked(fs.readdir).mockImplementation(async (dir: string, options?: any) => { + if (typeof dir === 'string' && dir.includes('media')) { + return [ + { name: 'media-1.jpg', isFile: () => true, isDirectory: () => false }, + { name: 'media-1.jpg.meta', isFile: () => true, isDirectory: () => false }, + ] as any; + } + return []; + }); + + // Sidecar with title field + const sidecarContent = `id: media-1 +originalName: test-image.jpg +mimeType: image/jpeg +size: 1024 +title: My Beautiful Sunset Photo +alt: A sunset over the ocean +caption: Taken during vacation +createdAt: 2024-01-15T10:00:00.000Z +updatedAt: 2024-01-15T10:00:00.000Z +tags: ["nature", "sunset"]`; + mockFiles.set('/mock/userData/projects/test-project/media/media-1.jpg.meta', sidecarContent); + mockFiles.set('/mock/userData/projects/test-project/media/media-1.jpg', Buffer.from('image-data')); + + await mediaEngine.rebuildDatabaseFromFiles(); + + // Verify title is stored in database + const insertedMedia = mockMedia.get('media-1'); + expect(insertedMedia).toBeDefined(); + expect(insertedMedia.title).toBe('My Beautiful Sunset Photo'); + expect(insertedMedia.alt).toBe('A sunset over the ocean'); + expect(insertedMedia.caption).toBe('Taken during vacation'); + }); }); describe('Full-Text Search', () => {