Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions TEST_MARKDOWN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Test Markdown File

This is a **test markdown file** to verify the new markdown rendering functionality in the file browser.

## Features

- **Bold text** and *italic text*
- `Inline code`
- [Links](https://example.com)

### Code Blocks

```javascript
function greet(name) {
console.log(`Hello, ${name}!`);
}
```

```python
def greet(name):
print(f"Hello, {name}!")
```

### Tables

| Feature | Status | Notes |
|---------|--------|-------|
| Markdown Preview | ✅ | Implemented |
| Split View | ✅ | Implemented |
| Edit Mode | ✅ | Implemented |

### Lists

1. First item
2. Second item
- Nested item
- Another nested item
3. Third item

### Blockquotes

> This is a blockquote
> with multiple lines

### Math (LaTeX)

Inline math: $E = mc^2$

Block math:
$$
\frac{1}{n} \sum_{i=1}^{n} x_i = \bar{x}
$$

### Images

![Sample Image](https://picsum.photos/400/200)

You can also use images with specific dimensions:
![Random Nature Image](https://picsum.photos/seed/nature/600/300)

---

## Summary

This test file demonstrates various markdown features including:
- Text formatting
- Code blocks with syntax highlighting
- Tables
- Lists (ordered and unordered)
- Blockquotes
- Math equations
- Horizontal rules
- Images

All these features should be properly rendered in the markdown preview!
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@siteboon/claude-code-ui",
"version": "1.13.6",
"version": "1.14.0",
"description": "A web-based UI for Claude Code CLI",
"type": "module",
"main": "server/index.js",
Expand Down
21 changes: 20 additions & 1 deletion server/routes/cli-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@ router.get('/codex/status', async (req, res) => {

async function checkClaudeCredentials() {
try {
// First try Claude Code 2.x auth location (.claude.json with oauthAccount)
const claudeJsonPath = path.join(os.homedir(), '.claude', '.claude.json');
try {
const claudeJsonContent = await fs.readFile(claudeJsonPath, 'utf8');
const claudeJson = JSON.parse(claudeJsonContent);

if (claudeJson.oauthAccount && claudeJson.oauthAccount.emailAddress) {
return {
authenticated: true,
email: claudeJson.oauthAccount.emailAddress,
method: 'claude_code_2x'
};
}
} catch (e) {
// .claude.json not found or invalid, try legacy location
}

// Fallback to legacy credentials location (.credentials.json)
const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
const content = await fs.readFile(credPath, 'utf8');
const creds = JSON.parse(content);
Expand All @@ -87,7 +105,8 @@ async function checkClaudeCredentials() {
if (!isExpired) {
return {
authenticated: true,
email: creds.email || creds.user || null
email: creds.email || creds.user || null,
method: 'legacy_oauth'
};
}
}
Expand Down
224 changes: 178 additions & 46 deletions src/components/CodeEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { oneDark } from '@codemirror/theme-one-dark';
import { EditorView, showPanel, ViewPlugin } from '@codemirror/view';
import { unifiedMergeView, getChunks } from '@codemirror/merge';
import { showMinimap } from '@replit/codemirror-minimap';
import { X, Save, Download, Maximize2, Minimize2 } from 'lucide-react';
import { X, Save, Download, Maximize2, Minimize2, Eye, Code, PanelLeftClose, PanelLeft } from 'lucide-react';
import { api } from '../utils/api';
import { Markdown } from '../utils/markdownUtils';

function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded = false, onToggleExpand = null }) {
const [content, setContent] = useState('');
Expand All @@ -38,6 +39,26 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
});
const editorRef = useRef(null);

// Check if this is a markdown file
const isMarkdownFile = useMemo(() => {
const ext = file.name.split('.').pop()?.toLowerCase();
return ext === 'md' || ext === 'markdown';
}, [file.name]);

// Preview mode state (for markdown files)
const [previewMode, setPreviewMode] = useState(() => {
if (!isMarkdownFile) return 'edit';
const savedMode = localStorage.getItem('codeEditorMarkdownMode');
return savedMode || 'split'; // Default to split view for markdown
});

// Save preview mode preference
useEffect(() => {
if (isMarkdownFile) {
localStorage.setItem('codeEditorMarkdownMode', previewMode);
}
}, [previewMode, isMarkdownFile]);

// Create minimap extension with chunk-based gutters
const minimapExtension = useMemo(() => {
if (!file.diffInfo || !showDiff || !minimapEnabled) return [];
Expand Down Expand Up @@ -583,6 +604,48 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
</div>

<div className="flex items-center gap-1 md:gap-2 flex-shrink-0">
{/* Markdown preview mode toggles */}
{isMarkdownFile && (
<div className="flex items-center gap-1 mr-2 border-r border-gray-200 dark:border-gray-700 pr-2">
<button
onClick={() => setPreviewMode('edit')}
className={`px-2 py-1.5 text-sm rounded-md flex items-center gap-1 transition-colors ${
previewMode === 'edit'
? 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
title="Edit mode"
>
<Code className="w-4 h-4" />
<span className="hidden md:inline">Edit</span>
</button>
<button
onClick={() => setPreviewMode('preview')}
className={`px-2 py-1.5 text-sm rounded-md flex items-center gap-1 transition-colors ${
previewMode === 'preview'
? 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
title="Preview mode"
>
<Eye className="w-4 h-4" />
<span className="hidden md:inline">Preview</span>
</button>
<button
onClick={() => setPreviewMode('split')}
className={`px-2 py-1.5 text-sm rounded-md flex items-center gap-1 transition-colors ${
previewMode === 'split'
? 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
title="Split view"
>
<PanelLeft className="w-4 h-4" />
<span className="hidden md:inline">Split</span>
</button>
</div>
)}

<button
onClick={handleDownload}
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
Expand Down Expand Up @@ -635,52 +698,121 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
</div>
</div>

{/* Editor */}
{/* Editor and/or Preview */}
<div className="flex-1 overflow-hidden">
<CodeMirror
ref={editorRef}
value={content}
onChange={setContent}
extensions={[
...getLanguageExtension(file.name),
// Always show the toolbar
...editorToolbarPanel,
// Only show diff-related extensions when diff is enabled
...(file.diffInfo && showDiff && file.diffInfo.old_string !== undefined
? [
unifiedMergeView({
original: file.diffInfo.old_string,
mergeControls: false,
highlightChanges: true,
syntaxHighlightDeletions: false,
gutter: true
// NOTE: NO collapseUnchanged - this shows the full file!
}),
...minimapExtension,
...scrollToFirstChunkExtension
]
: []),
...(wordWrap ? [EditorView.lineWrapping] : [])
]}
theme={isDarkMode ? oneDark : undefined}
height="100%"
style={{
fontSize: `${fontSize}px`,
height: '100%',
}}
basicSetup={{
lineNumbers: showLineNumbers,
foldGutter: true,
dropCursor: false,
allowMultipleSelections: false,
indentOnInput: true,
bracketMatching: true,
closeBrackets: true,
autocompletion: true,
highlightSelectionMatches: true,
searchKeymap: true,
}}
/>
{isMarkdownFile && previewMode === 'preview' ? (
/* Preview only mode */
<div className="h-full overflow-auto p-6 bg-white dark:bg-gray-900">
<div className="max-w-4xl mx-auto">
<Markdown prose={true}>
{content}
</Markdown>
</div>
</div>
) : isMarkdownFile && previewMode === 'split' ? (
/* Split view mode */
<div className="flex h-full">
<div className="flex-1 border-r border-gray-200 dark:border-gray-700 overflow-hidden">
<CodeMirror
ref={editorRef}
value={content}
onChange={setContent}
extensions={[
...getLanguageExtension(file.name),
// Always show the toolbar
...editorToolbarPanel,
// Only show diff-related extensions when diff is enabled
...(file.diffInfo && showDiff && file.diffInfo.old_string !== undefined
? [
unifiedMergeView({
original: file.diffInfo.old_string,
mergeControls: false,
highlightChanges: true,
syntaxHighlightDeletions: false,
gutter: true
// NOTE: NO collapseUnchanged - this shows the full file!
}),
...minimapExtension,
...scrollToFirstChunkExtension
]
: []),
...(wordWrap ? [EditorView.lineWrapping] : [])
]}
theme={isDarkMode ? oneDark : undefined}
height="100%"
style={{
fontSize: `${fontSize}px`,
height: '100%',
}}
basicSetup={{
lineNumbers: showLineNumbers,
foldGutter: true,
dropCursor: false,
allowMultipleSelections: false,
indentOnInput: true,
bracketMatching: true,
closeBrackets: true,
autocompletion: true,
highlightSelectionMatches: true,
searchKeymap: true,
}}
/>
</div>
<div className="flex-1 overflow-auto p-6 bg-white dark:bg-gray-900">
<div className="max-w-4xl mx-auto">
<Markdown prose={true}>
{content}
</Markdown>
</div>
</div>
</div>
) : (
/* Edit only mode (default for non-markdown or when edit mode selected) */
<CodeMirror
ref={editorRef}
value={content}
onChange={setContent}
extensions={[
...getLanguageExtension(file.name),
// Always show the toolbar
...editorToolbarPanel,
// Only show diff-related extensions when diff is enabled
...(file.diffInfo && showDiff && file.diffInfo.old_string !== undefined
? [
unifiedMergeView({
original: file.diffInfo.old_string,
mergeControls: false,
highlightChanges: true,
syntaxHighlightDeletions: false,
gutter: true
// NOTE: NO collapseUnchanged - this shows the full file!
}),
...minimapExtension,
...scrollToFirstChunkExtension
]
: []),
...(wordWrap ? [EditorView.lineWrapping] : [])
]}
theme={isDarkMode ? oneDark : undefined}
height="100%"
style={{
fontSize: `${fontSize}px`,
height: '100%',
}}
basicSetup={{
lineNumbers: showLineNumbers,
foldGutter: true,
dropCursor: false,
allowMultipleSelections: false,
indentOnInput: true,
bracketMatching: true,
closeBrackets: true,
autocompletion: true,
highlightSelectionMatches: true,
searchKeymap: true,
}}
/>
)}
</div>

{/* Footer */}
Expand Down
Loading