fix: images, couverture, slash, agents, wizard, Stripe et admin
- Collage/accès images: ownership path userId + meta legacy, 403 non caché - Couverture note: explicite (choix/aucune), plus d’auto depuis le corps - Éditeur: suppression image TipTap, sync immédiate, toolbar réordonnée - Slash: listes par titre (plus d’index), keywords nettoyés - Agents: nextRun à la réactivation, cron sans stampede null - Wizard: titres courts + mode Expert plus robuste - Stripe checkout hosted fiable; admin métriques live; dark mode IA/settings - Indexation notes courtes + test unit aligné
This commit is contained in:
@@ -332,10 +332,29 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
setLinks(links.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
/** Images extraites du corps (TipTap / markdown) — pas de couverture auto */
|
||||
const contentImages = useMemo(() => {
|
||||
if (isMarkdown) {
|
||||
const urls = new Set<string>()
|
||||
for (const match of content.matchAll(/!\[.*?\]\((.*?)\)/g)) {
|
||||
const src = match[1]?.trim()
|
||||
if (src) urls.add(src)
|
||||
}
|
||||
return Array.from(urls)
|
||||
}
|
||||
return extractImagesFromHTML(content)
|
||||
}, [content, isMarkdown])
|
||||
|
||||
/**
|
||||
* allImages = galerie explicite + corps (pour IA, sélecteur de couverture).
|
||||
* La couverture hero n'utilise PAS allImages[0] automatiquement.
|
||||
*/
|
||||
const allImages = useMemo(() => {
|
||||
const extracted = !isMarkdown ? extractImagesFromHTML(content) : []
|
||||
return Array.from(new Set([...images, ...extracted]))
|
||||
}, [images, content, isMarkdown])
|
||||
return Array.from(new Set([...images, ...contentImages]))
|
||||
}, [images, contentImages])
|
||||
|
||||
/** Couverture explicite = première entrée de note.images (jamais auto depuis le corps) */
|
||||
const coverImage = images[0] ?? null
|
||||
|
||||
const resolveContentForSave = useCallback((): string => {
|
||||
if (!isMarkdown) {
|
||||
@@ -345,27 +364,32 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
return contentRef.current
|
||||
}, [isMarkdown])
|
||||
|
||||
const resolveImagesForSave = useCallback((contentToSave: string): string[] => {
|
||||
// Images présentes dans le contenu de l'éditeur (inline dans le HTML ou Markdown)
|
||||
let contentImages: string[] = []
|
||||
if (contentToSave) {
|
||||
if (!isMarkdown) {
|
||||
contentImages = extractImagesFromHTML(contentToSave)
|
||||
} else {
|
||||
const urls = new Set<string>()
|
||||
const matches = contentToSave.matchAll(/!\[.*?\]\((.*?)\)/g)
|
||||
for (const match of matches) {
|
||||
const src = match[1]?.trim()
|
||||
if (src) urls.add(src)
|
||||
}
|
||||
contentImages = Array.from(urls)
|
||||
}
|
||||
}
|
||||
// Images "standalone" uploadées séparément (hors contenu éditeur), non supprimées
|
||||
const standaloneImages = images.filter(url => !removedImageUrls.includes(url) && !contentImages.includes(url))
|
||||
// Fusion dédupliquée : images du contenu + images standalone conservées
|
||||
return Array.from(new Set([...contentImages, ...standaloneImages]))
|
||||
}, [isMarkdown, images, removedImageUrls])
|
||||
/**
|
||||
* note.images = couverture / galerie **explicite** uniquement.
|
||||
* Ne plus fusionner les images du corps : sinon, coller une image dans l'éditeur
|
||||
* la plaçait sous le titre, et la supprimer du corps la laissait en "standalone" forever.
|
||||
*/
|
||||
const resolveImagesForSave = useCallback((_contentToSave: string): string[] => {
|
||||
return images.filter((url) => !removedImageUrls.includes(url))
|
||||
}, [images, removedImageUrls])
|
||||
|
||||
const setCoverImage = useCallback((url: string) => {
|
||||
setImages((prev) => {
|
||||
const rest = prev.filter((u) => u !== url)
|
||||
return [url, ...rest]
|
||||
})
|
||||
setRemovedImageUrls((prev) => prev.filter((u) => u !== url))
|
||||
setIsDirty(true)
|
||||
}, [])
|
||||
|
||||
const clearCoverImage = useCallback(() => {
|
||||
setImages((prev) => {
|
||||
if (prev.length === 0) return prev
|
||||
setRemovedImageUrls((r) => Array.from(new Set([...r, ...prev])))
|
||||
return []
|
||||
})
|
||||
setIsDirty(true)
|
||||
}, [])
|
||||
|
||||
|
||||
const handleGenerateTitles = async () => {
|
||||
@@ -902,6 +926,8 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
isAnalyzingSuggestions,
|
||||
isMarkdown,
|
||||
allImages,
|
||||
contentImages,
|
||||
coverImage,
|
||||
colorClasses,
|
||||
quotaExceededFeature,
|
||||
autoSaveEnabled,
|
||||
@@ -911,13 +937,14 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
isGeneratingTitles, titleSuggestions, dismissedTitleSuggestions, isReformulating,
|
||||
reformulationModal, previousContentForCopilot, showReminderDialog, currentReminder,
|
||||
showLinkDialog, linkUrl, comparisonNotes, fusionNotes, dismissedTags, filteredSuggestions,
|
||||
isAnalyzingSuggestions, isMarkdown, allImages, colorClasses, quotaExceededFeature, autoSaveEnabled
|
||||
isAnalyzingSuggestions, isMarkdown, allImages, contentImages, coverImage, colorClasses, quotaExceededFeature, autoSaveEnabled
|
||||
])
|
||||
|
||||
const actions: NoteEditorActions = useMemo(() => ({
|
||||
setTitle: (t) => { setTitle(t); setIsDirty(true); setDismissedTitleSuggestions(true) },
|
||||
setDismissedTitleSuggestions,
|
||||
setContent: (c) => { setContent(c); setIsDirty(true) },
|
||||
setContentImmediate: (c) => { setContentImmediate(c); setIsDirty(true) },
|
||||
setCheckItems,
|
||||
handleCheckItem,
|
||||
handleUpdateCheckItem,
|
||||
@@ -930,6 +957,8 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
setImages,
|
||||
handleImageUpload,
|
||||
handleRemoveImage,
|
||||
setCoverImage,
|
||||
clearCoverImage,
|
||||
uploadImageFile,
|
||||
setLinks,
|
||||
handleAddLink,
|
||||
@@ -974,6 +1003,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
setQuotaExceededFeature,
|
||||
toggleAutoSave,
|
||||
}), [
|
||||
setContent, setContentImmediate, setCoverImage, clearCoverImage,
|
||||
handleCheckItem, handleUpdateCheckItem, handleAddCheckItem, handleRemoveCheckItem,
|
||||
handleSelectGhostTag, handleDismissGhostTag, handleRemoveLabel, handleImageUpload,
|
||||
handleRemoveImage, handleAddLink, handleRemoveLink, handleReminderSave,
|
||||
|
||||
Reference in New Issue
Block a user