From d0387cd9a0e940674799f32396ca487a2e6ab349 Mon Sep 17 00:00:00 2001 From: sepehr Date: Sat, 2 May 2026 23:41:46 +0200 Subject: [PATCH] fix: extract images from rich text HTML to enable image description quick action --- memento-note/components/note-editor.tsx | 23 +++++++++++++------ .../components/note-inline-editor.tsx | 15 +++++++++--- memento-note/components/note-input.tsx | 11 ++++++--- memento-note/lib/utils.ts | 6 +++++ 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/memento-note/components/note-editor.tsx b/memento-note/components/note-editor.tsx index 014b695..b103d97 100644 --- a/memento-note/components/note-editor.tsx +++ b/memento-note/components/note-editor.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useRef, useEffect } from 'react' +import { useState, useRef, useEffect, useMemo } from 'react' import { Note, CheckItem, NOTE_COLORS, NoteColor, LinkMetadata, NoteType } from '@/lib/types' import { Dialog, @@ -28,7 +28,7 @@ import { RichTextEditor } from '@/components/rich-text-editor' import { X, Plus, Palette, Image as ImageIcon, Bell, Eye, Link as LinkIcon, Sparkles, Maximize2, Copy, Wand2, LogOut } from 'lucide-react' import { updateNote, createNote, cleanupOrphanedImages, leaveSharedNote } from '@/app/actions/notes' import { fetchLinkMetadata } from '@/app/actions/scrape' -import { cn } from '@/lib/utils' +import { cn, extractImagesFromHTML } from '@/lib/utils' import { toast } from 'sonner' import { MarkdownContent } from './markdown-content' import { LabelManager } from './label-manager' @@ -278,12 +278,21 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps) const handleGenerateTitles = async () => { // Combine content and link metadata for AI - const fullContent = [ + const fullContentForAI = [ content, ...links.map(l => `${l.title || ''} ${l.description || ''}`) - ].join(' ').trim() + ] + .join(' ') + .trim() - const wordCount = fullContent.split(/\s+/).filter(word => word.length > 0).length + const allImages = useMemo(() => { + const extracted = noteType === 'richtext' ? extractImagesFromHTML(content) : []; + return Array.from(new Set([...images, ...extracted])); + }, [images, content, noteType]); + + + + const wordCount = fullContentForAI.split(/\s+/).filter(word => word.length > 0).length if (wordCount < 10) { toast.error(t('ai.titleGenerationMinWords', { count: wordCount })) @@ -1006,7 +1015,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps) onClose={() => setAiOpen(false)} noteTitle={title} noteContent={content} - noteImages={images} + noteImages={allImages} onApplyToNote={(newContent) => { setPreviousContentForCopilot(content) setContent(newContent) @@ -1148,4 +1157,4 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps) )} ) -} \ No newline at end of file +} diff --git a/memento-note/components/note-inline-editor.tsx b/memento-note/components/note-inline-editor.tsx index 50bf07d..5cc1233 100644 --- a/memento-note/components/note-inline-editor.tsx +++ b/memento-note/components/note-inline-editor.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect, useRef, useCallback, useTransition } from 'react' +import { useState, useEffect, useRef, useCallback, useTransition, useMemo } from 'react' import { Note, CheckItem, NOTE_COLORS, NoteColor, NoteType } from '@/lib/types' import { Button } from '@/components/ui/button' import { @@ -17,7 +17,7 @@ import { ComparisonModal } from '@/components/comparison-modal' import { NoteTypeSelector } from '@/components/note-type-selector' import { RichTextEditor } from '@/components/rich-text-editor' import { useLanguage } from '@/lib/i18n' -import { cn } from '@/lib/utils' +import { cn, extractImagesFromHTML } from '@/lib/utils' import { updateNote, toggleArchive, @@ -130,6 +130,12 @@ export function NoteInlineEditor({ const [checkItems, setCheckItems] = useState(note.checkItems || []) const [noteType, setNoteType] = useState(note.type) const isMarkdown = noteType === 'markdown' + + const allImages = useMemo(() => { + const extracted = noteType === 'richtext' ? extractImagesFromHTML(content) : []; + return Array.from(new Set([...(note.images || []), ...extracted])); + }, [note.images, content, noteType]); + const [showMarkdownPreview, setShowMarkdownPreview] = useState( defaultPreviewMode && (note.isMarkdown || false) ) @@ -911,7 +917,7 @@ export function NoteInlineEditor({ onClose={() => setAiOpen(false)} noteTitle={title} noteContent={content} - noteImages={note.images || undefined} + noteImages={allImages} onApplyToNote={(newContent) => { setPreviousContent(content) changeContent(newContent) @@ -929,3 +935,6 @@ export function NoteInlineEditor({ ) } + + + diff --git a/memento-note/components/note-input.tsx b/memento-note/components/note-input.tsx index 1df1e27..adba553 100644 --- a/memento-note/components/note-input.tsx +++ b/memento-note/components/note-input.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useRef, useEffect } from 'react' +import { useState, useRef, useEffect, useMemo } from 'react' import { Card } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' @@ -37,7 +37,7 @@ import { DropdownMenuContent, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' -import { cn } from '@/lib/utils' +import { cn, extractImagesFromHTML } from '@/lib/utils' import { toast } from 'sonner' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' import { NoteTypeSelector } from '@/components/note-type-selector' @@ -145,6 +145,11 @@ export function NoteInput({ ...links.map(l => `${l.title || ''} ${l.description || ''}`) ].join(' ').trim(); + const allImages = useMemo(() => { + const extracted = type === 'richtext' ? extractImagesFromHTML(content) : []; + return Array.from(new Set([...images, ...extracted])); + }, [images, content, type]); + // Auto-tagging hook const { suggestions, isAnalyzing } = useAutoTagging({ content: type !== 'checklist' ? fullContentForAI : '', @@ -1036,7 +1041,7 @@ export function NoteInput({ onClose={() => setAiOpen(false)} noteTitle={title} noteContent={content} - noteImages={images} + noteImages={allImages} onApplyToNote={(newContent) => setContent(newContent)} lastActionApplied={false} notebooks={notebooks.map(nb => ({ id: nb.id, name: nb.name }))} diff --git a/memento-note/lib/utils.ts b/memento-note/lib/utils.ts index a47139e..91d046d 100644 --- a/memento-note/lib/utils.ts +++ b/memento-note/lib/utils.ts @@ -6,6 +6,12 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } +export function extractImagesFromHTML(html: string): string[] { + if (!html) return []; + const matches = html.matchAll(/]+src="([^">]+)"/gi); + return Array.from(matches).map(m => m[1]); +} + /** * Deep equality check for two values * More performant than JSON.stringify for comparison