11import katex from 'katex' ;
22import './style.css' ;
33import { loadContent , type ParsedContent } from './parser' ;
4+ import { CodeJar } from 'codejar' ;
5+ import Prism from 'prismjs' ;
6+ import './prism-custom' ;
7+ import { applyTermColors } from './prism-custom' ;
48
59// Equation metadata
610interface EquationInfo {
@@ -65,6 +69,12 @@ const colorSchemes: Record<string, ColorScheme> = {
6569let currentScheme = 'vibrant' ;
6670let parsedContent : ParsedContent | null = null ;
6771
72+ // Editor state
73+ let editor : any = null ;
74+ let isNewEquationMode = false ;
75+ let currentMarkdown = '' ;
76+ let previewTimeout : number | null = null ;
77+
6878function applyColorScheme ( schemeName : string ) {
6979 const scheme = colorSchemes [ schemeName ] ;
7080 if ( ! scheme || ! parsedContent ) return ;
@@ -252,6 +262,11 @@ async function loadEquation(equationId: string, updateHash = true) {
252262 }
253263 } ) ;
254264 }
265+
266+ // Load markdown into editor (for existing equation mode)
267+ if ( ! isNewEquationMode ) {
268+ await loadMarkdownIntoEditor ( `./examples/${ equation . file } ` ) ;
269+ }
255270}
256271
257272// Create equation selector buttons
@@ -285,6 +300,209 @@ window.addEventListener('hashchange', async () => {
285300 await loadEquation ( equationId , false ) ; // Don't update hash again
286301} ) ;
287302
303+ // Editor functions
304+ function highlightEditor ( editorElement : HTMLElement ) {
305+ // Use Prism to highlight
306+ editorElement . innerHTML = Prism . highlight (
307+ editorElement . textContent || '' ,
308+ Prism . languages . eqmd ,
309+ 'eqmd'
310+ ) ;
311+
312+ // Apply term colors if we have parsed content
313+ if ( parsedContent ) {
314+ applyTermColors ( editorElement , parsedContent . termOrder , colorSchemes [ currentScheme ] . colors ) ;
315+ }
316+ }
317+
318+ function initializeEditor ( ) {
319+ const editorContainer = document . getElementById ( 'editor-container' ) ;
320+ if ( ! editorContainer ) return ;
321+
322+ // Create code element for CodeJar
323+ const codeElement = document . createElement ( 'code' ) ;
324+ codeElement . className = 'language-eqmd' ;
325+ editorContainer . appendChild ( codeElement ) ;
326+
327+ // Initialize CodeJar with Prism highlighting
328+ editor = CodeJar ( codeElement , highlightEditor , {
329+ tab : ' ' , // 2 spaces for tab
330+ indentOn : / [ ( { [ ] $ / ,
331+ } ) ;
332+
333+ // Update preview on change (debounced)
334+ editor . onUpdate ( ( code : string ) => {
335+ currentMarkdown = code ;
336+
337+ // Clear previous timeout
338+ if ( previewTimeout !== null ) {
339+ clearTimeout ( previewTimeout ) ;
340+ }
341+
342+ // Debounce: update after 500ms of inactivity
343+ previewTimeout = window . setTimeout ( async ( ) => {
344+ await updatePreview ( ) ;
345+ } , 500 ) ;
346+ } ) ;
347+ }
348+
349+ async function updatePreview ( ) {
350+ if ( ! currentMarkdown . trim ( ) ) return ;
351+
352+ try {
353+ // Parse markdown content
354+ parsedContent = await loadContent ( currentMarkdown , true ) ; // true = from string
355+
356+ // Clear and re-render
357+ const equationContainer = document . getElementById ( 'equation-container' ) ;
358+ const descriptionContainer = document . getElementById ( 'static-description' ) ;
359+ const hoverContainer = document . getElementById ( 'hover-explanation' ) ;
360+
361+ if ( equationContainer ) equationContainer . innerHTML = '' ;
362+ if ( descriptionContainer ) descriptionContainer . innerHTML = '' ;
363+ if ( hoverContainer ) {
364+ hoverContainer . innerHTML = '' ;
365+ hoverContainer . classList . remove ( 'visible' ) ;
366+ }
367+
368+ // Render content
369+ renderEquation ( ) ;
370+ renderDescription ( ) ;
371+
372+ // Apply colors
373+ applyColorScheme ( currentScheme ) ;
374+
375+ // Setup hover effects
376+ setupHoverEffects ( ) ;
377+
378+ // Update editor highlighting with new colors
379+ const codeElement = document . querySelector ( '#editor-container code' ) as HTMLElement ;
380+ if ( codeElement ) {
381+ highlightEditor ( codeElement ) ;
382+ }
383+ } catch ( error ) {
384+ console . error ( 'Failed to parse markdown:' , error ) ;
385+ // Could show error message to user
386+ }
387+ }
388+
389+ async function loadMarkdownIntoEditor ( url : string ) {
390+ try {
391+ const response = await fetch ( url ) ;
392+ const markdown = await response . text ( ) ;
393+ currentMarkdown = markdown ;
394+
395+ if ( editor ) {
396+ editor . updateCode ( markdown ) ;
397+ }
398+ } catch ( error ) {
399+ console . error ( 'Failed to load markdown:' , error ) ;
400+ }
401+ }
402+
403+ function setupEditorControls ( ) {
404+ // Toggle editor collapse/expand
405+ const toggleBtn = document . getElementById ( 'toggle-editor-btn' ) ;
406+ const editorSidebar = document . getElementById ( 'editor-sidebar' ) ;
407+
408+ if ( toggleBtn && editorSidebar ) {
409+ toggleBtn . addEventListener ( 'click' , ( ) => {
410+ editorSidebar . classList . toggle ( 'collapsed' ) ;
411+ } ) ;
412+ }
413+
414+ // New equation mode toggle
415+ const newEquationBtn = document . getElementById ( 'new-equation-btn' ) ;
416+ if ( newEquationBtn && editorSidebar ) {
417+ newEquationBtn . addEventListener ( 'click' , ( ) => {
418+ isNewEquationMode = ! isNewEquationMode ;
419+
420+ if ( isNewEquationMode ) {
421+ // Switch to new mode: show editor, load template
422+ editorSidebar . classList . remove ( 'collapsed' ) ;
423+ newEquationBtn . textContent = 'Exit New Mode' ;
424+
425+ // Load template
426+ const template = `# Equation
427+
428+ $$
429+ \\mark[term1]{E} = \\mark[term2]{m} \\mark[term3]{c^2}
430+ $$
431+
432+ # Description
433+
434+ The famous [mass-energy equivalence]{.term2}: [energy]{.term1} equals [mass]{.term2} times the [speed of light]{.term3} squared.
435+
436+ ## .term1
437+
438+ Energy (E) is the capacity to do work, measured in joules.
439+
440+ ## .term2
441+
442+ Mass (m) is the amount of matter in an object, measured in kilograms.
443+
444+ ## .term3
445+
446+ The speed of light (c) is approximately 299,792,458 meters per second.
447+ ` ;
448+ currentMarkdown = template ;
449+ if ( editor ) {
450+ editor . updateCode ( template ) ;
451+ }
452+ } else {
453+ // Exit new mode: reload current equation
454+ newEquationBtn . textContent = 'New' ;
455+ if ( currentEquationId ) {
456+ loadEquation ( currentEquationId ) ;
457+ }
458+ }
459+ } ) ;
460+ }
461+
462+ // Download markdown
463+ const downloadBtn = document . getElementById ( 'download-btn' ) ;
464+ if ( downloadBtn ) {
465+ downloadBtn . addEventListener ( 'click' , ( ) => {
466+ const markdown = currentMarkdown || '' ;
467+ const blob = new Blob ( [ markdown ] , { type : 'text/markdown' } ) ;
468+ const url = URL . createObjectURL ( blob ) ;
469+ const a = document . createElement ( 'a' ) ;
470+ a . href = url ;
471+ a . download = isNewEquationMode ? 'new-equation.md' : `${ currentEquationId } .md` ;
472+ a . click ( ) ;
473+ URL . revokeObjectURL ( url ) ;
474+ } ) ;
475+ }
476+
477+ // Contribute instructions
478+ const contributeBtn = document . getElementById ( 'contribute-btn' ) ;
479+ if ( contributeBtn ) {
480+ contributeBtn . addEventListener ( 'click' , ( ) => {
481+ showContributeInstructions ( ) ;
482+ } ) ;
483+ }
484+ }
485+
486+ function showContributeInstructions ( ) {
487+ const instructions = `To contribute your equation to the repository:
488+
489+ 1. Download your markdown file using the Download button
490+ 2. Fork the repository: https://github.com/stared/equations-explained-colorfully
491+ 3. Add your .md file to the public/examples/ directory
492+ 4. Add an entry to public/examples/equations.json:
493+ {
494+ "id": "your-equation-id",
495+ "title": "Your Equation Name",
496+ "category": "Field (e.g., Physics, Math)",
497+ "file": "your-file.md"
498+ }
499+ 5. Submit a pull request with your changes
500+
501+ Thank you for contributing!` ;
502+
503+ alert ( instructions ) ;
504+ }
505+
288506// Initialize - load content and render
289507document . addEventListener ( 'DOMContentLoaded' , async ( ) => {
290508 try {
@@ -297,6 +515,10 @@ document.addEventListener('DOMContentLoaded', async () => {
297515 // Create color scheme switcher
298516 createColorSchemeSwitcher ( ) ;
299517
518+ // Initialize editor
519+ initializeEditor ( ) ;
520+ setupEditorControls ( ) ;
521+
300522 // Load equation from URL hash or default
301523 const initialEquation = getEquationFromHash ( ) ;
302524 await loadEquation ( initialEquation ) ;
0 commit comments