diff --git a/web_src/js/render/plugins/frontend-jupyter-notebook.ts b/web_src/js/render/plugins/frontend-jupyter-notebook.ts index d67671d51c..4122746484 100644 --- a/web_src/js/render/plugins/frontend-jupyter-notebook.ts +++ b/web_src/js/render/plugins/frontend-jupyter-notebook.ts @@ -4,12 +4,12 @@ import '../../../css/features/jupyter.css'; // Simple markdown to HTML converter for notebook cells using DOM methods function renderMarkdown(markdown: string): HTMLElement { const container = document.createElement('div'); - + // Split by lines and process const lines = markdown.split('\n'); for (const line of lines) { let element: HTMLElement; - + // Headers if (line.startsWith('### ')) { element = document.createElement('h3'); @@ -25,17 +25,17 @@ function renderMarkdown(markdown: string): HTMLElement { // Process inline formatting processInlineFormatting(element, line); } - + container.append(element); } - + return container; } // Process bold, italic, and inline code function processInlineFormatting(element: HTMLElement, text: string) { - const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/); - + const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g); + for (const part of parts) { if (part.startsWith('**') && part.endsWith('**')) { const strong = document.createElement('strong'); @@ -55,6 +55,7 @@ function processInlineFormatting(element: HTMLElement, text: string) { } } + export const frontendRender: FrontendRenderFunc = async (opts) => { try { const notebook = JSON.parse(opts.contentString()); @@ -107,7 +108,7 @@ export const frontendRender: FrontendRenderFunc = async (opts) => { outputWrapper.className = 'output-wrapper'; const hasExecutionResult = cell.outputs.some((o: any) => o.output_type === 'execute_result'); - + const outPrompt = document.createElement('div'); outPrompt.className = 'prompt output-prompt'; if (hasExecutionResult) { @@ -123,14 +124,14 @@ export const frontendRender: FrontendRenderFunc = async (opts) => { if (output.data) { if (output.data['image/png']) { const img = document.createElement('img'); - const imgData = Array.isArray(output.data['image/png']) ? + const imgData = Array.isArray(output.data['image/png']) ? output.data['image/png'].join('') : output.data['image/png']; img.src = `data:image/png;base64,${imgData}`; img.style.maxWidth = '100%'; outputDiv.append(img); } else if (output.data['image/jpeg']) { const img = document.createElement('img'); - const imgData = Array.isArray(output.data['image/jpeg']) ? + const imgData = Array.isArray(output.data['image/jpeg']) ? output.data['image/jpeg'].join('') : output.data['image/jpeg']; img.src = `data:image/jpeg;base64,${imgData}`; img.style.maxWidth = '100%'; @@ -150,10 +151,10 @@ export const frontendRender: FrontendRenderFunc = async (opts) => { output.data['text/html'].join('') : output.data['text/html']; htmlDiv.innerHTML = htmlData; // Ensure images inside HTML outputs are constrained - for (const img of htmlDiv.querySelectorAll('img')) { + htmlDiv.querySelectorAll('img').forEach((img) => { img.style.maxWidth = '100%'; img.style.height = 'auto'; - } + }); wrapperDiv.append(htmlDiv); outputDiv.append(wrapperDiv); } else if (output.data['application/javascript']) { @@ -208,7 +209,7 @@ export const frontendRender: FrontendRenderFunc = async (opts) => { const errorPre = document.createElement('pre'); errorPre.className = 'error-output'; errorPre.style.color = 'var(--color-red)'; - const traceback = Array.isArray(output.traceback) ? output.traceback.join('\n') : + const traceback = Array.isArray(output.traceback) ? output.traceback.join('\n') : (output.ename && output.evalue ? `${output.ename}: ${output.evalue}` : 'Error'); errorPre.textContent = traceback; outputDiv.append(errorPre); @@ -239,7 +240,7 @@ export const frontendRender: FrontendRenderFunc = async (opts) => { const {initMarkupCodeMath} = await import('../../markup/math.ts'); await initMarkupCodeMath(container); - + return true; } catch (error) { console.error('Jupyter notebook rendering failed:', error); @@ -250,7 +251,8 @@ export const frontendRender: FrontendRenderFunc = async (opts) => { errorTitle.textContent = 'Failed to render notebook:'; errorDiv.append(errorTitle); errorDiv.append(document.createElement('br')); - errorDiv.append(document.createTextNode(error.message)); + const errorMessage = error instanceof Error ? error.message : String(error); + errorDiv.append(document.createTextNode(errorMessage)); opts.container.append(errorDiv); return false; }