@difizen/libro-rendermime 0.0.2-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/es/components/html-render.d.ts +6 -0
- package/es/components/html-render.d.ts.map +1 -0
- package/es/components/html-render.js +39 -0
- package/es/components/image-render.d.ts +6 -0
- package/es/components/image-render.d.ts.map +1 -0
- package/es/components/image-render.js +36 -0
- package/es/components/index.d.ts +7 -0
- package/es/components/index.d.ts.map +1 -0
- package/es/components/index.js +6 -0
- package/es/components/latex-render.d.ts +6 -0
- package/es/components/latex-render.d.ts.map +1 -0
- package/es/components/latex-render.js +23 -0
- package/es/components/markdown-render.d.ts +6 -0
- package/es/components/markdown-render.d.ts.map +1 -0
- package/es/components/markdown-render.js +38 -0
- package/es/components/svg-render.d.ts +6 -0
- package/es/components/svg-render.d.ts.map +1 -0
- package/es/components/svg-render.js +33 -0
- package/es/components/text-render.d.ts +9 -0
- package/es/components/text-render.d.ts.map +1 -0
- package/es/components/text-render.js +44 -0
- package/es/index.d.ts +7 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +6 -0
- package/es/renderers.d.ts +42 -0
- package/es/renderers.d.ts.map +1 -0
- package/es/renderers.js +327 -0
- package/es/rendermime-factory.d.ts +30 -0
- package/es/rendermime-factory.d.ts.map +1 -0
- package/es/rendermime-factory.js +73 -0
- package/es/rendermime-module.d.ts +3 -0
- package/es/rendermime-module.d.ts.map +1 -0
- package/es/rendermime-module.js +11 -0
- package/es/rendermime-protocol.d.ts +405 -0
- package/es/rendermime-protocol.d.ts.map +1 -0
- package/es/rendermime-protocol.js +60 -0
- package/es/rendermime-registry.d.ts +135 -0
- package/es/rendermime-registry.d.ts.map +1 -0
- package/es/rendermime-registry.js +392 -0
- package/es/rendermime-utils.d.ts +87 -0
- package/es/rendermime-utils.d.ts.map +1 -0
- package/es/rendermime-utils.js +603 -0
- package/package.json +61 -0
- package/src/components/html-render.tsx +42 -0
- package/src/components/image-render.tsx +46 -0
- package/src/components/index.ts +6 -0
- package/src/components/latex-render.tsx +30 -0
- package/src/components/markdown-render.tsx +42 -0
- package/src/components/svg-render.tsx +38 -0
- package/src/components/text-render.tsx +51 -0
- package/src/index.ts +6 -0
- package/src/renderers.ts +325 -0
- package/src/rendermime-factory.ts +92 -0
- package/src/rendermime-module.ts +19 -0
- package/src/rendermime-protocol.ts +516 -0
- package/src/rendermime-registry.ts +301 -0
- package/src/rendermime-utils.ts +665 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { concatMultilineString } from '@difizen/libro-common';
|
|
2
|
+
import type { BaseOutputView } from '@difizen/libro-core';
|
|
3
|
+
import { useInject } from '@difizen/mana-app';
|
|
4
|
+
import React, { useEffect, useRef } from 'react';
|
|
5
|
+
|
|
6
|
+
import { renderHTML } from '../renderers.js';
|
|
7
|
+
import type { IRenderMimeRegistry } from '../rendermime-protocol.js';
|
|
8
|
+
import { RenderMimeRegistry } from '../rendermime-registry.js';
|
|
9
|
+
|
|
10
|
+
export const HTMLRender: React.FC<{ model: BaseOutputView }> = (props: {
|
|
11
|
+
model: BaseOutputView;
|
|
12
|
+
}) => {
|
|
13
|
+
const { model } = props;
|
|
14
|
+
const renderHTMLRef = useRef<HTMLDivElement>(null);
|
|
15
|
+
const defaultRenderMime = useInject<IRenderMimeRegistry>(RenderMimeRegistry);
|
|
16
|
+
|
|
17
|
+
const mimeType = defaultRenderMime.defaultPreferredMimeType(
|
|
18
|
+
model,
|
|
19
|
+
// model.trusted ? 'any' : 'ensure'
|
|
20
|
+
);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (mimeType) {
|
|
23
|
+
renderHTML({
|
|
24
|
+
host: renderHTMLRef.current as HTMLElement,
|
|
25
|
+
source: concatMultilineString(JSON.parse(JSON.stringify(model.data[mimeType]))),
|
|
26
|
+
trusted: model.trusted,
|
|
27
|
+
resolver: defaultRenderMime.resolver,
|
|
28
|
+
sanitizer: defaultRenderMime.sanitizer,
|
|
29
|
+
linkHandler: defaultRenderMime.linkHandler,
|
|
30
|
+
shouldTypeset: false,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, []);
|
|
35
|
+
return (
|
|
36
|
+
<div className="libro-html-render-container">
|
|
37
|
+
<div className="libro-html-common-render">
|
|
38
|
+
<div className="libro-html-render" ref={renderHTMLRef} />
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { JSONObject } from '@difizen/libro-common';
|
|
2
|
+
import { concatMultilineString } from '@difizen/libro-common';
|
|
3
|
+
import type { BaseOutputView } from '@difizen/libro-core';
|
|
4
|
+
import { useInject } from '@difizen/mana-app';
|
|
5
|
+
import React, { useEffect, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { renderImage } from '../renderers.js';
|
|
8
|
+
import type { IRenderMimeRegistry } from '../rendermime-protocol.js';
|
|
9
|
+
import { RenderMimeRegistry } from '../rendermime-registry.js';
|
|
10
|
+
|
|
11
|
+
export const ImageRender: React.FC<{ model: BaseOutputView }> = (props: {
|
|
12
|
+
model: BaseOutputView;
|
|
13
|
+
}) => {
|
|
14
|
+
const { model } = props;
|
|
15
|
+
const renderImageRef = useRef<HTMLDivElement>(null);
|
|
16
|
+
const defaultRenderMime = useInject<IRenderMimeRegistry>(RenderMimeRegistry);
|
|
17
|
+
const mimeType = defaultRenderMime.defaultPreferredMimeType(
|
|
18
|
+
model,
|
|
19
|
+
// model.trusted ? 'any' : 'ensure'
|
|
20
|
+
);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (mimeType) {
|
|
23
|
+
renderImage({
|
|
24
|
+
host: renderImageRef.current as HTMLElement,
|
|
25
|
+
source: concatMultilineString(JSON.parse(JSON.stringify(model.data[mimeType]))),
|
|
26
|
+
width: (model.metadata['width'] ||
|
|
27
|
+
(model.metadata[mimeType] as JSONObject)?.['width']) as unknown as
|
|
28
|
+
| number
|
|
29
|
+
| undefined,
|
|
30
|
+
height: (model.metadata['height'] ||
|
|
31
|
+
(model.metadata[mimeType] as JSONObject)?.['height']) as unknown as
|
|
32
|
+
| number
|
|
33
|
+
| undefined,
|
|
34
|
+
needsBackground: model.metadata['needs_background'] as string | undefined,
|
|
35
|
+
unconfined:
|
|
36
|
+
model.metadata && (model.metadata['unconfined'] as boolean | undefined),
|
|
37
|
+
mimeType: mimeType,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}, [mimeType, model.data, model.metadata, renderImageRef]);
|
|
41
|
+
return (
|
|
42
|
+
<div className="libro-image-render-container">
|
|
43
|
+
<div className="libro-image-render" ref={renderImageRef} />
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { concatMultilineString } from '@difizen/libro-common';
|
|
2
|
+
import type { BaseOutputView } from '@difizen/libro-core';
|
|
3
|
+
import { useInject } from '@difizen/mana-app';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import type { IRenderMimeRegistry } from '../rendermime-protocol.js';
|
|
7
|
+
import { RenderMimeRegistry } from '../rendermime-registry.js';
|
|
8
|
+
|
|
9
|
+
export const LatexRender: React.FC<{ model: BaseOutputView }> = (props: {
|
|
10
|
+
model: BaseOutputView;
|
|
11
|
+
}) => {
|
|
12
|
+
const { model } = props;
|
|
13
|
+
const defaultRenderMime = useInject<IRenderMimeRegistry>(RenderMimeRegistry);
|
|
14
|
+
|
|
15
|
+
const mimeType = defaultRenderMime.defaultPreferredMimeType(
|
|
16
|
+
model,
|
|
17
|
+
// model.trusted ? 'any' : 'ensure'
|
|
18
|
+
);
|
|
19
|
+
if (!mimeType) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="libro-latex-render-container">
|
|
25
|
+
<div className="libro-latex-render">
|
|
26
|
+
{concatMultilineString(JSON.parse(JSON.stringify(model.data[mimeType])))}
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { concatMultilineString } from '@difizen/libro-common';
|
|
2
|
+
import type { BaseOutputView } from '@difizen/libro-core';
|
|
3
|
+
import { useInject } from '@difizen/mana-app';
|
|
4
|
+
import React, { useEffect, useRef } from 'react';
|
|
5
|
+
|
|
6
|
+
import { renderMarkdown } from '../renderers.js';
|
|
7
|
+
import type { IRenderMimeRegistry } from '../rendermime-protocol.js';
|
|
8
|
+
import { RenderMimeRegistry } from '../rendermime-registry.js';
|
|
9
|
+
|
|
10
|
+
export const MarkdownRender: React.FC<{ model: BaseOutputView }> = (props: {
|
|
11
|
+
model: BaseOutputView;
|
|
12
|
+
}) => {
|
|
13
|
+
const { model } = props;
|
|
14
|
+
const renderMarkdownRef = useRef<HTMLDivElement>(null);
|
|
15
|
+
const defaultRenderMime = useInject<IRenderMimeRegistry>(RenderMimeRegistry);
|
|
16
|
+
|
|
17
|
+
const mimeType = defaultRenderMime.defaultPreferredMimeType(
|
|
18
|
+
model,
|
|
19
|
+
// model.trusted ? 'any' : 'ensure'
|
|
20
|
+
);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (mimeType) {
|
|
23
|
+
renderMarkdown({
|
|
24
|
+
host: renderMarkdownRef.current as HTMLElement,
|
|
25
|
+
source: concatMultilineString(JSON.parse(JSON.stringify(model.data[mimeType]))),
|
|
26
|
+
trusted: model.trusted,
|
|
27
|
+
resolver: defaultRenderMime.resolver,
|
|
28
|
+
sanitizer: defaultRenderMime.sanitizer,
|
|
29
|
+
linkHandler: defaultRenderMime.linkHandler,
|
|
30
|
+
// shouldTypeset: options.isAttached,
|
|
31
|
+
markdownParser: defaultRenderMime.markdownParser,
|
|
32
|
+
cellId: model.cell.model.id,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
|
+
}, []);
|
|
37
|
+
return (
|
|
38
|
+
<div className="libro-markdown-render-container">
|
|
39
|
+
<div className="libro-markdown-render" ref={renderMarkdownRef} />
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { concatMultilineString } from '@difizen/libro-common';
|
|
2
|
+
import type { BaseOutputView } from '@difizen/libro-core';
|
|
3
|
+
import { useInject } from '@difizen/mana-app';
|
|
4
|
+
import React, { useEffect, useRef } from 'react';
|
|
5
|
+
|
|
6
|
+
import { renderSVG } from '../renderers.js';
|
|
7
|
+
import type { IRenderMimeRegistry } from '../rendermime-protocol.js';
|
|
8
|
+
import { RenderMimeRegistry } from '../rendermime-registry.js';
|
|
9
|
+
|
|
10
|
+
export const SVGRender: React.FC<{ model: BaseOutputView }> = (props: {
|
|
11
|
+
model: BaseOutputView;
|
|
12
|
+
}) => {
|
|
13
|
+
const { model } = props;
|
|
14
|
+
const renderSVGRef = useRef<HTMLDivElement>(null);
|
|
15
|
+
const defaultRenderMime = useInject<IRenderMimeRegistry>(RenderMimeRegistry);
|
|
16
|
+
|
|
17
|
+
const mimeType = defaultRenderMime.defaultPreferredMimeType(
|
|
18
|
+
model,
|
|
19
|
+
// model.trusted ? 'any' : 'ensure'
|
|
20
|
+
);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (mimeType) {
|
|
23
|
+
renderSVG({
|
|
24
|
+
host: renderSVGRef.current as HTMLElement,
|
|
25
|
+
source: concatMultilineString(JSON.parse(JSON.stringify(model.data[mimeType]))),
|
|
26
|
+
trusted: model.trusted,
|
|
27
|
+
unconfined:
|
|
28
|
+
model.metadata && (model.metadata['unconfined'] as boolean | undefined),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
|
+
}, []);
|
|
33
|
+
return (
|
|
34
|
+
<div className="libro-svg-render-container">
|
|
35
|
+
<div className="libro-svg-render" ref={renderSVGRef} />
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { JSONValue } from '@difizen/libro-common';
|
|
2
|
+
import { concatMultilineString } from '@difizen/libro-common';
|
|
3
|
+
import type { BaseOutputView } from '@difizen/libro-core';
|
|
4
|
+
import { useInject } from '@difizen/mana-app';
|
|
5
|
+
import React, { useEffect, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { renderText } from '../renderers.js';
|
|
8
|
+
import type { IRenderMimeRegistry } from '../rendermime-protocol.js';
|
|
9
|
+
import { RenderMimeRegistry } from '../rendermime-registry.js';
|
|
10
|
+
|
|
11
|
+
export const RawTextRender: React.FC<{ model: BaseOutputView }> = (props: {
|
|
12
|
+
model: BaseOutputView;
|
|
13
|
+
}) => {
|
|
14
|
+
const { model } = props;
|
|
15
|
+
const renderTextRef = useRef<HTMLDivElement>(null);
|
|
16
|
+
const renderTextContainerRef = useRef<HTMLDivElement>(null);
|
|
17
|
+
const defaultRenderMime = useInject<IRenderMimeRegistry>(RenderMimeRegistry);
|
|
18
|
+
|
|
19
|
+
const mimeType = defaultRenderMime.defaultPreferredMimeType(
|
|
20
|
+
model,
|
|
21
|
+
// model.trusted ? 'any' : 'ensure'
|
|
22
|
+
);
|
|
23
|
+
let dataContent: JSONValue | null = null;
|
|
24
|
+
if (mimeType) {
|
|
25
|
+
dataContent = model.data[mimeType];
|
|
26
|
+
}
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (dataContent && mimeType) {
|
|
29
|
+
renderText({
|
|
30
|
+
host: renderTextRef.current as HTMLElement,
|
|
31
|
+
source: concatMultilineString(JSON.parse(JSON.stringify(dataContent))),
|
|
32
|
+
sanitizer: defaultRenderMime.sanitizer,
|
|
33
|
+
mimeType: mimeType,
|
|
34
|
+
});
|
|
35
|
+
if (mimeType === 'application/vnd.jupyter.stderr') {
|
|
36
|
+
renderTextContainerRef.current?.setAttribute(
|
|
37
|
+
'data-mime-type',
|
|
38
|
+
'application/vnd.jupyter.stderr',
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
43
|
+
}, [mimeType, dataContent]);
|
|
44
|
+
return (
|
|
45
|
+
<div className="libro-text-render-container" ref={renderTextContainerRef}>
|
|
46
|
+
<div className="libro-text-render" ref={renderTextRef} />
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const TextRender = React.memo(RawTextRender);
|
package/src/index.ts
ADDED
package/src/renderers.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { concatMultilineString } from '@difizen/libro-common';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
IRenderHTMLOptions,
|
|
5
|
+
IRenderImageOptions,
|
|
6
|
+
IRenderMarkdownOptions,
|
|
7
|
+
IRenderSVGOptions,
|
|
8
|
+
IRenderTextOptions,
|
|
9
|
+
} from './rendermime-protocol.js';
|
|
10
|
+
import {
|
|
11
|
+
ansiSpan,
|
|
12
|
+
autolink,
|
|
13
|
+
evalInnerHTMLScriptTags,
|
|
14
|
+
handleDefaults,
|
|
15
|
+
handleUrls,
|
|
16
|
+
splitShallowNode,
|
|
17
|
+
} from './rendermime-utils.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render text into a host node.
|
|
21
|
+
*
|
|
22
|
+
* @param options - The options for rendering.
|
|
23
|
+
*
|
|
24
|
+
* @returns A promise which resolves when rendering is complete.
|
|
25
|
+
*/
|
|
26
|
+
export function renderText(options: IRenderTextOptions): Promise<void> {
|
|
27
|
+
// Unpack the options.
|
|
28
|
+
const { host, sanitizer, source, mimeType } = options;
|
|
29
|
+
const data = concatMultilineString(JSON.parse(JSON.stringify(source)));
|
|
30
|
+
// Create the HTML content.
|
|
31
|
+
const content = sanitizer.sanitize(ansiSpan(data), {
|
|
32
|
+
allowedTags: ['span'],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (mimeType === 'application/vnd.jupyter.stderr') {
|
|
36
|
+
host.setAttribute('data-mime-type', 'application/vnd.jupyter.stderr');
|
|
37
|
+
}
|
|
38
|
+
// Set the sanitized content for the host node.
|
|
39
|
+
const pre = document.createElement('pre');
|
|
40
|
+
pre.innerHTML = content;
|
|
41
|
+
|
|
42
|
+
const preTextContent = pre.textContent;
|
|
43
|
+
|
|
44
|
+
if (preTextContent) {
|
|
45
|
+
// Note: only text nodes and span elements should be present after sanitization in the `<pre>` element.
|
|
46
|
+
const linkedNodes = autolink(preTextContent);
|
|
47
|
+
let inAnchorElement = false;
|
|
48
|
+
|
|
49
|
+
const combinedNodes: (HTMLAnchorElement | Text | HTMLSpanElement)[] = [];
|
|
50
|
+
const preNodes = Array.from(pre.childNodes) as (Text | HTMLSpanElement)[];
|
|
51
|
+
|
|
52
|
+
while (preNodes.length && linkedNodes.length) {
|
|
53
|
+
// Use non-null assertions to workaround TypeScript context awareness limitation
|
|
54
|
+
// (if any of the arrays were empty, we would not enter the body of the loop).
|
|
55
|
+
let preNode = preNodes.shift()!;
|
|
56
|
+
let linkNode = linkedNodes.shift()!;
|
|
57
|
+
|
|
58
|
+
// This should never happen because we modify the arrays in flight so they should end simultaneously,
|
|
59
|
+
// but this makes the coding assistance happy and might make it easier to conceptualize.
|
|
60
|
+
if (typeof preNode === 'undefined') {
|
|
61
|
+
combinedNodes.push(linkNode);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (typeof linkNode === 'undefined') {
|
|
65
|
+
combinedNodes.push(preNode);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const preLen = preNode.textContent?.length;
|
|
70
|
+
const linkLen = linkNode.textContent?.length;
|
|
71
|
+
if (preLen && linkLen) {
|
|
72
|
+
if (preLen > linkLen) {
|
|
73
|
+
// Split pre node and only keep the shorter part
|
|
74
|
+
const { pre: keep, post: postpone } = splitShallowNode(preNode, linkLen);
|
|
75
|
+
preNodes.unshift(postpone);
|
|
76
|
+
preNode = keep;
|
|
77
|
+
} else if (linkLen > preLen) {
|
|
78
|
+
const { pre: keep, post: postpone } = splitShallowNode(linkNode, preLen);
|
|
79
|
+
linkedNodes.unshift(postpone);
|
|
80
|
+
linkNode = keep;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const lastCombined = combinedNodes[combinedNodes.length - 1];
|
|
85
|
+
|
|
86
|
+
// If we are already in an anchor element and the anchor element did not change,
|
|
87
|
+
// we should insert the node from <pre> which is either Text node or coloured span Element
|
|
88
|
+
// into the anchor content as a child
|
|
89
|
+
if (
|
|
90
|
+
inAnchorElement &&
|
|
91
|
+
(linkNode as HTMLAnchorElement).href ===
|
|
92
|
+
(lastCombined as HTMLAnchorElement).href
|
|
93
|
+
) {
|
|
94
|
+
lastCombined.appendChild(preNode);
|
|
95
|
+
} else {
|
|
96
|
+
// the `linkNode` is either Text or AnchorElement;
|
|
97
|
+
const isAnchor = linkNode.nodeType !== Node.TEXT_NODE;
|
|
98
|
+
// if we are NOT about to start an anchor element, just add the pre Node
|
|
99
|
+
if (!isAnchor) {
|
|
100
|
+
combinedNodes.push(preNode);
|
|
101
|
+
inAnchorElement = false;
|
|
102
|
+
} else {
|
|
103
|
+
// otherwise start a new anchor; the contents of the `linkNode` and `preNode` should be the same,
|
|
104
|
+
// so we just put the neatly formatted `preNode` inside the anchor node (`linkNode`)
|
|
105
|
+
// and append that to combined nodes.
|
|
106
|
+
linkNode.textContent = '';
|
|
107
|
+
linkNode.appendChild(preNode);
|
|
108
|
+
combinedNodes.push(linkNode);
|
|
109
|
+
inAnchorElement = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// TODO: replace with `.replaceChildren()` once the target ES version allows it
|
|
114
|
+
pre.innerHTML = '';
|
|
115
|
+
for (const child of combinedNodes) {
|
|
116
|
+
pre.appendChild(child);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
host.appendChild(pre);
|
|
121
|
+
host.classList.add('libro-text-render');
|
|
122
|
+
|
|
123
|
+
// Return the rendered promise.
|
|
124
|
+
return Promise.resolve(undefined);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Render an image into a host node.
|
|
129
|
+
*
|
|
130
|
+
* @param options - The options for rendering.
|
|
131
|
+
*
|
|
132
|
+
* @returns A promise which resolves when rendering is complete.
|
|
133
|
+
*/
|
|
134
|
+
export function renderImage(options: IRenderImageOptions): Promise<void> {
|
|
135
|
+
// Unpack the options.
|
|
136
|
+
const { host, mimeType, source, width, height, needsBackground, unconfined } =
|
|
137
|
+
options;
|
|
138
|
+
|
|
139
|
+
// Clear the content in the host.
|
|
140
|
+
host.textContent = '';
|
|
141
|
+
|
|
142
|
+
// Create the image element.
|
|
143
|
+
const img = document.createElement('img');
|
|
144
|
+
|
|
145
|
+
// Set the source of the image.
|
|
146
|
+
img.src = `data:${mimeType};base64,${source}`;
|
|
147
|
+
|
|
148
|
+
// Set the size of the image if provided.
|
|
149
|
+
if (typeof height === 'number') {
|
|
150
|
+
img.height = height;
|
|
151
|
+
}
|
|
152
|
+
if (typeof width === 'number') {
|
|
153
|
+
img.width = width;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (needsBackground === 'light') {
|
|
157
|
+
img.classList.add('jp-needs-light-background');
|
|
158
|
+
} else if (needsBackground === 'dark') {
|
|
159
|
+
img.classList.add('jp-needs-dark-background');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (unconfined === true) {
|
|
163
|
+
img.classList.add('jp-mod-unconfined');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Add the image to the host.
|
|
167
|
+
host.appendChild(img);
|
|
168
|
+
|
|
169
|
+
// Return the rendered promise.
|
|
170
|
+
return Promise.resolve(undefined);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Render HTML into a host node.
|
|
175
|
+
*
|
|
176
|
+
* @param options - The options for rendering.
|
|
177
|
+
*
|
|
178
|
+
* @returns A promise which resolves when rendering is complete.
|
|
179
|
+
*/
|
|
180
|
+
export function renderHTML(options: IRenderHTMLOptions): Promise<void> {
|
|
181
|
+
// Unpack the options.
|
|
182
|
+
const {
|
|
183
|
+
host,
|
|
184
|
+
trusted,
|
|
185
|
+
sanitizer,
|
|
186
|
+
resolver,
|
|
187
|
+
linkHandler,
|
|
188
|
+
// translator,
|
|
189
|
+
} = options;
|
|
190
|
+
let source = options.source;
|
|
191
|
+
|
|
192
|
+
// translator = translator || nullTranslator;
|
|
193
|
+
// const trans = translator?.load('jupyterlab');
|
|
194
|
+
let originalSource = source;
|
|
195
|
+
|
|
196
|
+
// Bail early if the source is empty.
|
|
197
|
+
if (!source) {
|
|
198
|
+
host.textContent = '';
|
|
199
|
+
return Promise.resolve(undefined);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Sanitize the source if it is not trusted. This removes all
|
|
203
|
+
// `<script>` tags as well as other potentially harmful HTML.
|
|
204
|
+
if (!trusted) {
|
|
205
|
+
originalSource = `${source}`;
|
|
206
|
+
source = sanitizer.sanitize(source);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Set the inner HTML of the host.
|
|
210
|
+
host.innerHTML = source;
|
|
211
|
+
|
|
212
|
+
if (host.getElementsByTagName('script').length > 0) {
|
|
213
|
+
// If output it trusted, eval any script tags contained in the HTML.
|
|
214
|
+
// This is not done automatically by the browser when script tags are
|
|
215
|
+
// created by setting `innerHTML`.
|
|
216
|
+
if (trusted) {
|
|
217
|
+
evalInnerHTMLScriptTags(host);
|
|
218
|
+
} else {
|
|
219
|
+
const container = document.createElement('div');
|
|
220
|
+
const warning = document.createElement('pre');
|
|
221
|
+
warning.textContent =
|
|
222
|
+
'This HTML output contains inline scripts. Are you sure that you want to run arbitrary Javascript within your JupyterLab session?';
|
|
223
|
+
const runButton = document.createElement('button');
|
|
224
|
+
runButton.textContent = 'Run';
|
|
225
|
+
runButton.onclick = () => {
|
|
226
|
+
host.innerHTML = originalSource;
|
|
227
|
+
evalInnerHTMLScriptTags(host);
|
|
228
|
+
if (host.firstChild) {
|
|
229
|
+
host.removeChild(host.firstChild);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
container.appendChild(warning);
|
|
233
|
+
container.appendChild(runButton);
|
|
234
|
+
host.insertBefore(container, host.firstChild);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Handle default behavior of nodes.
|
|
239
|
+
handleDefaults(host, resolver);
|
|
240
|
+
|
|
241
|
+
// Patch the urls if a resolver is available.
|
|
242
|
+
let promise: Promise<void>;
|
|
243
|
+
if (resolver) {
|
|
244
|
+
promise = handleUrls(host, resolver, linkHandler);
|
|
245
|
+
} else {
|
|
246
|
+
promise = Promise.resolve(undefined);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Return the final rendered promise.
|
|
250
|
+
return promise;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Render SVG into a host node.
|
|
255
|
+
*
|
|
256
|
+
* @param options - The options for rendering.
|
|
257
|
+
*
|
|
258
|
+
* @returns A promise which resolves when rendering is complete.
|
|
259
|
+
*/
|
|
260
|
+
export function renderSVG(options: IRenderSVGOptions): Promise<void> {
|
|
261
|
+
// Unpack the options.
|
|
262
|
+
const { host, unconfined } = options;
|
|
263
|
+
let source = options.source;
|
|
264
|
+
// Clear the content if there is no source.
|
|
265
|
+
if (!source) {
|
|
266
|
+
host.textContent = '';
|
|
267
|
+
return Promise.resolve(undefined);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// // Display a message if the source is not trusted.
|
|
271
|
+
// if (!trusted) {
|
|
272
|
+
// host.textContent = 'Cannot display an untrusted SVG. Maybe you need to run the cell?';
|
|
273
|
+
// return Promise.resolve(undefined);
|
|
274
|
+
// }
|
|
275
|
+
|
|
276
|
+
// Add missing SVG namespace (if actually missing)
|
|
277
|
+
const patt = '<svg[^>]+xmlns=[^>]+svg';
|
|
278
|
+
if (source.search(patt) < 0) {
|
|
279
|
+
source = source.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Render in img so that user can save it easily
|
|
283
|
+
const img = new Image();
|
|
284
|
+
img.src = `data:image/svg+xml,${encodeURIComponent(source)}`;
|
|
285
|
+
host.appendChild(img);
|
|
286
|
+
|
|
287
|
+
if (unconfined === true) {
|
|
288
|
+
host.classList.add('jp-mod-unconfined');
|
|
289
|
+
}
|
|
290
|
+
return Promise.resolve();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Render Markdown into a host node.
|
|
295
|
+
*
|
|
296
|
+
* @param options - The options for rendering.
|
|
297
|
+
*
|
|
298
|
+
* @returns A promise which resolves when rendering is complete.
|
|
299
|
+
*/
|
|
300
|
+
export async function renderMarkdown(options: IRenderMarkdownOptions): Promise<void> {
|
|
301
|
+
// Unpack the options.
|
|
302
|
+
const { host, source, ...others } = options;
|
|
303
|
+
|
|
304
|
+
// Clear the content if there is no source.
|
|
305
|
+
if (!source) {
|
|
306
|
+
host.textContent = '';
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let html = '';
|
|
311
|
+
|
|
312
|
+
// Fallback if the application does not have any markdown parser.
|
|
313
|
+
html = `<pre>${source}</pre>`;
|
|
314
|
+
|
|
315
|
+
// Render HTML.
|
|
316
|
+
await renderHTML({
|
|
317
|
+
host,
|
|
318
|
+
source: html,
|
|
319
|
+
shouldTypeset: false,
|
|
320
|
+
...others,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Apply ids to the header nodes.
|
|
324
|
+
// headerAnchors(host);
|
|
325
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HTMLRender,
|
|
3
|
+
ImageRender,
|
|
4
|
+
LatexRender,
|
|
5
|
+
MarkdownRender,
|
|
6
|
+
SVGRender,
|
|
7
|
+
TextRender,
|
|
8
|
+
} from './components/index.js';
|
|
9
|
+
import type { IRendererFactory } from './rendermime-protocol.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A mime renderer factory for raw html.
|
|
13
|
+
*/
|
|
14
|
+
export const htmlRendererFactory: IRendererFactory = {
|
|
15
|
+
renderType: 'htmlRenderer',
|
|
16
|
+
safe: true,
|
|
17
|
+
mimeTypes: ['text/html'],
|
|
18
|
+
defaultRank: 50,
|
|
19
|
+
render: HTMLRender,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A mime renderer factory for images.
|
|
24
|
+
*/
|
|
25
|
+
export const imageRendererFactory: IRendererFactory = {
|
|
26
|
+
renderType: 'imageRenderer',
|
|
27
|
+
safe: true,
|
|
28
|
+
mimeTypes: ['image/bmp', 'image/png', 'image/jpeg', 'image/gif', 'image/webp'],
|
|
29
|
+
defaultRank: 90,
|
|
30
|
+
render: ImageRender,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A mime renderer factory for LaTeX.
|
|
35
|
+
*/
|
|
36
|
+
export const latexRendererFactory: IRendererFactory = {
|
|
37
|
+
renderType: 'latexRenderer',
|
|
38
|
+
safe: true,
|
|
39
|
+
mimeTypes: ['text/latex'],
|
|
40
|
+
defaultRank: 70,
|
|
41
|
+
render: LatexRender,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A mime renderer factory for Markdown.
|
|
46
|
+
*/
|
|
47
|
+
export const markdownRendererFactory: IRendererFactory = {
|
|
48
|
+
renderType: 'markdownRenderer',
|
|
49
|
+
safe: true,
|
|
50
|
+
mimeTypes: ['text/markdown'],
|
|
51
|
+
defaultRank: 60,
|
|
52
|
+
render: MarkdownRender,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A mime renderer factory for svg.
|
|
57
|
+
*/
|
|
58
|
+
export const svgRendererFactory: IRendererFactory = {
|
|
59
|
+
renderType: 'svgRenderer',
|
|
60
|
+
safe: false,
|
|
61
|
+
mimeTypes: ['image/svg+xml'],
|
|
62
|
+
defaultRank: 80,
|
|
63
|
+
render: SVGRender,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A mime renderer factory for plain and jupyter console text data.
|
|
68
|
+
*/
|
|
69
|
+
export const textRendererFactory: IRendererFactory = {
|
|
70
|
+
renderType: 'textRenderer',
|
|
71
|
+
safe: true,
|
|
72
|
+
mimeTypes: [
|
|
73
|
+
'text/plain',
|
|
74
|
+
'application/vnd.jupyter.stdout',
|
|
75
|
+
'application/vnd.jupyter.stderr',
|
|
76
|
+
],
|
|
77
|
+
defaultRank: 120,
|
|
78
|
+
render: TextRender,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The standard factories provided by the rendermime package.
|
|
83
|
+
*/
|
|
84
|
+
export const standardRendererFactories: IRendererFactory[] = [
|
|
85
|
+
htmlRendererFactory,
|
|
86
|
+
markdownRendererFactory,
|
|
87
|
+
latexRendererFactory,
|
|
88
|
+
svgRendererFactory,
|
|
89
|
+
imageRendererFactory,
|
|
90
|
+
// javaScriptRendererFactory,
|
|
91
|
+
textRendererFactory,
|
|
92
|
+
];
|