@@ -221,6 +221,11 @@ let zoomLevel = 100; // Percentage (100 = default)
221221// Word wrap setting
222222let 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
225230function 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+
35703675function 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 ) => {
0 commit comments