import React from "react"; import PropTypes from "prop-types"; import RichTextToolbarButton from "./RichTextToolbarButton"; export default class RichTextArea extends React.Component { constructor(props) { super(props); this.state = { value: props.value || "", rows: props.rows || 5 }; this.inputRef = React.createRef(); this.handleChange = this.handleChange.bind(this); this.handleKeyPress = this.handleKeyPress.bind(this); this.getSelection = this.getSelection.bind(this); this.link = this.link.bind(this); this.replaceSelection = this.replaceSelection.bind(this); } actions() { const simple = [ { name: "bold", className: "bold", hotkey: "b", fn: (str) => ["", str, ""] }, { name: "italic", className: "italic", hotkey: "i", fn: (str) => ["", str, ""] }, ]; const advanced = [ { name: "Heading 2", className: "header h2", fn: (str) => ["h2. ", str, ""] }, { name: "Heading 3", className: "header h3", fn: (str) => ["h3. ", str, ""] }, { name: "Heading 4", className: "header h4", fn: (str) => ["h4. ", str, ""] }, { name: "Blockquote", className: "quote-left", fn: (str) => ["bq. ", str, ""] }, { name: "List", className: "list-ul", fn: (str) => ["", this.strToList(str, "*"), ""] }, { name: "Ordered list", className: "list-ol", fn: (str) => ["", this.strToList(str, "#"), ""] }, { name: "Link", className: "link", fn: this.link }, { name: "Email link", className: "envelope", fn: this.emailLink }, ]; return this.props.simple ? simple : [...simple, ...advanced]; } applyAction(fn) { let [prefix, replacement, postfix] = fn(this.getSelection()); this.replaceSelection(prefix, replacement, postfix); } emailLink(selection) { var address = prompt("Enter email address", ""); let name = selection.length > 0 ? selection : address; return ["\"", name, `":mailto:${address}`]; } getSelection() { let { selectionStart, selectionEnd, value } = this.inputRef.current; return value.substr(selectionStart, (selectionEnd - selectionStart)); } handleChange(evt) { this.setState({ value: evt.target.value }); } handleKeyPress(evt) { let key; if (evt.which >= 65 && evt.which <= 90) { key = String.fromCharCode(evt.keyCode).toLowerCase(); } else if (evt.keyCode === 13) { key = "enter"; } let hotkeys = {}; this.actions().forEach(a => { if (a.hotkey) { hotkeys[a.hotkey] = a.fn; } }); if ((evt.metaKey || evt.ctrlKey) && Object.prototype.hasOwnProperty.call(hotkeys, key)) { evt.preventDefault(); this.applyAction(hotkeys[key]); } } link(selection) { let name = selection.length > 0 ? selection : "Link text"; var url = prompt("Enter link URL", ""); if (url) { return ["\"", name, `":${this.relativeUrl(url)}`]; } else { return ["", name, ""]; } } localeOptions() { let opts = {}; if (this.props.lang) { opts.lang = this.props.lang; } if (this.props.dir) { opts.dir = this.props.dir; } return opts; } relativeUrl(str) { let url = null; if (!str.match(/^https:\/\//) || !document || !document.location) { return str; } try { url = new URL(str); } catch (error) { console.log("Error parsing URL: ", error); } if (url && url.hostname == document.location.hostname && (document.location.port || "80") == (url.port || "80")) { return url.pathname; } return str; } render() { let { value, rows } = this.state; let { id, name } = this.props; const clickHandler = (fn) => (evt) => { evt.preventDefault(); this.applyAction(fn); }; return (