mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-21 04:30:24 +02:00
fix: Moved from custom to using marked for rendering
This commit is contained in:
parent
d967030ff0
commit
73ffd647cc
@ -52,6 +52,7 @@
|
|||||||
"jquery": "4.0.0",
|
"jquery": "4.0.0",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
"katex": "0.16.45",
|
"katex": "0.16.45",
|
||||||
|
"marked": "18.0.2",
|
||||||
"mermaid": "11.14.0",
|
"mermaid": "11.14.0",
|
||||||
"online-3d-viewer": "0.18.0",
|
"online-3d-viewer": "0.18.0",
|
||||||
"pdfobject": "2.3.1",
|
"pdfobject": "2.3.1",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@ -166,6 +166,9 @@ importers:
|
|||||||
katex:
|
katex:
|
||||||
specifier: 0.16.45
|
specifier: 0.16.45
|
||||||
version: 0.16.45
|
version: 0.16.45
|
||||||
|
marked:
|
||||||
|
specifier: 18.0.2
|
||||||
|
version: 18.0.2
|
||||||
mermaid:
|
mermaid:
|
||||||
specifier: 11.14.0
|
specifier: 11.14.0
|
||||||
version: 11.14.0
|
version: 11.14.0
|
||||||
@ -3113,6 +3116,11 @@ packages:
|
|||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
marked@18.0.2:
|
||||||
|
resolution: {integrity: sha512-NsmlUYBS/Zg57rgDWMYdnre6OTj4e+qq/JS2ot3KrYLSoHLw+sDu0Nm1ZGpRgYAq6c+b1ekaY5NzVchMCQnzcg==}
|
||||||
|
engines: {node: '>= 20'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
marked@4.3.0:
|
marked@4.3.0:
|
||||||
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
|
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
@ -7084,6 +7092,8 @@ snapshots:
|
|||||||
|
|
||||||
marked@16.4.2: {}
|
marked@16.4.2: {}
|
||||||
|
|
||||||
|
marked@18.0.2: {}
|
||||||
|
|
||||||
marked@4.3.0: {}
|
marked@4.3.0: {}
|
||||||
|
|
||||||
material-icon-theme@5.33.1:
|
material-icon-theme@5.33.1:
|
||||||
|
|||||||
@ -15,7 +15,7 @@ test.describe('jupyter notebook rendering', () => {
|
|||||||
// Single comprehensive test notebook
|
// Single comprehensive test notebook
|
||||||
const notebook = JSON.stringify({
|
const notebook = JSON.stringify({
|
||||||
cells: [
|
cells: [
|
||||||
{cell_type: 'markdown', source: ['# Test\n', '**bold**']},
|
{cell_type: 'markdown', source: ['# Header 1\n', '## Header 2\n', '**bold** *italic* `code`\n', '- List item 1\n', '- List item 2\n', '[link](https://example.com)\n', '| Col1 | Col2 |\n', '|------|------|\n', '| A | B |\n', '```python\ncode block\n```\n', '> blockquote\n', '~~strikethrough~~']},
|
||||||
{cell_type: 'code', execution_count: 1, source: ['print("Hello")'], outputs: [{output_type: 'stream', name: 'stdout', text: ['Hello\n']}]},
|
{cell_type: 'code', execution_count: 1, source: ['print("Hello")'], outputs: [{output_type: 'stream', name: 'stdout', text: ['Hello\n']}]},
|
||||||
{cell_type: 'code', execution_count: 2, source: ['x'], outputs: [{output_type: 'execute_result', data: {'image/png': 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='}}]},
|
{cell_type: 'code', execution_count: 2, source: ['x'], outputs: [{output_type: 'execute_result', data: {'image/png': 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='}}]},
|
||||||
{cell_type: 'code', source: ['# No output'], outputs: []},
|
{cell_type: 'code', source: ['# No output'], outputs: []},
|
||||||
@ -32,7 +32,11 @@ test.describe('jupyter notebook rendering', () => {
|
|||||||
await login(page);
|
await login(page);
|
||||||
await page.goto(`/${owner}/${repoName}/src/branch/main/test.ipynb`);
|
await page.goto(`/${owner}/${repoName}/src/branch/main/test.ipynb`);
|
||||||
await assertNoJsError(page);
|
await assertNoJsError(page);
|
||||||
await expect(page.frameLocator('iframe.external-render-iframe').locator('.cell.markdown strong')).toBeVisible();
|
const frame = page.frameLocator('iframe.external-render-iframe');
|
||||||
|
await expect(frame.locator('.cell.markdown h1')).toBeVisible();
|
||||||
|
await expect(frame.locator('.cell.markdown strong')).toBeVisible();
|
||||||
|
await expect(frame.locator('.cell.markdown ul li').first()).toBeVisible();
|
||||||
|
await expect(frame.locator('.cell.markdown table')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders code cells with outputs', async ({page}) => {
|
test('renders code cells with outputs', async ({page}) => {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type {FrontendRenderFunc} from '../plugin.ts';
|
import type {FrontendRenderFunc} from '../plugin.ts';
|
||||||
|
import {marked} from 'marked';
|
||||||
import '../../../css/features/jupyter.css';
|
import '../../../css/features/jupyter.css';
|
||||||
|
|
||||||
// Sanitize HTML by removing dangerous attributes and elements
|
// Sanitize HTML by removing dangerous attributes and elements
|
||||||
@ -38,60 +39,14 @@ function sanitizeHtml(element: HTMLElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple markdown to HTML converter for notebook cells using DOM methods
|
// Render markdown using marked library
|
||||||
function renderMarkdown(markdown: string): HTMLElement {
|
function renderMarkdown(markdown: string): HTMLElement {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
|
container.innerHTML = marked.parse(markdown) as string;
|
||||||
// Split by lines and process
|
sanitizeHtml(container);
|
||||||
const lines = markdown.split('\n');
|
|
||||||
for (const line of lines) {
|
|
||||||
let element: HTMLElement;
|
|
||||||
|
|
||||||
// Headers
|
|
||||||
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;
|
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) => {
|
export const frontendRender: FrontendRenderFunc = async (opts) => {
|
||||||
try {
|
try {
|
||||||
const notebook = JSON.parse(opts.contentString());
|
const notebook = JSON.parse(opts.contentString());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user