@@ -5,7 +5,7 @@ import { CodeJar } from 'codejar';
55import Prism from 'prismjs' ;
66import './prism-custom' ;
77import { applyTermColors , markErrors } from './prism-custom' ;
8- import { exportContent , type ExportFormat , type ColorScheme as ExportColorScheme } from './exporter' ;
8+ import { exportContent , getFileExtension , type ExportFormat , type ColorScheme as ExportColorScheme } from './exporter' ;
99
1010// Equation metadata
1111interface EquationInfo {
@@ -74,6 +74,7 @@ let parsedContent: ParsedContent | null = null;
7474let editor : any = null ;
7575let currentMarkdown = '' ;
7676let previewTimeout : number | null = null ;
77+ let isExportMode = false ;
7778
7879function applyColorScheme ( schemeName : string ) {
7980 const scheme = colorSchemes [ schemeName ] ;
@@ -345,6 +346,11 @@ function initializeEditor() {
345346
346347 // Update preview on change (debounced)
347348 editor . onUpdate ( ( code : string ) => {
349+ // Don't update preview when in export mode
350+ if ( isExportMode ) {
351+ return ;
352+ }
353+
348354 currentMarkdown = code ;
349355
350356 // Clear previous timeout
@@ -433,6 +439,123 @@ function setupEditorControls() {
433439 }
434440}
435441
442+ function setupExportUI ( ) {
443+ const exportBtn = document . getElementById ( 'export-btn' ) ;
444+ const exportMenu = document . getElementById ( 'export-menu' ) ;
445+ const copyBtn = document . getElementById ( 'copy-btn' ) ;
446+ const downloadBtn = document . getElementById ( 'download-btn' ) ;
447+ const editorContainer = document . getElementById ( 'editor-container' ) ;
448+
449+ let currentExport = '' ;
450+ let currentFormat : ExportFormat = 'html' ;
451+
452+ // Toggle export menu
453+ if ( exportBtn && exportMenu ) {
454+ exportBtn . addEventListener ( 'click' , ( e ) => {
455+ e . stopPropagation ( ) ;
456+
457+ if ( isExportMode ) {
458+ // Back to edit mode
459+ isExportMode = false ;
460+ if ( editor ) {
461+ editor . updateCode ( currentMarkdown ) ;
462+ editorContainer ?. classList . remove ( 'export-mode' ) ;
463+ // Make editor editable again
464+ const codeElement = editorContainer ?. querySelector ( 'code' ) ;
465+ if ( codeElement ) {
466+ ( codeElement as HTMLElement ) . contentEditable = 'true' ;
467+ }
468+ }
469+ exportBtn . textContent = 'Export as...' ;
470+ exportMenu . style . display = 'none' ;
471+ } else {
472+ // Toggle menu
473+ const isVisible = exportMenu . style . display === 'block' ;
474+ exportMenu . style . display = isVisible ? 'none' : 'block' ;
475+ }
476+ } ) ;
477+
478+ // Close menu when clicking outside
479+ document . addEventListener ( 'click' , ( ) => {
480+ if ( ! isExportMode ) {
481+ exportMenu . style . display = 'none' ;
482+ }
483+ } ) ;
484+
485+ // Handle format selection
486+ exportMenu . querySelectorAll ( '.export-option' ) . forEach ( ( option ) => {
487+ option . addEventListener ( 'click' , ( e ) => {
488+ e . stopPropagation ( ) ;
489+ const format = ( option as HTMLElement ) . dataset . format as ExportFormat ;
490+
491+ try {
492+ // Generate export
493+ currentFormat = format ;
494+ currentExport = generateExport ( format ) ;
495+
496+ // Show export in editor (read-only)
497+ if ( editor ) {
498+ isExportMode = true ;
499+ editor . updateCode ( currentExport ) ;
500+ editorContainer ?. classList . add ( 'export-mode' ) ;
501+ // Make editor read-only
502+ const codeElement = editorContainer ?. querySelector ( 'code' ) ;
503+ if ( codeElement ) {
504+ ( codeElement as HTMLElement ) . contentEditable = 'false' ;
505+ }
506+ exportBtn . textContent = 'Back to Edit' ;
507+ exportMenu . style . display = 'none' ;
508+ }
509+ } catch ( error ) {
510+ console . error ( 'Export failed:' , error ) ;
511+ alert ( `Export failed: ${ error instanceof Error ? error . message : String ( error ) } ` ) ;
512+ }
513+ } ) ;
514+ } ) ;
515+ }
516+
517+ if ( copyBtn ) {
518+ copyBtn . addEventListener ( 'click' , async ( ) => {
519+ try {
520+ const contentToCopy = isExportMode ? currentExport : currentMarkdown ;
521+ await navigator . clipboard . writeText ( contentToCopy ) ;
522+
523+ const originalText = copyBtn . textContent ;
524+ copyBtn . textContent = 'Copied!' ;
525+ setTimeout ( ( ) => {
526+ copyBtn . textContent = originalText ;
527+ } , 2000 ) ;
528+ } catch ( error ) {
529+ console . error ( 'Copy failed:' , error ) ;
530+ alert ( 'Failed to copy to clipboard' ) ;
531+ }
532+ } ) ;
533+ }
534+
535+ if ( downloadBtn ) {
536+ downloadBtn . addEventListener ( 'click' , ( ) => {
537+ try {
538+ const content = isExportMode ? currentExport : currentMarkdown ;
539+ const extension = isExportMode ? getFileExtension ( currentFormat ) : 'md' ;
540+ const mimeType = isExportMode ? 'text/html' : 'text/markdown' ;
541+
542+ const blob = new Blob ( [ content ] , { type : mimeType } ) ;
543+ const url = URL . createObjectURL ( blob ) ;
544+ const a = document . createElement ( 'a' ) ;
545+ a . href = url ;
546+ a . download = `${ parsedContent ?. title || 'equation' } .${ extension } ` . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 . ] + / g, '-' ) ;
547+ document . body . appendChild ( a ) ;
548+ a . click ( ) ;
549+ document . body . removeChild ( a ) ;
550+ URL . revokeObjectURL ( url ) ;
551+ } catch ( error ) {
552+ console . error ( 'Download failed:' , error ) ;
553+ alert ( 'Failed to download file' ) ;
554+ }
555+ } ) ;
556+ }
557+ }
558+
436559/**
437560 * Generate export for current content
438561 * UI will be added in Commit 6
@@ -466,6 +589,7 @@ document.addEventListener('DOMContentLoaded', async () => {
466589 // Initialize editor
467590 initializeEditor ( ) ;
468591 setupEditorControls ( ) ;
592+ setupExportUI ( ) ;
469593
470594 // Load equation from URL hash or default
471595 const initialEquation = getEquationFromHash ( ) ;
0 commit comments