Commit b8452bf3 authored by Stan Hu's avatar Stan Hu

Fix images not showing in Jupyter Markdown tables

Previously cell attachments were ignored by the iPython notebook parser.
As described in
https://nbformat.readthedocs.io/en/latest/format_description.html, we
need to support the `attachments` dictionary and use that to render
images.

Closes https://gitlab.com/gitlab-org/gitlab/-/issues/18540
parent 71709028
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import katex from 'katex'; import katex from 'katex';
import marked from 'marked'; import marked from 'marked';
import { sanitize } from '~/lib/dompurify'; import { sanitize } from '~/lib/dompurify';
import { hasContent } from '~/lib/utils/text_utility';
import Prompt from './prompt.vue'; import Prompt from './prompt.vue';
const renderer = new marked.Renderer(); const renderer = new marked.Renderer();
...@@ -85,6 +86,38 @@ renderer.listitem = (t) => { ...@@ -85,6 +86,38 @@ renderer.listitem = (t) => {
const [text, inline] = renderKatex(t); const [text, inline] = renderKatex(t);
return `<li class="${inline ? 'inline-katex' : ''}">${text}</li>`; return `<li class="${inline ? 'inline-katex' : ''}">${text}</li>`;
}; };
renderer.originalImage = renderer.image;
renderer.image = function image(href, title, text) {
const attachmentHeader = `attachment:`; // eslint-disable-line @gitlab/require-i18n-strings
if (!this.attachments || !href.startsWith(attachmentHeader)) {
return this.originalImage(href, title, text);
}
let img = ``;
const filename = href.substring(attachmentHeader.length);
if (hasContent(filename)) {
const attachment = this.attachments[filename];
if (attachment) {
const imageType = Object.keys(attachment)[0];
if (hasContent(imageType)) {
const data = attachment[imageType];
const inlined = `data:${imageType};base64,${data}"`; // eslint-disable-line @gitlab/require-i18n-strings
img = this.originalImage(inlined, title, text);
}
}
}
if (!hasContent(img)) {
return this.originalImage(href, title, text);
}
return sanitize(img);
};
marked.setOptions({ marked.setOptions({
renderer, renderer,
...@@ -102,6 +135,8 @@ export default { ...@@ -102,6 +135,8 @@ export default {
}, },
computed: { computed: {
markdown() { markdown() {
renderer.attachments = this.cell.attachments;
return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), { return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), {
// allowedTags from GitLab's inline HTML guidelines // allowedTags from GitLab's inline HTML guidelines
// https://docs.gitlab.com/ee/user/markdown.html#inline-html // https://docs.gitlab.com/ee/user/markdown.html#inline-html
......
---
title: Fix images not showing in Jupyter Markdown tables
merge_request: 59551
author:
type: fixed
...@@ -25,6 +25,10 @@ RSpec.describe 'Raw files', '(JavaScript fixtures)' do ...@@ -25,6 +25,10 @@ RSpec.describe 'Raw files', '(JavaScript fixtures)' do
@blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb') @blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb')
end end
it 'blob/notebook/markdown-table.json' do
@blob = project.repository.blob_at('b0316785', 'files/ipython/markdown-table.ipynb')
end
it 'blob/notebook/worksheets.json' do it 'blob/notebook/worksheets.json' do
@blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb') @blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb')
end end
......
...@@ -27,7 +27,7 @@ describe('Markdown component', () => { ...@@ -27,7 +27,7 @@ describe('Markdown component', () => {
return vm.$nextTick(); return vm.$nextTick();
}); });
it('does not render promot', () => { it('does not render prompt', () => {
expect(vm.$el.querySelector('.prompt span')).toBeNull(); expect(vm.$el.querySelector('.prompt span')).toBeNull();
}); });
...@@ -51,6 +51,36 @@ describe('Markdown component', () => { ...@@ -51,6 +51,36 @@ describe('Markdown component', () => {
}); });
}); });
describe('tables', () => {
beforeEach(() => {
json = getJSONFixture('blob/notebook/markdown-table.json');
});
it('renders images and text', () => {
vm = new Component({
propsData: {
cell: json.cells[0],
},
}).$mount();
return vm.$nextTick().then(() => {
const images = vm.$el.querySelectorAll('img');
expect(images.length).toBe(3);
const columns = vm.$el.querySelectorAll('td');
expect(images.length).toBe(3);
expect(columns[0].textContent).toEqual('Hello ');
expect(columns[1].textContent).toEqual('Test ');
expect(columns[2].textContent).toEqual('World ');
expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64');
expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64');
expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64');
});
});
});
describe('katex', () => { describe('katex', () => {
beforeEach(() => { beforeEach(() => {
json = getJSONFixture('blob/notebook/math.json'); json = getJSONFixture('blob/notebook/math.json');
......
...@@ -52,7 +52,7 @@ module TestEnv ...@@ -52,7 +52,7 @@ module TestEnv
'wip' => 'b9238ee', 'wip' => 'b9238ee',
'csv' => '3dd0896', 'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3', 'v1.1.0' => 'b83d6e3',
'add-ipython-files' => '93ee732', 'add-ipython-files' => 'b031678',
'add-pdf-file' => 'e774ebd', 'add-pdf-file' => 'e774ebd',
'squash-large-files' => '54cec52', 'squash-large-files' => '54cec52',
'add-pdf-text-binary' => '79faa7b', 'add-pdf-text-binary' => '79faa7b',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment