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