Skip to content

Commit f77126f

Browse files
ozgesolidkeyclaude
andcommitted
Add markdown preview for .md files
- Render markdown files as formatted HTML by default - Toggle between preview and raw view with Wrap/Raw button - Add marked.js library for markdown parsing - Styled preview with headings, code blocks, tables, links - Update file dialog to show "All Supported" by default including .md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 13bb8e8 commit f77126f

6 files changed

Lines changed: 258 additions & 3 deletions

File tree

package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"postinstall": "npx @electron/rebuild",
88
"build": "tsc && npm run copy-assets",
9-
"copy-assets": "cp src/renderer/index.html src/renderer/styles.css dist/renderer/ && cp -r src/renderer/assets dist/renderer/ && mkdir -p dist/renderer/lib && cp node_modules/xterm/css/xterm.css dist/renderer/lib/ && cp node_modules/xterm/lib/xterm.js dist/renderer/lib/ && cp node_modules/xterm-addon-fit/lib/xterm-addon-fit.js dist/renderer/lib/",
9+
"copy-assets": "cp src/renderer/index.html src/renderer/styles.css dist/renderer/ && cp -r src/renderer/assets dist/renderer/ && mkdir -p dist/renderer/lib && cp node_modules/xterm/css/xterm.css dist/renderer/lib/ && cp node_modules/xterm/lib/xterm.js dist/renderer/lib/ && cp node_modules/xterm-addon-fit/lib/xterm-addon-fit.js dist/renderer/lib/ && cp node_modules/marked/lib/marked.umd.js dist/renderer/lib/",
1010
"start": "npm run build && electron .",
1111
"dev": "npm run build && electron .",
1212
"watch": "tsc -w",
@@ -33,6 +33,7 @@
3333
"typescript": "^5.3.0"
3434
},
3535
"dependencies": {
36+
"marked": "^17.0.1",
3637
"node-pty": "^1.1.0",
3738
"xterm": "^5.3.0",
3839
"xterm-addon-fit": "^0.8.0"

src/main/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,9 @@ ipcMain.handle(IPC.OPEN_FILE_DIALOG, async () => {
281281
const result = await dialog.showOpenDialog(mainWindow!, {
282282
properties: ['openFile'],
283283
filters: [
284+
{ name: 'All Supported', extensions: ['log', 'txt', 'out', 'err', 'md', 'markdown', 'json', 'xml', 'yaml', 'yml', 'csv', 'ini', 'conf', 'cfg'] },
284285
{ name: 'Log Files', extensions: ['log', 'txt', 'out', 'err'] },
285-
{ name: 'Text Files', extensions: ['md', 'markdown', 'rst', 'text'] },
286+
{ name: 'Markdown', extensions: ['md', 'markdown'] },
286287
{ name: 'Data Files', extensions: ['json', 'xml', 'yaml', 'yml', 'csv', 'tsv', 'toml'] },
287288
{ name: 'Config Files', extensions: ['ini', 'conf', 'cfg', 'config', 'properties', 'env'] },
288289
{ name: 'All Files', extensions: ['*'] },

src/renderer/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<link rel="stylesheet" href="lib/xterm.css">
1010
<script src="lib/xterm.js"></script>
1111
<script src="lib/xterm-addon-fit.js"></script>
12+
<script src="lib/marked.umd.js"></script>
1213
</head>
1314
<body>
1415
<div id="app">
@@ -180,6 +181,8 @@
180181
<!-- Log Viewer -->
181182
<main class="log-viewer">
182183
<div id="editor-container" class="editor-container">
184+
<!-- Markdown Preview -->
185+
<div id="markdown-preview" class="markdown-preview hidden"></div>
183186
<div id="welcome-message" class="welcome-message">
184187
<img src="assets/logo.png" alt="LOGAN" class="welcome-logo">
185188
<h2>LOGAN</h2>

src/renderer/renderer.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,11 @@ let zoomLevel = 100; // Percentage (100 = default)
221221
// Word wrap setting
222222
let wordWrapEnabled = false;
223223

224+
// Markdown preview
225+
let isMarkdownFile = false;
226+
let markdownPreviewMode = true; // Start in preview mode for md files
227+
declare const marked: { parse: (text: string) => string };
228+
224229
// Get current line height based on zoom
225230
function getLineHeight(): number {
226231
return Math.round(BASE_LINE_HEIGHT * (zoomLevel / 100));
@@ -258,6 +263,7 @@ const elements = {
258263
sidebar: document.getElementById('sidebar') as HTMLElement,
259264
editorContainer: document.getElementById('editor-container') as HTMLDivElement,
260265
welcomeMessage: document.getElementById('welcome-message') as HTMLDivElement,
266+
markdownPreview: document.getElementById('markdown-preview') as HTMLDivElement,
261267
foldersList: document.getElementById('folders-list') as HTMLDivElement,
262268
btnAddFolder: document.getElementById('btn-add-folder') as HTMLButtonElement,
263269
folderSearchInput: document.getElementById('folder-search-input') as HTMLInputElement,
@@ -2431,6 +2437,27 @@ async function loadFile(filePath: string, createNewTab: boolean = true): Promise
24312437
updateStatusBar();
24322438
updateSplitNavigation();
24332439

2440+
// Check if this is a markdown file
2441+
isMarkdownFile = isMarkdownExtension(filePath);
2442+
if (isMarkdownFile) {
2443+
// Show markdown preview by default
2444+
markdownPreviewMode = true;
2445+
elements.btnWordWrap.textContent = 'Raw';
2446+
elements.btnWordWrap.title = 'Show raw markdown';
2447+
await renderMarkdownPreview();
2448+
showMarkdownPreview();
2449+
} else {
2450+
// Reset for non-markdown files
2451+
markdownPreviewMode = false;
2452+
elements.markdownPreview.classList.add('hidden');
2453+
elements.btnWordWrap.textContent = 'Wrap';
2454+
elements.btnWordWrap.title = 'Toggle word wrap (⌥Z)';
2455+
const wrapper = document.querySelector('.log-viewer-wrapper') as HTMLElement;
2456+
if (wrapper) {
2457+
wrapper.style.display = '';
2458+
}
2459+
}
2460+
24342461
// Build minimap with progress
24352462
unsubscribe(); // Stop listening to indexing progress
24362463
showProgress('Building minimap...');
@@ -3567,6 +3594,84 @@ function toggleWordWrap(): void {
35673594
}
35683595
}
35693596

3597+
function isMarkdownExtension(filePath: string): boolean {
3598+
const ext = filePath.toLowerCase().split('.').pop();
3599+
return ext === 'md' || ext === 'markdown';
3600+
}
3601+
3602+
async function renderMarkdownPreview(): Promise<void> {
3603+
if (!state.filePath || !isMarkdownFile) return;
3604+
3605+
try {
3606+
// Fetch all content for markdown preview
3607+
const result = await window.api.getLines(0, state.totalLines);
3608+
if (result.success && result.lines) {
3609+
const content = result.lines.map(l => l.text).join('\n');
3610+
const html = marked.parse(content);
3611+
elements.markdownPreview.innerHTML = html;
3612+
3613+
// Make links open in external browser
3614+
elements.markdownPreview.querySelectorAll('a').forEach(link => {
3615+
link.addEventListener('click', (e) => {
3616+
e.preventDefault();
3617+
const href = link.getAttribute('href');
3618+
if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
3619+
window.api.openExternalUrl?.(href);
3620+
}
3621+
});
3622+
});
3623+
}
3624+
} catch (error) {
3625+
elements.markdownPreview.innerHTML = '<p>Error rendering markdown</p>';
3626+
}
3627+
}
3628+
3629+
function showMarkdownPreview(): void {
3630+
if (!isMarkdownFile) return;
3631+
3632+
markdownPreviewMode = true;
3633+
elements.markdownPreview.classList.remove('hidden');
3634+
3635+
// Hide log viewer wrapper if it exists
3636+
const wrapper = document.querySelector('.log-viewer-wrapper') as HTMLElement;
3637+
if (wrapper) {
3638+
wrapper.style.display = 'none';
3639+
}
3640+
3641+
// Update button state
3642+
elements.btnWordWrap.textContent = 'Raw';
3643+
elements.btnWordWrap.title = 'Show raw markdown';
3644+
}
3645+
3646+
function showMarkdownRaw(): void {
3647+
markdownPreviewMode = false;
3648+
elements.markdownPreview.classList.add('hidden');
3649+
3650+
// Show log viewer wrapper
3651+
const wrapper = document.querySelector('.log-viewer-wrapper') as HTMLElement;
3652+
if (wrapper) {
3653+
wrapper.style.display = '';
3654+
}
3655+
3656+
// Update button state
3657+
elements.btnWordWrap.textContent = 'Preview';
3658+
elements.btnWordWrap.title = 'Show markdown preview';
3659+
}
3660+
3661+
function toggleMarkdownPreview(): void {
3662+
if (!isMarkdownFile) {
3663+
toggleWordWrap();
3664+
return;
3665+
}
3666+
3667+
if (markdownPreviewMode) {
3668+
showMarkdownRaw();
3669+
} else {
3670+
showMarkdownPreview();
3671+
renderMarkdownPreview();
3672+
}
3673+
}
3674+
35703675
function applyZoom(): void {
35713676
// Update status bar
35723677
elements.statusZoom.textContent = `${zoomLevel}%`;
@@ -4045,7 +4150,7 @@ function init(): void {
40454150
elements.btnColumnsNone.addEventListener('click', () => setAllColumnsVisibility(false));
40464151

40474152
// Word wrap
4048-
elements.btnWordWrap.addEventListener('click', toggleWordWrap);
4153+
elements.btnWordWrap.addEventListener('click', toggleMarkdownPreview);
40494154

40504155
// Split mode and value change handlers
40514156
document.querySelectorAll('input[name="split-mode"]').forEach((radio) => {

src/renderer/styles.css

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1872,3 +1872,134 @@ kbd {
18721872
#section-terminal .section-btn {
18731873
font-size: 14px;
18741874
}
1875+
1876+
/* Markdown Preview */
1877+
.markdown-preview {
1878+
position: absolute;
1879+
top: 0;
1880+
left: 0;
1881+
right: 0;
1882+
bottom: 0;
1883+
overflow: auto;
1884+
padding: 20px 40px;
1885+
background: var(--bg-primary);
1886+
color: var(--text-primary);
1887+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
1888+
font-size: 15px;
1889+
line-height: 1.6;
1890+
}
1891+
1892+
.markdown-preview.hidden {
1893+
display: none;
1894+
}
1895+
1896+
.markdown-preview h1,
1897+
.markdown-preview h2,
1898+
.markdown-preview h3,
1899+
.markdown-preview h4,
1900+
.markdown-preview h5,
1901+
.markdown-preview h6 {
1902+
color: #fff;
1903+
margin-top: 24px;
1904+
margin-bottom: 16px;
1905+
font-weight: 600;
1906+
line-height: 1.25;
1907+
}
1908+
1909+
.markdown-preview h1 {
1910+
font-size: 2em;
1911+
border-bottom: 1px solid var(--border-color);
1912+
padding-bottom: 0.3em;
1913+
}
1914+
1915+
.markdown-preview h2 {
1916+
font-size: 1.5em;
1917+
border-bottom: 1px solid var(--border-color);
1918+
padding-bottom: 0.3em;
1919+
}
1920+
1921+
.markdown-preview h3 {
1922+
font-size: 1.25em;
1923+
}
1924+
1925+
.markdown-preview p {
1926+
margin-bottom: 16px;
1927+
}
1928+
1929+
.markdown-preview a {
1930+
color: var(--info-color);
1931+
text-decoration: none;
1932+
}
1933+
1934+
.markdown-preview a:hover {
1935+
text-decoration: underline;
1936+
}
1937+
1938+
.markdown-preview code {
1939+
background: var(--bg-tertiary);
1940+
padding: 0.2em 0.4em;
1941+
border-radius: 3px;
1942+
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
1943+
font-size: 0.9em;
1944+
}
1945+
1946+
.markdown-preview pre {
1947+
background: var(--bg-tertiary);
1948+
padding: 16px;
1949+
border-radius: 6px;
1950+
overflow-x: auto;
1951+
margin-bottom: 16px;
1952+
}
1953+
1954+
.markdown-preview pre code {
1955+
background: none;
1956+
padding: 0;
1957+
font-size: 0.9em;
1958+
line-height: 1.45;
1959+
}
1960+
1961+
.markdown-preview ul,
1962+
.markdown-preview ol {
1963+
margin-bottom: 16px;
1964+
padding-left: 2em;
1965+
}
1966+
1967+
.markdown-preview li {
1968+
margin-bottom: 4px;
1969+
}
1970+
1971+
.markdown-preview blockquote {
1972+
border-left: 4px solid var(--accent-color);
1973+
padding-left: 16px;
1974+
margin: 0 0 16px 0;
1975+
color: var(--text-secondary);
1976+
}
1977+
1978+
.markdown-preview table {
1979+
border-collapse: collapse;
1980+
margin-bottom: 16px;
1981+
width: 100%;
1982+
}
1983+
1984+
.markdown-preview th,
1985+
.markdown-preview td {
1986+
border: 1px solid var(--border-color);
1987+
padding: 8px 12px;
1988+
text-align: left;
1989+
}
1990+
1991+
.markdown-preview th {
1992+
background: var(--bg-tertiary);
1993+
font-weight: 600;
1994+
}
1995+
1996+
.markdown-preview hr {
1997+
border: none;
1998+
border-top: 1px solid var(--border-color);
1999+
margin: 24px 0;
2000+
}
2001+
2002+
.markdown-preview img {
2003+
max-width: 100%;
2004+
height: auto;
2005+
}

0 commit comments

Comments
 (0)