fix: better hydration and link/unlink handling
This commit is contained in:
@@ -265,6 +265,44 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
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 {
|
.editor-preview .preview-content {
|
||||||
|
|||||||
@@ -643,6 +643,9 @@ const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
|||||||
const [availableCategories, setAvailableCategories] = useState<string[]>(['article', 'picture', 'aside', 'page']);
|
const [availableCategories, setAvailableCategories] = useState<string[]>(['article', 'picture', 'aside', 'page']);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [hasPublishedVersion, setHasPublishedVersion] = useState(false);
|
const [hasPublishedVersion, setHasPublishedVersion] = useState(false);
|
||||||
|
const hydrationOverlayRef = useRef<HTMLDivElement>(null);
|
||||||
|
const isHydratingRef = useRef(false);
|
||||||
|
const previewContentRef = useRef<HTMLDivElement>(null);
|
||||||
const [editorMode, setEditorMode] = useState<EditorMode>(preferredEditorMode);
|
const [editorMode, setEditorMode] = useState<EditorMode>(preferredEditorMode);
|
||||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||||
@@ -700,23 +703,57 @@ const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
|||||||
|
|
||||||
// Hydrate galleries and photo archives when in preview mode
|
// Hydrate galleries and photo archives when in preview mode
|
||||||
useEffect(() => {
|
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
|
// Small delay to ensure DOM is updated
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(async () => {
|
||||||
if (previewRef.current) {
|
if (cancelled || !previewRef.current) return;
|
||||||
const lightboxHandler = (index: number, imgs: { src: string; alt: string }[]) => {
|
|
||||||
setGalleryImages(imgs);
|
const lightboxHandler = (index: number, imgs: { src: string; alt: string }[]) => {
|
||||||
setLightboxIndex(index);
|
setGalleryImages(imgs);
|
||||||
setLightboxOpen(true);
|
setLightboxIndex(index);
|
||||||
};
|
setLightboxOpen(true);
|
||||||
|
};
|
||||||
hydrateGalleries(previewRef.current, postId, lightboxHandler);
|
|
||||||
hydratePhotoArchive(previewRef.current, postId, lightboxHandler);
|
// 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);
|
}, 100);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
}, [editorMode, postId, resolvedContent]);
|
}, [editorMode, postId, resolvedContent]);
|
||||||
|
|
||||||
// Track latest values for auto-save on unmount/switch
|
// Track latest values for auto-save on unmount/switch
|
||||||
@@ -1326,9 +1363,15 @@ const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
|||||||
|
|
||||||
{editorMode === 'preview' && (
|
{editorMode === 'preview' && (
|
||||||
<div className="editor-preview markdown-body" ref={previewRef}>
|
<div className="editor-preview markdown-body" ref={previewRef}>
|
||||||
|
<div className="preview-hydrating-overlay" ref={hydrationOverlayRef} style={{ display: 'none' }}>
|
||||||
|
<div className="preview-hydrating-content">
|
||||||
|
<div className="preview-hydrating-spinner" />
|
||||||
|
<span>Linking images to post...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="preview-content"
|
className="preview-content"
|
||||||
dangerouslySetInnerHTML={{ __html: markdownToHtml(resolvedContent, postId) }}
|
ref={previewContentRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user