Handle wiki style links

This commit is contained in:
2024-09-30 19:01:27 +02:00
parent 43d647c9ea
commit b64c13442b
8 changed files with 211 additions and 10 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { GeistProvider, CssBaseline, Page } from '@geist-ui/core';
import { GeistProvider, CssBaseline, Page, useToasts } from '@geist-ui/core';
import Header from './components/Header';
import MainContent from './components/MainContent';
import useFileManagement from './hooks/useFileManagement';
@@ -10,6 +10,7 @@ function App() {
const [themeType, setThemeType] = useState('light');
const [userId, setUserId] = useState(1);
const [settings, setSettings] = useState({ gitEnabled: false });
const { setToast } = useToasts();
useEffect(() => {
const loadUserSettings = async () => {
@@ -36,12 +37,33 @@ function App() {
handleContentChange,
handleSave,
pullLatestChanges,
lookupFileByName,
} = useFileManagement(settings.gitEnabled);
const setTheme = (newTheme) => {
setThemeType(newTheme);
};
const handleLinkClick = async (filename) => {
try {
const filePaths = await lookupFileByName(filename);
if (filePaths.length === 1) {
handleFileSelect(filePaths[0]);
} else if (filePaths.length > 1) {
setFileOptions(filePaths.map((path) => ({ label: path, value: path })));
setFileSelectionModalVisible(true);
} else {
setToast({ text: `File "${filename}" not found`, type: 'error' });
}
} catch (error) {
console.error('Error looking up file:', error);
setToast({
text: 'Failed to lookup file. Please try again.',
type: 'error',
});
}
};
return (
<GeistProvider themeType={themeType}>
<CssBaseline />
@@ -60,6 +82,7 @@ function App() {
onSave={handleSave}
settings={settings}
pullLatestChanges={pullLatestChanges}
onLinkClick={handleLinkClick}
/>
</Page.Content>
</Page>

View File

@@ -19,6 +19,7 @@ import {
saveFileContent,
deleteFile,
getFileUrl,
lookupFileByName,
} from '../services/api';
const isImageFile = (filePath) => {
@@ -37,6 +38,7 @@ const MainContent = ({
onSave,
settings,
pullLatestChanges,
onLinkClick,
}) => {
const [activeTab, setActiveTab] = useState('source');
const { type: themeType } = useTheme();
@@ -212,6 +214,8 @@ const MainContent = ({
<MarkdownPreview
content={content}
baseUrl={window.API_BASE_URL}
onLinkClick={onLinkClick}
lookupFileByName={lookupFileByName}
/>
)}
</div>

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
@@ -6,7 +6,69 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import 'katex/dist/katex.min.css';
const MarkdownPreview = ({ content, baseUrl }) => {
const MarkdownPreview = ({
content,
baseUrl,
onLinkClick,
lookupFileByName,
}) => {
const [processedContent, setProcessedContent] = useState(content);
useEffect(() => {
const processContent = async (rawContent) => {
const regex = /(!?)\[\[(.*?)\]\]/g;
let result = rawContent;
const matches = [...rawContent.matchAll(regex)];
for (const match of matches) {
const [fullMatch, isImage, fileName] = match;
try {
const paths = await lookupFileByName(fileName);
if (paths && paths.length > 0) {
const filePath = paths[0];
if (isImage) {
result = result.replace(
fullMatch,
`![${fileName}](${baseUrl}/files/${filePath})`
);
} else {
// Use a valid URL format that React Markdown will recognize
result = result.replace(
fullMatch,
`[${fileName}](${baseUrl}/internal/${encodeURIComponent(
filePath
)})`
);
}
} else {
// Use a valid URL format for not found links
result = result.replace(
fullMatch,
`[${fileName}](${baseUrl}/notfound/${encodeURIComponent(
fileName
)})`
);
}
} catch (error) {
console.error('Error looking up file:', error);
result = result.replace(
fullMatch,
`[${fileName}](${baseUrl}/notfound/${encodeURIComponent(fileName)})`
);
}
}
return result;
};
processContent(content).then(setProcessedContent);
}, [content, baseUrl, lookupFileByName]);
const handleImageError = (event) => {
console.error('Failed to load image:', event.target.src);
event.target.alt = 'Failed to load image';
};
return (
<div className="markdown-preview">
<ReactMarkdown
@@ -30,17 +92,48 @@ const MarkdownPreview = ({ content, baseUrl }) => {
</code>
);
},
img({ src, alt, ...props }) {
// Check if the src is a relative path
if (src && !src.startsWith('http') && !src.startsWith('data:')) {
// Prepend the baseUrl to create an absolute path
src = `${baseUrl}/files/${src}`;
img: ({ src, alt, ...props }) => (
<img src={src} alt={alt} onError={handleImageError} {...props} />
),
a: ({ href, children }) => {
if (href.startsWith(`${baseUrl}/internal/`)) {
const filePath = decodeURIComponent(
href.replace(`${baseUrl}/internal/`, '')
);
return (
<a
href="#"
onClick={(e) => {
e.preventDefault();
onLinkClick(filePath);
}}
>
{children}
</a>
);
} else if (href.startsWith(`${baseUrl}/notfound/`)) {
const fileName = decodeURIComponent(
href.replace(`${baseUrl}/notfound/`, '')
);
return (
<a
href="#"
style={{ color: 'red', textDecoration: 'underline' }}
onClick={(e) => {
e.preventDefault();
onLinkClick(fileName);
}}
>
{children}
</a>
);
}
return <img src={src} alt={alt} {...props} />;
// Regular markdown link
return <a href={href}>{children}</a>;
},
}}
>
{content}
{processedContent}
</ReactMarkdown>
</div>
);

View File

@@ -5,6 +5,7 @@ import {
fetchFileContent,
saveFileContent,
pullChanges,
lookupFileByName,
} from '../services/api';
const DEFAULT_FILE = {
@@ -134,6 +135,7 @@ const useFileManagement = (gitEnabled = false) => {
handleContentChange,
handleSave,
pullLatestChanges,
lookupFileByName,
};
};

View File

@@ -131,3 +131,19 @@ export const commitAndPush = async (message) => {
export const getFileUrl = (filePath) => {
return `${API_BASE_URL}/files/${filePath}`;
};
export const lookupFileByName = async (filename) => {
try {
const response = await fetch(
`${API_BASE_URL}/files/lookup?filename=${encodeURIComponent(filename)}`
);
if (!response.ok) {
throw new Error('File not found');
}
const data = await response.json();
return data.paths;
} catch (error) {
console.error('Error looking up file:', error);
throw error;
}
};