From 6002070af0c531cd5136af6c2892120aa76c80f4 Mon Sep 17 00:00:00 2001 From: "karthik.bhandary" Date: Mon, 27 Apr 2026 06:55:42 +0000 Subject: [PATCH] ran lint and fmt --- tests/e2e/jupyter-render.test.ts | 4 +- web_src/css/features/jupyter.css | 7 +- .../plugins/frontend-jupyter-notebook.ts | 81 ++++++++++++++----- 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/tests/e2e/jupyter-render.test.ts b/tests/e2e/jupyter-render.test.ts index dfa62ed915..020c2aee04 100644 --- a/tests/e2e/jupyter-render.test.ts +++ b/tests/e2e/jupyter-render.test.ts @@ -9,9 +9,9 @@ test.describe('jupyter notebook rendering', () => { test.beforeAll(async ({request}) => { repoName = `e2e-jupyter-${randomString(8)}`; owner = env.GITEA_TEST_E2E_USER; - + await apiCreateRepo(request, {name: repoName}); - + // Single comprehensive test notebook const notebook = JSON.stringify({ cells: [ diff --git a/web_src/css/features/jupyter.css b/web_src/css/features/jupyter.css index 5762210651..49d2029cb9 100644 --- a/web_src/css/features/jupyter.css +++ b/web_src/css/features/jupyter.css @@ -23,7 +23,7 @@ line-height: 1.6; background: var(--color-body); color: var(--color-text); - margin-left: 90px; + margin-left: 110px; } .jupyter-notebook .cell.markdown h1, @@ -107,7 +107,7 @@ white-space: nowrap; user-select: none; text-align: right; - width: 80px; + width: 100px; flex-shrink: 0; } @@ -116,6 +116,7 @@ background-color: var(--color-code-bg, #f6f8fa); border: 1px solid var(--color-secondary-alpha-20, #d0d7de); border-radius: 4px; + min-height: 40px; } .jupyter-notebook .cell.code .input pre { @@ -139,6 +140,8 @@ flex: 1; background: var(--color-body); color: var(--color-text); + overflow-x: auto; + min-width: 0; } .jupyter-notebook .cell.code .output pre { diff --git a/web_src/js/render/plugins/frontend-jupyter-notebook.ts b/web_src/js/render/plugins/frontend-jupyter-notebook.ts index edaca1263f..d67671d51c 100644 --- a/web_src/js/render/plugins/frontend-jupyter-notebook.ts +++ b/web_src/js/render/plugins/frontend-jupyter-notebook.ts @@ -1,21 +1,58 @@ import type {FrontendRenderFunc} from '../plugin.ts'; import '../../../css/features/jupyter.css'; -// Simple markdown to HTML converter for notebook cells -function renderMarkdown(markdown: string): string { - return markdown +// 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 - .replace(/^### (.*$)/gim, '

$1

') - .replace(/^## (.*$)/gim, '

$1

') - .replace(/^# (.*$)/gim, '

$1

') - // Bold - .replace(/\*\*(.+?)\*\*/g, '$1') - // Italic - .replace(/\*(.+?)\*/g, '$1') - // Inline code - .replace(/`(.+?)`/g, '$1') - // Line breaks - .replace(/\n/g, '
'); + if (line.startsWith('### ')) { + element = document.createElement('h3'); + element.textContent = line.substring(4); + } else if (line.startsWith('## ')) { + element = document.createElement('h2'); + element.textContent = line.substring(3); + } else if (line.startsWith('# ')) { + element = document.createElement('h1'); + element.textContent = line.substring(2); + } else { + element = document.createElement('p'); + // 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(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/); + + for (const part of parts) { + if (part.startsWith('**') && part.endsWith('**')) { + const strong = document.createElement('strong'); + strong.textContent = part.slice(2, -2); + element.append(strong); + } else if (part.startsWith('*') && part.endsWith('*')) { + const em = document.createElement('em'); + em.textContent = part.slice(1, -1); + element.append(em); + } else if (part.startsWith('`') && part.endsWith('`')) { + const code = document.createElement('code'); + code.textContent = part.slice(1, -1); + element.append(code); + } else if (part) { + element.append(document.createTextNode(part)); + } + } } export const frontendRender: FrontendRenderFunc = async (opts) => { @@ -41,7 +78,7 @@ export const frontendRender: FrontendRenderFunc = async (opts) => { const inputDiv = document.createElement('div'); inputDiv.className = 'input markup'; const source = Array.isArray(cell.source) ? cell.source.join('') : (cell.source || ''); - inputDiv.innerHTML = renderMarkdown(source); + inputDiv.append(renderMarkdown(source)); cellDiv.append(inputDiv); } else if (cell.cell_type === 'code') { const inputWrapper = document.createElement('div'); @@ -70,7 +107,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) { @@ -86,14 +123,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%'; @@ -113,10 +150,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 - htmlDiv.querySelectorAll('img').forEach((img) => { + for (const img of htmlDiv.querySelectorAll('img')) { img.style.maxWidth = '100%'; img.style.height = 'auto'; - }); + } wrapperDiv.append(htmlDiv); outputDiv.append(wrapperDiv); } else if (output.data['application/javascript']) { @@ -171,7 +208,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); @@ -202,7 +239,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);