feat: easy post-links via search

This commit is contained in:
2026-02-12 22:29:36 +01:00
parent 91111c7572
commit dc4e8749d7
6 changed files with 472 additions and 49 deletions

View File

@@ -13,8 +13,16 @@ import { TagInput } from '../TagInput';
import { ChatPanel } from '../ChatPanel';
import { AutoSaveManager } from '../../utils';
import { parseMacros, getMacro } from '../../macros/registry';
import { PostSearchModal } from '../PostSearchModal';
import './Editor.css';
interface SearchResult {
id: string;
title: string;
slug: string;
excerpt?: string;
}
// Module-level AutoSaveManager for idle-time based auto-saving
const autoSaveManager = new AutoSaveManager({
idleTimeMs: 3000, // Save after 3 seconds of idle time
@@ -254,6 +262,7 @@ const PostEditor: React.FC<PostEditorProps> = ({ post }) => {
const [lightboxOpen, setLightboxOpen] = useState(false);
const [lightboxIndex, setLightboxIndex] = useState(0);
const [galleryImages, setGalleryImages] = useState<{ src: string; alt: string }[]>([]);
const [showPostSearch, setShowPostSearch] = useState(false);
const editorRef = useRef<unknown>(null);
const previewRef = useRef<HTMLDivElement>(null);
@@ -522,10 +531,45 @@ const PostEditor: React.FC<PostEditorProps> = ({ post }) => {
};
// Handle Monaco editor mount
const handleEditorDidMount = (editor: unknown) => {
const handleEditorDidMount = (editor: unknown, monaco: Monaco) => {
editorRef.current = editor;
const ed = editor as any;
// Add keyboard shortcut and command for inserting post links
ed.addAction({
id: 'editor.action.insertPostLink',
label: 'Insert Link to Post',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK],
run: () => {
setShowPostSearch(true);
}
});
};
// Handle post selection from search modal
const handlePostSelected = useCallback((post: SearchResult) => {
const editor = editorRef.current as any;
if (!editor) return;
const model = editor.getModel();
if (!model) return;
const selection = editor.getSelection();
const selectedText = selection ? model.getValueInRange(selection) : '';
const linkText = selectedText || post.title;
const linkUrl = `/posts/${post.slug}`;
const linkMarkdown = `[${linkText}](${linkUrl})`;
editor.executeEdits('insert-post-link', [{
range: selection || editor.getSelection(),
text: linkMarkdown,
forceMoveMarkers: true
}]);
setShowPostSearch(false);
}, []);
// Configure Monaco before mount to add macro syntax highlighting
const handleEditorWillMount = (monaco: Monaco) => {
// Register a custom language that extends markdown with macro support
@@ -698,8 +742,9 @@ const PostEditor: React.FC<PostEditorProps> = ({ post }) => {
</div>
</div>
<PostLinks
<PostLinks
postId={post.id}
updatedAt={post.updatedAt}
onPostClick={(id) => useAppStore.getState().setSelectedPost(id)}
/>
</div>
@@ -736,7 +781,7 @@ const PostEditor: React.FC<PostEditorProps> = ({ post }) => {
</button>
</div>
{images.length > 0 && (
<button
<button
className="gallery-button"
onClick={() => { setLightboxIndex(0); setLightboxOpen(true); }}
title={`View ${images.length} image(s)`}
@@ -744,6 +789,15 @@ const PostEditor: React.FC<PostEditorProps> = ({ post }) => {
📷 {images.length}
</button>
)}
{editorMode === 'markdown' && (
<button
className="insert-post-link-button"
onClick={() => setShowPostSearch(true)}
title="Link to post (Ctrl+K)"
>
📝
</button>
)}
</div>
{editorMode === 'wysiwyg' && (
@@ -813,6 +867,13 @@ const PostEditor: React.FC<PostEditorProps> = ({ post }) => {
</span>
)}
</div>
{showPostSearch && (
<PostSearchModal
onSelect={handlePostSelected}
onClose={() => setShowPostSearch(false)}
/>
)}
</div>
);
};