Home Reference Source

scripts/widgets/ui_richedit.js

import * as ui_base from '../core/ui_base.js'
import * as util from '../path-controller/util/util.js';
import {ColumnFrame, RowFrame, Container} from "../core/ui.js";
let UIBase = ui_base.UIBase, Icons = ui_base.Icons;
import {TextBoxBase} from './ui_textbox.js';
import {keymap} from "../path-controller/util/simple_events.js";

export class RichEditor extends TextBoxBase {
  constructor() {
    super();

    this._internalDisabled = false;
    this._value = "";

    this.textOnlyMode = false;

    this.styletag = document.createElement("style");
    this.styletag.textContent = `
      div.rich-text-editor-x {
        width        :   100%;
        height       :   100%;
        min-height   :   150px;
        overflow     :   scroll;
        padding      :   5px;
        white-space  :   pre-wrap;
      }
      
      rich-text-editor-x {
        display        : flex;
        flex-direction : column;
      }
    `;

    this.shadow.appendChild(this.styletag);

    let controls = this.controls = UIBase.createElement("rowframe-x");


    let makeicon = (icon, description, cb) => {
      icon = controls.iconbutton(icon, description, cb);
      icon.iconsheet = 1; //use second smallest icon size
      icon.overrideDefault("padding", 3);

      return icon;
    };

    makeicon(Icons.BOLD, "Bold", () => {
      document.execCommand("bold");
    });
    makeicon(Icons.ITALIC, "Italic", () => {
      document.execCommand("italic");
    });
    makeicon(Icons.UNDERLINE, "Underline", () => {
      document.execCommand("underline");
    });
    makeicon(Icons.STRIKETHRU, "Strikethrough", () => {
      document.execCommand("strikeThrough");
    });

    controls.background = this.getDefault("background-color");

    this.shadow.appendChild(controls);

    this.textarea = document.createElement("div");
    this.textarea.contentEditable = true;
    this.textarea.setAttribute("class", "rich-text-editor-x");

    this.textarea.style["font"] = this.getDefault("DefaultText").genCSS();
    this.textarea.style["background-color"] = this.getDefault("background-color");
    this.textarea.setAttribute("white-space", "pre-wrap");

    this.textarea.addEventListener("keydown", (e) => {
      if (e.keyCode === keymap["S"] && e.shiftKey && (e.ctrlKey || e.commandKey)) {
        this.toggleStrikeThru();

        e.preventDefault();
        e.stopPropagation();
      }
    });

    this.textarea.addEventListener("focus", (e) => {
      this._focus = 1;
      this.setCSS();
    });
    this.textarea.addEventListener("blur", (e) => {
      this._focus = 0;
      this.setCSS();
    });

    document.execCommand("styleWithCSS", true);

    window.ta = this;

    this.textarea.addEventListener("selectionchange", (e) => {
      console.log("sel1");
    });

    document.addEventListener("selectionchange", (e, b) => {
      console.log("sel2", document.getSelection().startNode, b);
    });

    this.textarea.addEventListener("input", (e) => {
      if (this.internalDisabled) {
        return;
      }

      console.log("text input", e);

      let text;

      if (this.textOnlyMode) {
        text = this.textarea.innerText;
      } else {
        text = this.textarea.innerHTML;
      }

      if (this.textOnlyMode && text === this._value) {
        //formatting changed?
        console.log("detected formatting change");
        return;
      }

      //console.log("text changed", ...arguments);
      let sel = document.getSelection();
      let range = sel.getRangeAt(0);
      //console.log(range.startOffset, range.endOffset, range.startContainer);

      let node = sel.anchorNode;
      let off = sel.anchorOffset;

      this._value = text;

      //sel.collapse(node, off);

      if (this.hasAttribute("datapath")) {
        let path = this.getAttribute("datapath");
        this.setPathValue(this.ctx, path, this.value);
      }

      if (this.onchange) {
        this.onchange(this._value);
      }
      if (this.oninput) {
        this.oninput(this._value);
      }

      this.dispatchEvent(new InputEvent(this));
      this.dispatchEvent(new CustomEvent('change'));
    });

    this.shadow.appendChild(this.textarea);
  }

  /**
   * Only available in textOnlyMode.  Called when starting text formatting
  */
  formatStart() {
  }

  /**
  * Only available in textOnlyMode.  Formats html-formated line.
   *
   * @param line : line to format
   * @parem text : whole text
  * */
  formatLine(line, text) {
    return line;
  }

  toggleStrikeThru() {
    console.log("strike thru!");
    document.execCommand("strikeThrough");
  }
  /**
   * Only available in textOnlyMode.  Called when starting text formatting
   */
  formatEnd() {
  }

  init() {
    super.init();

    window.rc = this;

    document.execCommand("defaultParagraphSeparator", false, "div");


    this.setCSS();
  }

  get internalDisabled() {
    return this._internalDisabled;
  }

  set internalDisabled(val) {
    let changed = !!this._internalDisabled !== !!val;

    if (changed || 1) {
      this._internalDisabled = !!val;
      super.internalDisabled = val;

      this.textarea.internalDisabled = val;
      this.textarea.contentEditable = !val;
      this.setCSS();
    }
  }

  set value(val) {
    this._value = val;

    if (this.textOnlyMode) {
      let val2 = "";
      for (let l of val.split("\n")) {
        val2 += l + "<br>";
      }
      val = val2;
    }

    this.textarea.innerHTML = val;
  }

  get value() {
    return this._value;
  }

  setCSS() {
    super.setCSS();

    this.controls.background = this.getDefault("background-color");

    if (this._focus) {
      this.textarea.style["border"] = `2px dashed ${this.getDefault('focus-border-color')}`;
    } else {
      this.textarea.style["border"] = "none";
    }

    if (this.style["font"]) {
      this.textarea.style["font"] = this.style["font"];
    } else {
      this.textarea.style["font"] = this.getDefault("DefaultText").genCSS();
    }

    if (this.style["color"]) {
      this.textarea.style["color"] = this.style["color"];
    }  else {
      this.textarea.style["color"] = this.getDefault("DefaultText").color;
    }


    if (this.disabled) {
      this.textarea.style["background-color"] = this.getDefault("DisabledBG");
    } else {
      this.textarea.style["background-color"] = this.getDefault("background-color");
    }
  }

  updateDataPath() {
    if (!this.hasAttribute("datapath")) {
      return;
    }

    let path = this.getAttribute("datapath");
    let prop = this.getPathMeta(this.ctx, path);

    if (prop === undefined) {
      console.warn("invalid datapath " + path);

      this.internalDisabled = true;
      return;
    }

    this.internalDisabled = false;
    let value = this.getPathValue(this.ctx, path);

    if (value !== this._value) {
      console.log("text change");
      this.value = value;
    }
  }

  update() {
    super.update();

    this.updateDataPath();
  }

  static define() {return {
    tagname : "rich-text-editor-x",
    style   : "richtext",
    modalKeyEvents : true
  }}
}
UIBase.internalRegister(RichEditor);

export class RichViewer extends UIBase {
  constructor() {
    super();

    this.contents = document.createElement("div");
    this.contents.style["padding"] = "10px";
    this.contents.style["margin"] = "10px";
    this.contents.style["overflow"] = "scroll";

    this.shadow.appendChild(this.contents);
    this._value = "";
  }

  hideScrollBars() {
    this.contents.style["overflow"] = "hidden"
  }

  showScrollBars() {
    this.contents.style["overflow"] = "scroll"
  }

  //transforms text into final html form
  //note that client code is allowed to override this directly
  textTransform(text) {
    return text;
  }
  set value(val) {
    this._value = val;

    this.contents.innerHTML = this.textTransform(val);
  }

  get value() {
    return this._value;
  }

  updateDataPath() {
    if (!this.hasAttribute("datapath")) {
      return;
    }

    let path = this.getAttribute("datapath");
    let prop = this.getPathMeta(this.ctx, path);

    if (prop === undefined) {
      console.warn("invalid datapath " + path);

      this.internalDisabled = true;
      return;
    }

    this.internalDisabled = false;
    let value = this.getPathValue(this.ctx, path);

    if (value !== this.value) {
      this.value = value;
    }
  }

  update() {
    super.update();

    this.updateDataPath();
  }

  static define() {return {
    tagname : "html-viewer-x",
    style   : "html_viewer"
  }}
}
UIBase.internalRegister(RichViewer);