From 33a94d41c2275fbc1ef557685905c0764d987ae4 Mon Sep 17 00:00:00 2001 From: hugo Date: Sat, 14 Feb 2026 22:38:37 +0100 Subject: [PATCH] fix: better hydration and link/unlink handling --- src/renderer/components/Editor/Editor.css | 38 +++++++++++++ src/renderer/components/Editor/Editor.tsx | 69 ++++++++++++++++++----- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/renderer/components/Editor/Editor.css b/src/renderer/components/Editor/Editor.css index 6009096..b0e3c08 100644 --- a/src/renderer/components/Editor/Editor.css +++ b/src/renderer/components/Editor/Editor.css @@ -265,6 +265,44 @@ overflow-y: auto; font-size: 14px; line-height: 1.6; + position: relative; +} + +/* Hydration loading overlay */ +.preview-hydrating-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(30, 30, 30, 0.85); + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + border-radius: 4px; +} + +.preview-hydrating-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + color: var(--vscode-foreground); + font-size: 14px; +} + +.preview-hydrating-spinner { + width: 32px; + height: 32px; + border: 3px solid var(--vscode-panel-border); + border-top-color: var(--vscode-focusBorder); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } } .editor-preview .preview-content { diff --git a/src/renderer/components/Editor/Editor.tsx b/src/renderer/components/Editor/Editor.tsx index da7443f..630a26c 100644 --- a/src/renderer/components/Editor/Editor.tsx +++ b/src/renderer/components/Editor/Editor.tsx @@ -643,6 +643,9 @@ const PostEditor: React.FC = ({ postId }) => { const [availableCategories, setAvailableCategories] = useState(['article', 'picture', 'aside', 'page']); const [isSaving, setIsSaving] = useState(false); const [hasPublishedVersion, setHasPublishedVersion] = useState(false); + const hydrationOverlayRef = useRef(null); + const isHydratingRef = useRef(false); + const previewContentRef = useRef(null); const [editorMode, setEditorMode] = useState(preferredEditorMode); const [lightboxOpen, setLightboxOpen] = useState(false); const [lightboxIndex, setLightboxIndex] = useState(0); @@ -700,23 +703,57 @@ const PostEditor: React.FC = ({ postId }) => { // Hydrate galleries and photo archives when in preview mode useEffect(() => { - if (editorMode !== 'preview' || !previewRef.current) return; + if (editorMode !== 'preview' || !previewRef.current || !previewContentRef.current) return; + + let cancelled = false; + + // Helper to show/hide the overlay without triggering React re-render + const showOverlay = (show: boolean) => { + if (hydrationOverlayRef.current) { + hydrationOverlayRef.current.style.display = show ? 'flex' : 'none'; + } + }; + + // Set content immediately if not hydrating + // During hydration, we skip updating to preserve the hydrated DOM + if (!isHydratingRef.current) { + previewContentRef.current.innerHTML = markdownToHtml(resolvedContent, postId); + } // Small delay to ensure DOM is updated - const timer = setTimeout(() => { - if (previewRef.current) { - const lightboxHandler = (index: number, imgs: { src: string; alt: string }[]) => { - setGalleryImages(imgs); - setLightboxIndex(index); - setLightboxOpen(true); - }; - - hydrateGalleries(previewRef.current, postId, lightboxHandler); - hydratePhotoArchive(previewRef.current, postId, lightboxHandler); + const timer = setTimeout(async () => { + if (cancelled || !previewRef.current) return; + + const lightboxHandler = (index: number, imgs: { src: string; alt: string }[]) => { + setGalleryImages(imgs); + setLightboxIndex(index); + setLightboxOpen(true); + }; + + // Check if there are photo_archive macros that need hydration + const hasPhotoArchives = previewRef.current.querySelectorAll('.macro-photo-archive[data-year], .macro-photo-archive[data-recent]').length > 0; + + if (hasPhotoArchives) { + isHydratingRef.current = true; + showOverlay(true); + } + + try { + await hydrateGalleries(previewRef.current, postId, lightboxHandler); + if (!cancelled) { + await hydratePhotoArchive(previewRef.current, postId, lightboxHandler); + } + } finally { + // Always reset hydration state when complete - the ref is global to the component + isHydratingRef.current = false; + showOverlay(false); } }, 100); - return () => clearTimeout(timer); + return () => { + cancelled = true; + clearTimeout(timer); + }; }, [editorMode, postId, resolvedContent]); // Track latest values for auto-save on unmount/switch @@ -1326,9 +1363,15 @@ const PostEditor: React.FC = ({ postId }) => { {editorMode === 'preview' && (
+
+
+
+ Linking images to post... +
+
)}