CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/components/html.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
// IMPORTANT: we only update this component when the value changes.
7
// You can't change other things like the style, href_transform function,
8
// etc. This is an assumption that makes things much more efficient,
9
// and should be fine for everything in cocalc.
10
11
import { CSSProperties as CSS, useEffect, useRef } from "react";
12
import ReactDOM from "react-dom";
13
import useIsMountedRef from "@cocalc/frontend/app-framework/is-mounted-hook";
14
import { is_share_server } from "./share-server";
15
import { sanitize_html, sanitize_html_safe } from "../misc/sanitize";
16
import "@cocalc/frontend/misc/process-links/jquery"; // ensure jquery plugin defined.
17
18
declare var $;
19
20
export interface Props {
21
value?: string;
22
style?: CSS;
23
auto_render_math?: boolean; // optional -- used to detect and render math
24
preProcessMath?: boolean; // if true (the default), and auto_render_math, also run tex2jax.PreProcess to find math in $'s, etc., instead of only rendering <script type="math/tex"...
25
project_id?: string; // optional -- can be used to improve link handling (e.g., to images)
26
file_path?: string; // optional -- ...
27
className?: string; // optional class
28
29
/* optional -- default true, if true scripts and unsafe attributes are removed
30
from sanitized html WARNING!!! ATTN! false does not work / cannot work!! scripts
31
will NEVER be run. See commit 1abcd43bd5fff811b5ffaf7c76cb86a0ad494498, which
32
I've reverted, since it breaks katex... and on balance if we can get by with
33
other approaches to this problem we should since script is dangerous. See also
34
https://github.com/sagemathinc/cocalc/issues/4695
35
*/
36
safeHTML?: boolean;
37
38
// optional function that link/src hrefs are fed through
39
href_transform?: (string) => string;
40
41
/* optional function post_hook(elt), which should mutate elt, where elt is
42
the jQuery wrapped set that is created (and discarded!) in the course of
43
sanitizing input. Use this as an opportunity to modify the HTML structure
44
before it is exported to text and given to react. Obviously, you can't
45
install click handlers here.
46
*/
47
post_hook?: (any) => void;
48
49
content_editable?: boolean; // if true, makes rendered HTML contenteditable; otherwise, explicitly set false.
50
51
// If true, after any update to component, force reloading of all images.
52
reload_images?: boolean;
53
54
/* If true, after rendering run the smc_image_scaling pluging to handle
55
smc-image-scaling= attributes, which are used in smc_sagews to rescale certain
56
png images produced by other kernels (e.g., the R kernel). See
57
https://github.com/sagemathinc/cocalc/issues/4421. This functionality is NOT
58
actually used at all right now, since it doesn't work on the share server
59
anyways...
60
*/
61
smc_image_scaling?: boolean;
62
63
// if true, highlight some <code class='language-r'> </code> blocks.
64
// this uses a jquery plugin that I wrote that uses codemirror.
65
highlight_code?: boolean;
66
67
id?: string;
68
69
onClick?: (event?: any) => void;
70
onDoubleClick?: (event?: any) => void;
71
}
72
73
export function HTML({
74
value,
75
style,
76
auto_render_math = true,
77
preProcessMath,
78
project_id,
79
file_path,
80
className,
81
safeHTML = true,
82
href_transform,
83
post_hook,
84
content_editable,
85
reload_images,
86
smc_image_scaling,
87
highlight_code = true,
88
id,
89
onClick,
90
onDoubleClick,
91
}: Props) {
92
const isMountedRef = useIsMountedRef();
93
const ref = useRef(null);
94
95
function jq(): any {
96
if (!isMountedRef.current) return;
97
return $(ReactDOM.findDOMNode(ref.current)) as any;
98
}
99
100
function update_mathjax(): void {
101
if (!isMountedRef.current) {
102
// see https://github.com/sagemathinc/cocalc/issues/1689
103
return;
104
}
105
if (!auto_render_math) {
106
return;
107
}
108
jq()?.katex({ preProcess: preProcessMath ?? true });
109
}
110
111
function update_links(): void {
112
if (!isMountedRef.current) {
113
return;
114
}
115
jq()?.process_smc_links({
116
project_id,
117
file_path,
118
href_transform,
119
});
120
}
121
122
function update_tables(): void {
123
if (!isMountedRef.current) {
124
return;
125
}
126
jq()?.find("table").addClass("table");
127
}
128
129
function update_images(): void {
130
if (!isMountedRef.current) {
131
return;
132
}
133
if (reload_images) {
134
jq()?.reload_images();
135
}
136
if (smc_image_scaling) {
137
jq()?.smc_image_scaling();
138
}
139
}
140
141
function update_code(): void {
142
if (isMountedRef.current && highlight_code) {
143
// note that the highlight_code plugin might not be defined.
144
jq()?.highlight_code?.();
145
}
146
}
147
148
function do_updates(): void {
149
if (is_share_server()) {
150
return;
151
}
152
update_mathjax();
153
update_links();
154
update_tables();
155
update_code();
156
update_images();
157
}
158
159
function update_content(): void {
160
if (!isMountedRef.current) {
161
return;
162
}
163
do_updates();
164
}
165
166
useEffect(update_content);
167
168
function render_html(): { __html: string } {
169
let html;
170
if (!value) {
171
return { __html: "" };
172
}
173
174
if (is_share_server()) {
175
/* No sanitization at all for share server. For now we
176
have set things up so that the share server is served
177
from a different subdomain and user can't sign into it,
178
so XSS is not an issue. Note that the sanitizing
179
in the else below (on non-share server) is expensive and
180
can crash on "big" documents (e.g., 500K).
181
*/
182
const elt = $("<div>") as any;
183
elt.html(value);
184
if (auto_render_math) {
185
elt.katex({ preProcess: preProcessMath ?? true });
186
}
187
elt.find("table").addClass("table");
188
if (highlight_code) {
189
elt.highlight_code();
190
}
191
elt.process_smc_links({
192
project_id,
193
file_path,
194
href_transform,
195
});
196
html = elt.html();
197
} else {
198
if (safeHTML) {
199
html = sanitize_html_safe(value, post_hook);
200
} else {
201
html = sanitize_html(value, true, true, post_hook);
202
}
203
}
204
205
return { __html: html };
206
}
207
208
/* The random key is the whole span (hence the html) does
209
get rendered whenever this component is updated. Otherwise,
210
it will NOT re-render except when the value changes.
211
*/
212
if (content_editable) {
213
return (
214
<div
215
ref={ref}
216
id={id}
217
contentEditable={true}
218
key={Math.random()}
219
className={`${className ?? ""} cocalc-html-component`}
220
dangerouslySetInnerHTML={render_html()}
221
style={style}
222
onClick={onClick}
223
onDoubleClick={onDoubleClick}
224
></div>
225
);
226
} else {
227
return (
228
<span
229
ref={ref}
230
id={id}
231
contentEditable={false}
232
key={Math.random()}
233
className={`${className ?? ""} cocalc-html-component`}
234
dangerouslySetInnerHTML={render_html()}
235
style={style}
236
onClick={onClick}
237
onDoubleClick={onDoubleClick}
238
></span>
239
);
240
}
241
}
242
243
// this displayName is assumed and USED in the packages/hub/share/mathjax-support
244
// to identify this component; do NOT change or remove!!
245
HTML.displayName = "Misc-HTML";
246
247