<commit_before>/*
 *  This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
 *  License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
 */

// Component that shows rendered markdown.
//
// It also:
//
//    - [x] tracks and restores scroll position
//    - [x] is scrollable
//    - [x] is zoomable
//    - [x] math is properly typeset

import { Markdown } from "smc-webapp/r_misc";
import { is_different, path_split } from "smc-util/misc";
import { debounce } from "lodash";
import { React, ReactDOM, CSS } from "../../app-framework";
import { use_font_size_scaling } from "../frame-tree/hooks";
import { MAX_WIDTH_NUM } from "../options";
import { EditorState } from "../frame-tree/types";
import { Actions } from "./actions";
import { Path } from "../frame-tree/path";

interface Props {
  actions: Actions;
  id: string;
  path: string;
  project_id: string;
  font_size: number;
  read_only: boolean;
  value: string;
  editor_state: EditorState;
  reload_images: boolean;
  is_current: boolean;
}

function should_memoize(prev, next): boolean {
  return !is_different(prev, next, [
    "id",
    "project_id",
    "path",
    "font_size",
    "read_only",
    "value",
    "reload_images",
    "is_current",
  ]);
}

export const RenderedMarkdown: React.FC<Props> = React.memo((props: Props) => {
  const {
    actions,
    id,
    path,
    project_id,
    font_size,
    value,
    editor_state,
    reload_images,
    is_current,
  } = props;

  const scroll = React.useRef<HTMLDivElement>(null);

  const scaling = use_font_size_scaling(font_size);

  // once when mounted
  React.useEffect(() => {
    restore_scroll();
  }, []);

  function on_scroll(): void {
    const elt = ReactDOM.findDOMNode(scroll.current);
    if (elt == null) {
      return;
    }
    const scroll_val = $(elt).scrollTop();
    actions.save_editor_state(id, { scroll: scroll_val });
  }

  async function restore_scroll(): Promise<void> {
    const scroll_val = editor_state.get("scroll");
    const elt = $(ReactDOM.findDOMNode(scroll.current));
    try {
      if (elt.length === 0) {
        return;
      }
      await delay(0); // wait until render happens
      elt.scrollTop(scroll_val);
      await delay(0);
      elt.css("opacity", 1);

      // do any scrolling after image loads
      elt.find("img").on("load", function () {
        elt.scrollTop(scroll_val);
      });
    } finally {
      // for sure change opacity to 1 so visible after
      // doing whatever scrolling above
      elt.css("opacity", 1);
    }
  }

  function goto_source_line(event) {
    let elt = event.target;
    while (elt != null && elt.dataset?.sourceLine == null) {
      elt = elt.parentElement;
    }
    if (elt == null) return;
    const line = elt.dataset?.sourceLine;
    if (line != null) {
      actions.programmatical_goto_line(line);
      return;
    }
  }

  const style: CSS = {
    overflowY: "auto",
    width: "100%",
    opacity: 0, // changed to 1 after initial scroll to avoid flicker
  };
  const style_inner: CSS = {
    ...{
      maxWidth: `${(1 + (scaling - 1) / 2) * MAX_WIDTH_NUM}px`,
      margin: "0 auto",
      padding: "50px 75px",
      backgroundColor: "white",
      border: "1px solid lightgrey",
    },
    ...{
      // transform: scale() and transformOrigin: "0 0" or "center 0"
      // doesn't work well. Changing the base font size is fine.
      fontSize: `${100 * scaling}%`,
    },
  };

  return (
    <div className="smc-vfill" style={{ backgroundColor: "#eee" }}>
      <Path
        is_current={is_current}
        path={path}
        project_id={project_id}
      />
      <div
        style={style}
        ref={scroll}
        onScroll={debounce(() => on_scroll(), 200)}
        /* this cocalc-editor-div class is needed for a safari hack only */
        className={"cocalc-editor-div"}
      >
        <div style={style_inner}>
          <Markdown
            line_numbers={true}
            onDoubleClick={goto_source_line}
            value={value}
            project_id={project_id}
            file_path={path_split(path).head}
            safeHTML={true}
            reload_images={reload_images}
            highlight_code={true}
          />
        </div>
      </div>
    </div><commit_msg>rendered markdown: get rid of dynamic maxwidth, for better consistency with jupyter and editable etc. -- users can change frame size or browser, and this lets them have a widescreen *option* if they want it.<commit_after>/*
 *  This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
 *  License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
 */

// Component that shows rendered markdown.
//
// It also:
//
//    - [x] tracks and restores scroll position
//    - [x] is scrollable
//    - [x] is zoomable
//    - [x] math is properly typeset

import { Markdown } from "smc-webapp/r_misc";
import { is_different, path_split } from "smc-util/misc";
import { debounce } from "lodash";
import { React, ReactDOM, CSS } from "../../app-framework";
import { use_font_size_scaling } from "../frame-tree/hooks";
import { EditorState } from "../frame-tree/types";
import { Actions } from "./actions";
import { Path } from "../frame-tree/path";

interface Props {
  actions: Actions;
  id: string;
  path: string;
  project_id: string;
  font_size: number;
  read_only: boolean;
  value: string;
  editor_state: EditorState;
  reload_images: boolean;
  is_current: boolean;
}

function should_memoize(prev, next): boolean {
  return !is_different(prev, next, [
    "id",
    "project_id",
    "path",
    "font_size",
    "read_only",
    "value",
    "reload_images",
    "is_current",
  ]);
}

export const RenderedMarkdown: React.FC<Props> = React.memo((props: Props) => {
  const {
    actions,
    id,
    path,
    project_id,
    font_size,
    value,
    editor_state,
    reload_images,
    is_current,
  } = props;

  const scroll = React.useRef<HTMLDivElement>(null);

  const scaling = use_font_size_scaling(font_size);

  // once when mounted
  React.useEffect(() => {
    restore_scroll();
  }, []);

  function on_scroll(): void {
    const elt = ReactDOM.findDOMNode(scroll.current);
    if (elt == null) {
      return;
    }
    const scroll_val = $(elt).scrollTop();
    actions.save_editor_state(id, { scroll: scroll_val });
  }

  async function restore_scroll(): Promise<void> {
    const scroll_val = editor_state.get("scroll");
    const elt = $(ReactDOM.findDOMNode(scroll.current));
    try {
      if (elt.length === 0) {
        return;
      }
      await delay(0); // wait until render happens
      elt.scrollTop(scroll_val);
      await delay(0);
      elt.css("opacity", 1);

      // do any scrolling after image loads
      elt.find("img").on("load", function () {
        elt.scrollTop(scroll_val);
      });
    } finally {
      // for sure change opacity to 1 so visible after
      // doing whatever scrolling above
      elt.css("opacity", 1);
    }
  }

  function goto_source_line(event) {
    let elt = event.target;
    while (elt != null && elt.dataset?.sourceLine == null) {
      elt = elt.parentElement;
    }
    if (elt == null) return;
    const line = elt.dataset?.sourceLine;
    if (line != null) {
      actions.programmatical_goto_line(line);
      return;
    }
  }

  const style: CSS = {
    overflowY: "auto",
    width: "100%",
    opacity: 0, // changed to 1 after initial scroll to avoid flicker
  };
  const style_inner: CSS = {
    ...{
      margin: "0 auto",
      padding: "50px 75px",
      backgroundColor: "white",
      border: "1px solid lightgrey",
    },
    ...{
      // transform: scale() and transformOrigin: "0 0" or "center 0"
      // doesn't work well. Changing the base font size is fine.
      fontSize: `${100 * scaling}%`,
    },
  };

  return (
    <div className="smc-vfill" style={{ backgroundColor: "#eee" }}>
      <Path is_current={is_current} path={path} project_id={project_id} />
      <div
        style={style}
        ref={scroll}
        onScroll={debounce(() => on_scroll(), 200)}
        /* this cocalc-editor-div class is needed for a safari hack only */
        className={"cocalc-editor-div"}
      >
        <div style={style_inner}>
          <Markdown
            line_numbers={true}
            onDoubleClick={goto_source_line}
            value={value}
            project_id={project_id}
            file_path={path_split(path).head}
            safeHTML={true}
            reload_images={reload_images}
            highlight_code={true}
          />
        </div>
      </div>
    </div>
{'fraction_non_alphanumeric': 0.1396543671181691, 'fraction_numerical': 0.007589911256422233, 'mean_word_length': 2.4902200488997557, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1373117', 'n_tokens_mistral': 2826, 'n_tokens_neox': 2721, 'n_words': 708}