fixde: editor breaking when clicking vim button
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { EditorView, keymap, placeholder as cmPlaceholder } from '@codemirror/view';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { EditorState, Compartment } from '@codemirror/state';
|
||||
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
||||
import { languages } from '@codemirror/language-data';
|
||||
import { defaultKeymap, indentWithTab } from '@codemirror/commands';
|
||||
@@ -18,7 +18,6 @@ interface Props {
|
||||
editSlug?: string;
|
||||
}
|
||||
|
||||
// CodeMirror theme matching the Catppuccin narlblog style
|
||||
const narlblogTheme = EditorView.theme({
|
||||
'&': {
|
||||
backgroundColor: 'var(--crust)',
|
||||
@@ -42,7 +41,6 @@ const narlblogTheme = EditorView.theme({
|
||||
border: 'none',
|
||||
},
|
||||
'.cm-activeLineGutter': { backgroundColor: 'var(--surface0)' },
|
||||
// Search panel styling
|
||||
'.cm-panels': {
|
||||
backgroundColor: 'var(--mantle)',
|
||||
color: 'var(--text)',
|
||||
@@ -50,7 +48,6 @@ const narlblogTheme = EditorView.theme({
|
||||
},
|
||||
'.cm-searchMatch': { backgroundColor: 'var(--yellow)', opacity: '0.3' },
|
||||
'.cm-searchMatch-selected': { backgroundColor: 'var(--peach)', opacity: '0.4' },
|
||||
// Vim cursor styling
|
||||
'.cm-fat-cursor': {
|
||||
backgroundColor: 'var(--mauve) !important',
|
||||
color: 'var(--crust) !important',
|
||||
@@ -98,6 +95,9 @@ function renderMathInElement(element: HTMLElement) {
|
||||
for (let i = 0; i < 3; i++) walk(element);
|
||||
}
|
||||
|
||||
// Compartment for hot-swapping vim mode without recreating the editor
|
||||
const vimCompartment = new Compartment();
|
||||
|
||||
export default function Editor({ editSlug }: Props) {
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
const viewRef = useRef<EditorView | null>(null);
|
||||
@@ -131,7 +131,6 @@ export default function Editor({ editSlug }: Props) {
|
||||
} else {
|
||||
result.then(h => { if (previewRef.current) previewRef.current.innerHTML = h; });
|
||||
}
|
||||
// Enhance preview after render
|
||||
requestAnimationFrame(() => {
|
||||
if (!previewRef.current) return;
|
||||
renderMathInElement(previewRef.current);
|
||||
@@ -141,52 +140,50 @@ export default function Editor({ editSlug }: Props) {
|
||||
});
|
||||
}, [showPreview]);
|
||||
|
||||
function buildExtensions() {
|
||||
const exts = [
|
||||
...(vimEnabled ? [vim()] : []),
|
||||
keymap.of([...defaultKeymap, ...searchKeymap, indentWithTab]),
|
||||
search(),
|
||||
closeBrackets(),
|
||||
markdown({ base: markdownLanguage, codeLanguages: languages }),
|
||||
EditorView.lineWrapping,
|
||||
narlblogTheme,
|
||||
cmPlaceholder('# Hello World\nWrite your markdown here...'),
|
||||
EditorView.updateListener.of(update => {
|
||||
if (!update.docChanged) return;
|
||||
// Debounced preview update
|
||||
if (previewTimerRef.current) clearTimeout(previewTimerRef.current);
|
||||
previewTimerRef.current = setTimeout(updatePreview, 300);
|
||||
// Autocomplete trigger
|
||||
const pos = update.state.selection.main.head;
|
||||
const line = update.state.doc.lineAt(pos);
|
||||
const textBefore = line.text.slice(0, pos - line.from);
|
||||
const lastChar = textBefore.slice(-1);
|
||||
if (lastChar === '/' || lastChar === '!') {
|
||||
triggerAutocomplete(update.view);
|
||||
} else if (lastChar === ' ' || textBefore.length === 0) {
|
||||
setShowAutocomplete(false);
|
||||
}
|
||||
}),
|
||||
];
|
||||
return exts;
|
||||
}
|
||||
|
||||
// Initialize / rebuild CodeMirror when vim mode toggles
|
||||
// Initialize CodeMirror once
|
||||
useEffect(() => {
|
||||
if (!editorRef.current) return;
|
||||
|
||||
// Preserve content if rebuilding
|
||||
const oldContent = viewRef.current?.state.doc.toString() || '';
|
||||
if (viewRef.current) viewRef.current.destroy();
|
||||
if (!editorRef.current || viewRef.current) return;
|
||||
|
||||
const state = EditorState.create({
|
||||
doc: oldContent,
|
||||
extensions: buildExtensions(),
|
||||
doc: '',
|
||||
extensions: [
|
||||
vimCompartment.of(window.innerWidth > 768 ? vim() : []),
|
||||
keymap.of([...defaultKeymap, ...searchKeymap, indentWithTab]),
|
||||
search(),
|
||||
closeBrackets(),
|
||||
markdown({ base: markdownLanguage, codeLanguages: languages }),
|
||||
EditorView.lineWrapping,
|
||||
narlblogTheme,
|
||||
cmPlaceholder('# Hello World\nWrite your markdown here...'),
|
||||
EditorView.updateListener.of(update => {
|
||||
if (!update.docChanged) return;
|
||||
if (previewTimerRef.current) clearTimeout(previewTimerRef.current);
|
||||
previewTimerRef.current = setTimeout(updatePreview, 300);
|
||||
const pos = update.state.selection.main.head;
|
||||
const line = update.state.doc.lineAt(pos);
|
||||
const textBefore = line.text.slice(0, pos - line.from);
|
||||
const lastChar = textBefore.slice(-1);
|
||||
if (lastChar === '/' || lastChar === '!') {
|
||||
triggerAutocomplete(update.view);
|
||||
} else if (lastChar === ' ' || textBefore.length === 0) {
|
||||
setShowAutocomplete(false);
|
||||
}
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const view = new EditorView({ state, parent: editorRef.current });
|
||||
viewRef.current = view;
|
||||
|
||||
return () => { view.destroy(); viewRef.current = null; };
|
||||
}, []);
|
||||
|
||||
// Hot-swap vim mode via compartment reconfiguration
|
||||
useEffect(() => {
|
||||
if (!viewRef.current) return;
|
||||
viewRef.current.dispatch({
|
||||
effects: vimCompartment.reconfigure(vimEnabled ? vim() : []),
|
||||
});
|
||||
}, [vimEnabled]);
|
||||
|
||||
// Load existing post for editing
|
||||
@@ -202,7 +199,6 @@ export default function Editor({ editSlug }: Props) {
|
||||
}).catch(() => showAlertMsg('Failed to load post.', 'error'));
|
||||
}, [editSlug]);
|
||||
|
||||
// Update preview when toggled on
|
||||
useEffect(() => {
|
||||
if (showPreview) updatePreview();
|
||||
}, [showPreview, updatePreview]);
|
||||
@@ -389,10 +385,10 @@ export default function Editor({ editSlug }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Editor + Preview */}
|
||||
<div className={`relative ${showPreview ? 'grid grid-cols-1 md:grid-cols-2 gap-4' : ''}`}>
|
||||
<div className="relative">
|
||||
<div ref={editorRef} className="min-h-[500px]" />
|
||||
{/* Editor + Preview — both columns stretch to the taller one */}
|
||||
<div className={`relative ${showPreview ? 'grid grid-cols-1 md:grid-cols-2 gap-4 items-stretch' : ''}`}>
|
||||
<div className="relative min-h-[500px]" style={showPreview ? { display: 'flex', flexDirection: 'column' } : undefined}>
|
||||
<div ref={editorRef} style={showPreview ? { flex: '1 1 0', minHeight: 0 } : undefined} className={showPreview ? '' : 'min-h-[500px]'} />
|
||||
|
||||
{/* Autocomplete dropdown */}
|
||||
{showAutocomplete && autocompleteAssets.length > 0 && (
|
||||
@@ -427,13 +423,13 @@ export default function Editor({ editSlug }: Props) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Live Preview */}
|
||||
{/* Live Preview — stretches to match editor height */}
|
||||
{showPreview && (
|
||||
<div className="border border-surface1 rounded-xl bg-crust/50 overflow-y-auto max-h-[600px] min-h-[500px]">
|
||||
<div className="border border-surface1 rounded-xl bg-crust/50 overflow-y-auto flex flex-col min-h-[500px]">
|
||||
<div className="sticky top-0 bg-mantle px-4 py-2 text-xs text-subtext0 uppercase border-b border-surface1 z-10">
|
||||
Preview
|
||||
</div>
|
||||
<div ref={previewRef} className="prose max-w-none p-4 md:p-6" />
|
||||
<div ref={previewRef} className="prose max-w-none p-4 md:p-6 flex-1" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -73,7 +73,7 @@ const isAdmin = Astro.cookies.has('admin_token');
|
||||
</div>
|
||||
</header>
|
||||
<div id="post-content" class="prose max-w-none" set:html={html} />
|
||||
<PostEnhancer client:load containerId="post-content" />
|
||||
<PostEnhancer client:only="react" containerId="post-content" />
|
||||
</>
|
||||
)}
|
||||
</article>
|
||||
|
||||
Reference in New Issue
Block a user