Path: blob/master/src/packages/frontend/components/html.tsx
1503 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45// IMPORTANT: we only update this component when the value changes.6// You can't change other things like the style, href_transform function,7// etc. This is an assumption that makes things much more efficient,8// and should be fine for everything in cocalc.910import { CSSProperties as CSS, useEffect, useRef } from "react";11import useIsMountedRef from "@cocalc/frontend/app-framework/is-mounted-hook";12import { is_share_server } from "./share-server";13import { sanitize_html, sanitize_html_safe } from "../misc/sanitize";14import $ from "jquery";1516export interface Props {17value?: string;18style?: CSS;19auto_render_math?: boolean; // optional -- used to detect and render math20preProcessMath?: 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"...21project_id?: string; // optional -- can be used to improve link handling (e.g., to images)22file_path?: string; // optional -- ...23className?: string; // optional class2425/* optional -- default true, if true scripts and unsafe attributes are removed26from sanitized html WARNING!!! ATTN! false does not work / cannot work!! scripts27will NEVER be run. See commit 1abcd43bd5fff811b5ffaf7c76cb86a0ad494498, which28I've reverted, since it breaks katex... and on balance if we can get by with29other approaches to this problem we should since script is dangerous. See also30https://github.com/sagemathinc/cocalc/issues/469531*/32safeHTML?: boolean;3334// optional function that link/src hrefs are fed through35href_transform?: (string) => string;3637/* optional function post_hook(elt), which should mutate elt, where elt is38the jQuery wrapped set that is created (and discarded!) in the course of39sanitizing input. Use this as an opportunity to modify the HTML structure40before it is exported to text and given to react. Obviously, you can't41install click handlers here.42*/43post_hook?: (any) => void;4445content_editable?: boolean; // if true, makes rendered HTML contenteditable; otherwise, explicitly set false.4647// If true, after any update to component, force reloading of all images.48reload_images?: boolean;4950/* If true, after rendering run the smc_image_scaling pluging to handle51smc-image-scaling= attributes, which are used in smc_sagews to rescale certain52png images produced by other kernels (e.g., the R kernel). See53https://github.com/sagemathinc/cocalc/issues/4421. This functionality is NOT54actually used at all right now, since it doesn't work on the share server55anyways...56*/57smc_image_scaling?: boolean;5859// if true, highlight some <code class='language-r'> </code> blocks.60// this uses a jquery plugin that I wrote that uses codemirror.61highlight_code?: boolean;6263id?: string;6465onClick?: (event?: any) => void;66onDoubleClick?: (event?: any) => void;67}6869export function HTML({70value,71style,72auto_render_math = true,73preProcessMath,74project_id,75file_path,76className,77safeHTML = true,78href_transform,79post_hook,80content_editable,81reload_images,82smc_image_scaling,83highlight_code = true,84id,85onClick,86onDoubleClick,87}: Props) {88const isMountedRef = useIsMountedRef();89const ref = useRef<any>(null);9091function jq(): any {92if (!isMountedRef.current) return;93const elt = ref.current;94if (elt == null) {95return undefined;96}97return $(elt);98}99100function update_mathjax(): void {101if (!isMountedRef.current) {102// see https://github.com/sagemathinc/cocalc/issues/1689103return;104}105if (!auto_render_math) {106return;107}108jq()?.katex({ preProcess: preProcessMath ?? true });109}110111function update_links(): void {112if (!isMountedRef.current) {113return;114}115jq()?.process_smc_links({116project_id,117file_path,118href_transform,119});120}121122function update_tables(): void {123if (!isMountedRef.current) {124return;125}126jq()?.find("table").addClass("table");127}128129function update_images(): void {130if (!isMountedRef.current) {131return;132}133if (reload_images) {134jq()?.reload_images();135}136if (smc_image_scaling) {137jq()?.smc_image_scaling();138}139}140141function update_code(): void {142if (isMountedRef.current && highlight_code) {143// note that the highlight_code plugin might not be defined.144jq()?.highlight_code?.();145}146}147148function do_updates(): void {149if (is_share_server()) {150return;151}152update_mathjax();153update_links();154update_tables();155update_code();156update_images();157}158159function update_content(): void {160if (!isMountedRef.current) {161return;162}163do_updates();164}165166useEffect(update_content);167168function render_html(): { __html: string } {169let html;170if (!value) {171return { __html: "" };172}173174if (is_share_server()) {175/* No sanitization at all for share server. For now we176have set things up so that the share server is served177from a different subdomain and user can't sign into it,178so XSS is not an issue. Note that the sanitizing179in the else below (on non-share server) is expensive and180can crash on "big" documents (e.g., 500K).181*/182const elt = $("<div>") as any;183elt.html(value);184if (auto_render_math) {185elt.katex({ preProcess: preProcessMath ?? true });186}187elt.find("table").addClass("table");188if (highlight_code) {189elt.highlight_code();190}191elt.process_smc_links({192project_id,193file_path,194href_transform,195});196html = elt.html();197} else {198if (safeHTML) {199html = sanitize_html_safe(value, post_hook);200} else {201html = sanitize_html(value, true, true, post_hook);202}203}204205return { __html: html };206}207208/* The random key is the whole span (hence the html) does209get rendered whenever this component is updated. Otherwise,210it will NOT re-render except when the value changes.211*/212if (content_editable) {213return (214<div215ref={ref}216id={id}217contentEditable={true}218key={Math.random()}219className={`${className ?? ""} cocalc-html-component`}220dangerouslySetInnerHTML={render_html()}221style={style}222onClick={onClick}223onDoubleClick={onDoubleClick}224></div>225);226} else {227return (228<span229ref={ref}230id={id}231contentEditable={false}232key={Math.random()}233className={`${className ?? ""} cocalc-html-component`}234dangerouslySetInnerHTML={render_html()}235style={style}236onClick={onClick}237onDoubleClick={onDoubleClick}238></span>239);240}241}242243// this displayName is assumed and USED in the packages/hub/share/mathjax-support244// to identify this component; do NOT change or remove!!245HTML.displayName = "Misc-HTML";246247248