From 09b938b02082d285e63a6b104184ae53f463d394 Mon Sep 17 00:00:00 2001 From: Wisdom Nwokocha Date: Sat, 2 Nov 2024 08:49:34 +0100 Subject: [PATCH] Update MarkdownEditor.tsx --- src/components/MarkdownEditor.tsx | 202 ++++++++++-------------------- 1 file changed, 63 insertions(+), 139 deletions(-) diff --git a/src/components/MarkdownEditor.tsx b/src/components/MarkdownEditor.tsx index 1b589bb..464c942 100644 --- a/src/components/MarkdownEditor.tsx +++ b/src/components/MarkdownEditor.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Save, Upload, Youtube, BookOpen } from 'lucide-react'; @@ -22,18 +22,16 @@ interface MarkedOptions { mangle: boolean; } -// Define types interface AlertType { - message: string; - type: 'error' | 'success' | 'info' | 'warning'; - } + message: string; + type: 'error' | 'success' | 'info' | 'warning'; +} - interface KatexOptions { - delimiters: Array<{ left: string; right: string; display: boolean }>; - throwOnError: boolean; - } +interface KatexOptions { + delimiters: Array<{ left: string; right: string; display: boolean }>; + throwOnError: boolean; +} -// Keep existing library imports const scripts = [ 'https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.2/marked.min.js', @@ -60,26 +58,12 @@ declare global { } } -scripts.forEach(src => { - const script = document.createElement('script'); - script.src = src; - script.async = true; - document.head.appendChild(script); -}); - -styles.forEach(href => { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = href; - document.head.appendChild(link); -}); - const MarkdownEditor: React.FC = () => { const [markdown, setMarkdown] = useState(''); const [html, setHtml] = useState(''); const [alert, setAlert] = useState(null); const [librariesLoaded, setLibrariesLoaded] = useState(false); - + const fileInputRef = useRef(null); const initialMarkdown = `# Markdown Editor Examples @@ -146,38 +130,6 @@ $$ \\end{aligned} $$ -### Subscript & Superscript -- Water formula: H~2~O -- Square of x: x^2^ -- Complex: Fe^2+^ + 2OH^-^ → Fe(OH)~2~ - -### Footnotes -Here's a statement that needs a citation[^1]. -Here's another statement[^2]. - -[^1]: This is the first footnote with *formatted* **text**. -[^2]: This is the second footnote. - -### Chemical Equations -$$ -\\ce{CO2 + C -> 2 CO} -$$ - -$$ -\\ce{Zn^2+ + 2OH- -> Zn(OH)2 v} -$$ - -### Mixed Examples -1. Einstein's Field Equations: - $$G_{\\mu\\nu} + \\Lambda g_{\\mu\\nu} = \\frac{8\\pi G}{c^4}T_{\\mu\\nu}$$ - -2. Chemical equilibrium with subscripts and superscripts: - - K~eq~ = [H~3~O^+^][OH^-^] - -3. Footnoted equation[^3] - -[^3]: $E = mc^2$ represents mass-energy equivalence. - ### Task Lists - [x] Basic Markdown - [x] Math Support @@ -188,38 +140,63 @@ $$ > **Note**: You can combine any of these features together! `; - // Keep existing useEffects and functions + // Load scripts and styles useEffect(() => { - const checkLibraries = setInterval(() => { - if (window.marked && window.DOMPurify && window.katex) { - setLibrariesLoaded(true); - clearInterval(checkLibraries); - setMarkdown(initialMarkdown); + // Load scripts + const loadedScripts = scripts.map(src => { + const script = document.createElement('script'); + script.src = src; + script.async = true; + return new Promise((resolve, reject) => { + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); + }); + + // Load styles + styles.forEach(href => { + if (!document.querySelector(`link[href="${href}"]`)) { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = href; + document.head.appendChild(link); } - }, 100); + }); + + // Wait for all scripts to load + Promise.all(loadedScripts) + .then(() => { + const checkLibraries = setInterval(() => { + if (window.marked && window.DOMPurify && window.katex) { + setLibrariesLoaded(true); + clearInterval(checkLibraries); + setMarkdown(initialMarkdown); + } + }, 100); - return () => clearInterval(checkLibraries); - }, []); + return () => clearInterval(checkLibraries); + }) + .catch(error => { + console.error('Error loading libraries:', error); + }); + }, [initialMarkdown]); // Configure marked with custom renderer useEffect(() => { if (librariesLoaded) { const renderer = new window.marked.Renderer(); - /* eslint-disable @typescript-eslint/no-unused-vars */ - // Support subscript and superscript + + /* eslint-disable @typescript-eslint/no-unused-vars */ renderer.text = (text: string): string => { text = text.replace(/~([^~]+)~/g, (_match: string, p1: string) => `${p1}`); text = text.replace(/\^([^\^]+)\^/g, (_match: string, p1: string) => `${p1}`); return text; }; - /* eslint-disable @typescript-eslint/no-unused-vars */ - // Support footnotes - const footnotes: Record = {}; let footnoteIndex = 1; - + renderer.paragraph = (text: string): string => { - // Handle footnote references text = text.replace(/\[\^([^\]]+)\]/g, (_match: string, _id: string) => { const currentIndex = footnoteIndex; footnoteIndex++; @@ -227,53 +204,8 @@ $$ }); return `

${text}

`; }; - - window.marked.setOptions({ - renderer: renderer, - gfm: true, - breaks: true, - tables: true, - smartLists: true, - smartypants: true, - headerIds: true, - mangle: false - }); - } - }, [librariesLoaded]); - - // Convert markdown to HTML with math support - useEffect(() => { - const checkLibraries = setInterval(() => { - if (window.marked && window.DOMPurify && window.katex) { - setLibrariesLoaded(true); - clearInterval(checkLibraries); - setMarkdown(initialMarkdown); - } - }, 100); + /* eslint-enable @typescript-eslint/no-unused-vars */ - return () => clearInterval(checkLibraries); - }, [initialMarkdown]); // Added initialMarkdown to dependencies - - useEffect(() => { - if (librariesLoaded) { - const renderer = new window.marked.Renderer(); - /* eslint-disable @typescript-eslint/no-unused-vars */ - renderer.text = (text: string): string => { - text = text.replace(/~([^~]+)~/g, (unused: string, p1: string) => `${p1}`); - text = text.replace(/\^([^\^]+)\^/g, (unused: string, p1: string) => `${p1}`); - return text; - }; - /* eslint-disable @typescript-eslint/no-unused-vars */ - let footnoteIndex = 1; - renderer.paragraph = (text: string): string => { - text = text.replace(/\[\^([^\]]+)\]/g, (unused: string, id: string) => { - const currentIndex = footnoteIndex; - footnoteIndex++; - return `${currentIndex}`; - }); - return `

${text}

`; - }; - window.marked.setOptions({ renderer: renderer, gfm: true, @@ -287,18 +219,19 @@ $$ } }, [librariesLoaded]); + // Convert markdown to HTML with math support useEffect(() => { if (librariesLoaded && markdown) { try { - const rawHtml = window.marked.parse(markdown); // Changed to const - + const rawHtml = window.marked.parse(markdown); + const cleanHtml = window.DOMPurify.sanitize(rawHtml, { ADD_TAGS: ['iframe', 'sub', 'sup'], ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling'] }); - + setHtml(cleanHtml); - + setTimeout(() => { const preview = document.querySelector('.preview-content') as HTMLElement; if (preview) { @@ -318,7 +251,6 @@ $$ } }, [markdown, librariesLoaded]); - // Keep existing alert and file handling functions const showAlert = (message: string, type: AlertType['type'] = 'info') => { setAlert({ message, type }); setTimeout(() => setAlert(null), 3000); @@ -361,13 +293,6 @@ $$ reader.readAsText(file); }; - if (!librariesLoaded) { - return ( -
-

Loading editor...

-
- ); - } const handleLearnMarkdown = () => { window.open('https://www.youtube.com/watch?v=i-3ifG_ycjw', '_blank'); }; @@ -411,11 +336,11 @@ $$
- @@ -429,7 +354,7 @@ $$