Add autocompletion for wiki links

This commit is contained in:
2025-11-11 21:56:18 +01:00
parent 0579b8d0e5
commit 2e7bd88a57
13 changed files with 538 additions and 107 deletions

View File

@@ -104,6 +104,7 @@ describe('ContentView', () => {
handleContentChange={mockHandleContentChange}
handleSave={mockHandleSave}
handleFileSelect={mockHandleFileSelect}
files={[]}
/>
</TestWrapper>
);
@@ -121,6 +122,7 @@ describe('ContentView', () => {
handleContentChange={mockHandleContentChange}
handleSave={mockHandleSave}
handleFileSelect={mockHandleFileSelect}
files={[]}
/>
</TestWrapper>
);
@@ -138,6 +140,7 @@ describe('ContentView', () => {
handleContentChange={mockHandleContentChange}
handleSave={mockHandleSave}
handleFileSelect={mockHandleFileSelect}
files={[]}
/>
</TestWrapper>
);
@@ -157,6 +160,7 @@ describe('ContentView', () => {
handleContentChange={mockHandleContentChange}
handleSave={mockHandleSave}
handleFileSelect={mockHandleFileSelect}
files={[]}
/>
</TestWrapper>
);
@@ -179,6 +183,7 @@ describe('ContentView', () => {
handleContentChange={mockHandleContentChange}
handleSave={mockHandleSave}
handleFileSelect={mockHandleFileSelect}
files={[]}
/>
</TestWrapper>
);
@@ -208,6 +213,7 @@ describe('ContentView', () => {
handleContentChange={mockHandleContentChange}
handleSave={mockHandleSave}
handleFileSelect={mockHandleFileSelect}
files={[]}
/>
</TestWrapper>
);

View File

@@ -4,6 +4,7 @@ import Editor from './Editor';
import MarkdownPreview from './MarkdownPreview';
import { getFileUrl, isImageFile } from '../../utils/fileHelpers';
import { useWorkspace } from '@/contexts/WorkspaceContext';
import type { FileNode } from '../../types/models';
type ViewTab = 'source' | 'preview';
@@ -14,6 +15,7 @@ interface ContentViewProps {
handleContentChange: (content: string) => void;
handleSave: (filePath: string, content: string) => Promise<boolean>;
handleFileSelect: (filePath: string | null) => Promise<void>;
files: FileNode[];
}
const ContentView: React.FC<ContentViewProps> = ({
@@ -23,6 +25,7 @@ const ContentView: React.FC<ContentViewProps> = ({
handleContentChange,
handleSave,
handleFileSelect,
files,
}) => {
const { currentWorkspace } = useWorkspace();
if (!currentWorkspace) {
@@ -67,6 +70,7 @@ const ContentView: React.FC<ContentViewProps> = ({
handleContentChange={handleContentChange}
handleSave={handleSave}
selectedFile={selectedFile}
files={files}
/>
) : (
<MarkdownPreview content={content} handleFileSelect={handleFileSelect} />

View File

@@ -1,17 +1,22 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useMemo } from 'react';
import { basicSetup } from 'codemirror';
import { EditorState } from '@codemirror/state';
import { EditorView, keymap } from '@codemirror/view';
import { markdown } from '@codemirror/lang-markdown';
import { defaultKeymap } from '@codemirror/commands';
import { oneDark } from '@codemirror/theme-one-dark';
import { autocompletion } from '@codemirror/autocomplete';
import { useWorkspace } from '../../hooks/useWorkspace';
import { createWikiLinkCompletions } from '../../utils/wikiLinkCompletion';
import { flattenFileTree } from '../../utils/fileHelpers';
import type { FileNode } from '../../types/models';
interface EditorProps {
content: string;
handleContentChange: (content: string) => void;
handleSave: (filePath: string, content: string) => Promise<boolean>;
selectedFile: string;
files: FileNode[];
}
const Editor: React.FC<EditorProps> = ({
@@ -19,11 +24,19 @@ const Editor: React.FC<EditorProps> = ({
handleContentChange,
handleSave,
selectedFile,
files,
}) => {
const { colorScheme } = useWorkspace();
const { colorScheme, currentWorkspace } = useWorkspace();
const editorRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
// Flatten file tree for autocompletion, respecting showHiddenFiles setting
const showHiddenFiles = currentWorkspace?.showHiddenFiles || false;
const flatFiles = useMemo(
() => flattenFileTree(files, showHiddenFiles),
[files, showHiddenFiles]
);
useEffect(() => {
const handleEditorSave = (view: EditorView): boolean => {
void handleSave(selectedFile, view.state.doc.toString());
@@ -71,6 +84,12 @@ const Editor: React.FC<EditorProps> = ({
}),
theme,
colorScheme === 'dark' ? oneDark : [],
autocompletion({
override: [createWikiLinkCompletions(flatFiles)],
activateOnTyping: true,
maxRenderedOptions: 10,
closeOnBlur: true,
}),
],
});
@@ -87,7 +106,7 @@ const Editor: React.FC<EditorProps> = ({
};
// TODO: Refactor
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colorScheme, handleContentChange, handleSave, selectedFile]);
}, [colorScheme, handleContentChange, handleSave, selectedFile, flatFiles]);
useEffect(() => {
if (viewRef.current && content !== viewRef.current.state.doc.toString()) {

View File

@@ -53,6 +53,7 @@ const Layout: React.FC = () => {
selectedFile={selectedFile}
handleFileSelect={handleFileSelect}
loadFileList={loadFileList}
files={files}
/>
</Container>
</AppShell.Main>

View File

@@ -131,6 +131,7 @@ describe('MainContent', () => {
selectedFile="docs/guide.md"
handleFileSelect={mockHandleFileSelect}
loadFileList={mockLoadFileList}
files={[]}
/>
</TestWrapper>
);
@@ -156,6 +157,7 @@ describe('MainContent', () => {
selectedFile="test.md"
handleFileSelect={mockHandleFileSelect}
loadFileList={mockLoadFileList}
files={[]}
/>
</TestWrapper>
);
@@ -172,6 +174,7 @@ describe('MainContent', () => {
selectedFile="test.md"
handleFileSelect={mockHandleFileSelect}
loadFileList={mockLoadFileList}
files={[]}
/>
</TestWrapper>
);
@@ -188,6 +191,7 @@ describe('MainContent', () => {
selectedFile={null}
handleFileSelect={mockHandleFileSelect}
loadFileList={mockLoadFileList}
files={[]}
/>
</TestWrapper>
);

View File

@@ -12,6 +12,7 @@ import { useFileContent } from '../../hooks/useFileContent';
import { useFileOperations } from '../../hooks/useFileOperations';
import { useGitOperations } from '../../hooks/useGitOperations';
import { useModalContext } from '../../contexts/ModalContext';
import type { FileNode } from '../../types/models';
type ViewTab = 'source' | 'preview';
@@ -19,12 +20,14 @@ interface MainContentProps {
selectedFile: string | null;
handleFileSelect: (filePath: string | null) => Promise<void>;
loadFileList: () => Promise<void>;
files: FileNode[];
}
const MainContent: React.FC<MainContentProps> = ({
selectedFile,
handleFileSelect,
loadFileList,
files,
}) => {
const [activeTab, setActiveTab] = useState<ViewTab>('source');
const {
@@ -161,6 +164,7 @@ const MainContent: React.FC<MainContentProps> = ({
handleContentChange={handleContentChange}
handleSave={handleSaveFile}
handleFileSelect={handleFileSelect}
files={files}
/>
</Box>
<CreateFileModal onCreateFile={handleCreateFile} />