Skip to content

Commit

Permalink
Merge pull request #2 from jordan-dalby/support-more-languages
Browse files Browse the repository at this point in the history
Support more languages
  • Loading branch information
jordan-dalby authored Oct 25, 2024
2 parents 649bc3f + e5bb9de commit 4db98eb
Show file tree
Hide file tree
Showing 16 changed files with 24,666 additions and 5,972 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# db storage directory
data
node_modules

node_modules
dist
5,174 changes: 2,225 additions & 2,949 deletions client/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"client": "file:",
"date-fns": "^4.1.0",
"framer-motion": "^11.11.9",
"lucide-react": "^0.452.0",
Expand Down
20 changes: 19 additions & 1 deletion client/src/components/common/CopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,25 @@ const CopyButton: React.FC<CopyButtonProps> = ({ text }) => {
const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(text);
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
} else {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();

try {
document.execCommand('copy');
} finally {
textArea.remove();
}
}

setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
Expand Down
80 changes: 45 additions & 35 deletions client/src/components/snippets/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { getLanguageForPrism } from '../../utils/languageUtils';
import { normalizeLanguage } from '../../utils/languageUtils';
import CopyButton from '../common/CopyButton';
import { CodeBlockProps } from '../../types/types';

const processCodeForPreview = (code: string, isPreview: boolean, previewLines: number): string => {
if (!isPreview) return code;
const CodeBlock: React.FC<CodeBlockProps> = ({
code,
language = 'plaintext',
isPreview = false,
previewLines = 4
}) => {
const [normalizedLang, setNormalizedLang] = useState('plaintext');
const LINE_HEIGHT = 24;
const visibleHeight = (previewLines + 2) * LINE_HEIGHT;

const lines = code.split('\n');
const displayLines = lines.slice(0, previewLines);

// Ensure we always have exactly previewLines number of lines
while (displayLines.length < previewLines) {
displayLines.push('');
}

return displayLines.join('\n');
};

const CodeBlock: React.FC<CodeBlockProps> = ({ code, language, isPreview = false, previewLines = 4 }) => {
const displayCode = processCodeForPreview(code, isPreview, previewLines);
useEffect(() => {
const normalized = normalizeLanguage(language);
setNormalizedLang(normalized);
}, [language]);

return (
<div className="relative">
Expand All @@ -31,32 +29,44 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ code, language, isPreview = false
color: inherit !important;
}
.syntax-highlighter {
min-height: ${isPreview ? `${previewLines * 1.5}em` : 'auto'} !important;
min-height: ${isPreview ? `${visibleHeight}px` : 'auto'} !important;
}
.syntax-highlighter pre {
min-height: ${isPreview ? `${previewLines * 1.5}em` : 'auto'} !important;
min-height: ${isPreview ? `${visibleHeight}px` : 'auto'} !important;
line-height: ${LINE_HEIGHT}px !important;
}
.syntax-highlighter code {
display: inline-block;
min-height: ${isPreview ? `${previewLines * 1.5}em` : 'auto'} !important;
min-height: ${isPreview ? `${visibleHeight}px` : 'auto'} !important;
line-height: ${LINE_HEIGHT}px !important;
}
`}
</style>
<SyntaxHighlighter
language={getLanguageForPrism(language)}
style={vscDarkPlus}
customStyle={{
padding: '1rem',
borderRadius: '0.5rem',
fontSize: '0.875rem',
lineHeight: '1.5em',
}}
wrapLines={true}
className="syntax-highlighter"
>
{displayCode}
</SyntaxHighlighter>
<CopyButton text={code} />
<div className="relative">
<SyntaxHighlighter
language={normalizedLang}
style={vscDarkPlus}
customStyle={{
padding: '1rem',
borderRadius: '0.5rem',
fontSize: '0.875rem',
lineHeight: `${LINE_HEIGHT}px`,
maxHeight: isPreview ? `${visibleHeight}px` : 'none',
overflow: 'hidden',
}}
wrapLines={true}
className="syntax-highlighter"
>
{code}
</SyntaxHighlighter>
{isPreview && (
<div
className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-gray-900 to-transparent pointer-events-none"
style={{ height: `${LINE_HEIGHT * 2}px` }}
/>
)}
<CopyButton text={code} />
</div>
</div>
);
};
Expand Down
26 changes: 18 additions & 8 deletions client/src/components/snippets/DynamicCodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import React, { useState, useRef, useEffect } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { getLanguageForPrism } from '../../utils/languageUtils';
import { normalizeLanguage } from '../../utils/languageUtils';
import { DynamicCodeEditorProps } from '../../types/types';

const DynamicCodeEditor: React.FC<DynamicCodeEditorProps> = ({ code: initialCode, language, onValueChange, expandable = false }) => {
const DynamicCodeEditor: React.FC<DynamicCodeEditorProps> = ({
code: initialCode,
language = 'plaintext',
onValueChange,
expandable = false
}) => {
const [code, setCode] = useState(initialCode);
const [normalizedLang, setNormalizedLang] = useState('plaintext');
const [editorHeight, setEditorHeight] = useState('400px');
const textareaRef = useRef<HTMLTextAreaElement>(null);
const highlighterRef = useRef<HTMLDivElement>(null);
Expand All @@ -15,6 +21,11 @@ const DynamicCodeEditor: React.FC<DynamicCodeEditorProps> = ({ code: initialCode
adjustHeight();
}, [initialCode]);

useEffect(() => {
const normalized = normalizeLanguage(language);
setNormalizedLang(normalized);
}, [language]);

const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newCode = e.target.value;
setCode(newCode);
Expand All @@ -30,6 +41,10 @@ const DynamicCodeEditor: React.FC<DynamicCodeEditorProps> = ({ code: initialCode
}
};

const processCodeForHighlighter = (code: string): string => {
return code.split('\n').map(line => line || ' ').join('\n');
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Tab') {
e.preventDefault();
Expand Down Expand Up @@ -147,11 +162,6 @@ const DynamicCodeEditor: React.FC<DynamicCodeEditorProps> = ({ code: initialCode
minHeight: '400px',
};

// Ensure empty lines are preserved
const processCodeForHighlighter = (code: string): string => {
return code.split('\n').map(line => line || ' ').join('\n');
};

return (
<div className="relative rounded-md" style={{ backgroundColor: '#1e1e1e', height: editorHeight, minHeight: '400px' }}>
<textarea
Expand Down Expand Up @@ -189,7 +199,7 @@ const DynamicCodeEditor: React.FC<DynamicCodeEditorProps> = ({ code: initialCode
}}
>
<SyntaxHighlighter
language={getLanguageForPrism(language)}
language={normalizedLang}
style={vscDarkPlus}
customStyle={{
margin: 0,
Expand Down
13 changes: 11 additions & 2 deletions client/src/components/snippets/EditSnippetModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Modal from '../common/Modal';
import 'prismjs';
import 'prismjs/components/prism-markup-templating.js';
import 'prismjs/themes/prism.css';
import { getSupportedLanguages } from '../../utils/languageUtils';
import { getSupportedLanguages, getLanguageLabel } from '../../utils/languageUtils';
import DynamicCodeEditor from './DynamicCodeEditor';
import { EditSnippetModalProps } from '../../types/types';

Expand Down Expand Up @@ -134,7 +134,16 @@ const EditSnippetModal: React.FC<EditSnippetModalProps> = ({ isOpen, onClose, on

useEffect(() => {
if (isOpen) {
setSupportedLanguages(getSupportedLanguages());
const languages = getSupportedLanguages().reduce((acc: string[], lang) => {
acc.push(lang.language);
acc.push(...lang.aliases);
return acc;
}, [])
.sort((a, b) => {
return getLanguageLabel(a).localeCompare(getLanguageLabel(b));
});

setSupportedLanguages(languages);

if (snippetToEdit) {
setTitle(snippetToEdit.title?.slice(0, 255) || '');
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/snippets/SnippetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { formatDistanceToNow } from 'date-fns';
import CodeBlock from './CodeBlock';
import DeleteConfirmationModal from './DeleteConfirmationModal';
import { SnippetCardProps } from '../../types/types';
import { getLanguageLabel } from '../../utils/languageUtils';

const SnippetCard: React.FC<SnippetCardProps> = ({
snippet,
Expand Down Expand Up @@ -68,7 +69,7 @@ const SnippetCard: React.FC<SnippetCardProps> = ({
<h3 className={`${compactView ? 'text-lg' : 'text-xl'} font-bold mb-2 text-gray-200 truncate`} title={snippet.title}>
{snippet.title}
</h3>
<p className="text-sm text-gray-400 mb-2 truncate">{snippet.language}</p>
<p className="text-sm text-gray-400 mb-2 truncate">{getLanguageLabel(snippet.language)}</p>
{!compactView && (
<p className="text-sm text-gray-300 mb-2 line-clamp-1 min-h-[1em] break-words">
{snippet.description ? snippet.description : 'No description available'}
Expand Down
10 changes: 4 additions & 6 deletions client/src/components/snippets/SnippetModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ const SnippetModal: React.FC<SnippetModalProps> = ({ snippet, isOpen, onClose })

return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className={`transition-all duration-300 ease-in-out ${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95'}`}>
<h2 className="text-2xl font-bold text-gray-100 mb-2 truncate">{snippet.title}</h2>
<p className="text-gray-400 mb-4 truncate">{snippet.language}</p>
<p className="text-sm text-gray-300 mb-4 break-words">{snippet.description}</p>
<CodeBlock code={snippet.code} language={snippet.language} />
</div>
<h2 className="text-2xl font-bold text-gray-100 mb-2 truncate">{snippet.title}</h2>
<p className="text-gray-400 mb-4 truncate">{snippet.language}</p>
<p className="text-sm text-gray-300 mb-4 break-words">{snippet.description}</p>
<CodeBlock code={snippet.code} language={snippet.language} />
</Modal>
);
};
Expand Down
15 changes: 0 additions & 15 deletions client/src/hooks/useSnippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useState, useEffect, useCallback, useRef } from 'react';
import { fetchSnippets, createSnippet, editSnippet, deleteSnippet } from '../api/snippets';
import { useToast } from '../components/toast/Toast';
import { Snippet } from '../types/types';
import { addCustomLanguage, removeCustomLanguage } from '../utils/languageUtils';

export const useSnippets = () => {
const [snippets, setSnippets] = useState<Snippet[]>([]);
Expand All @@ -18,11 +17,6 @@ export const useSnippets = () => {
const sortedSnippets = fetchedSnippets.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime());
setSnippets(sortedSnippets);

removeCustomLanguage(null);
fetchedSnippets.forEach(snippet => {
addCustomLanguage(snippet.language);
});

if (!hasLoadedRef.current) {
addToast('Snippets loaded successfully', 'success');
hasLoadedRef.current = true;
Expand All @@ -43,7 +37,6 @@ export const useSnippets = () => {
try {
const newSnippet = await createSnippet(snippetData);
setSnippets(prevSnippets => [...prevSnippets, newSnippet]);
addCustomLanguage(newSnippet.language);
addToast('New snippet created successfully', 'success');
return newSnippet;
} catch (error) {
Expand All @@ -59,7 +52,6 @@ export const useSnippets = () => {
setSnippets(prevSnippets =>
prevSnippets.map(s => s.id === updatedSnippet.id ? updatedSnippet : s)
);
addCustomLanguage(updatedSnippet.language);
addToast('Snippet updated successfully', 'success');
return updatedSnippet;
} catch (error) {
Expand All @@ -74,13 +66,6 @@ export const useSnippets = () => {
await deleteSnippet(id);
setSnippets(prevSnippets => {
const updatedSnippets = prevSnippets.filter(snippet => snippet.id !== id);
const deletedSnippet = prevSnippets.find(snippet => snippet.id === id);
if (deletedSnippet) {
const languageStillInUse = updatedSnippets.some(snippet => snippet.language === deletedSnippet.language);
if (!languageStillInUse) {
removeCustomLanguage(deletedSnippet.language);
}
}
return updatedSnippets;
});
addToast('Snippet deleted successfully', 'success');
Expand Down
4 changes: 2 additions & 2 deletions client/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export interface Snippet {

export interface CodeBlockProps {
code: string;
language: string;
language?: string;
isPreview?: boolean;
previewLines?: number;
}
Expand All @@ -94,7 +94,7 @@ export interface Snippet {

export interface DynamicCodeEditorProps {
code: string;
language: string;
language?: string;
onValueChange: (value: string) => void;
expandable?: boolean;
}
Expand Down
Loading

0 comments on commit 4db98eb

Please sign in to comment.