{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "\n",
    "pn.extension('texteditor')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``TextEditor`` widget provides a WYSIWYG (what-you-see-is-what-you-get) rich text editor into a Panel application which outputs HTML. The editor is built on top of the [Quill.js](https://quilljs.com/) library.\n",
    "\n",
    "#### Parameters:\n",
    "\n",
    "For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n",
    "\n",
    "* **``disabled``** (boolean): Whether the editor is disabled\n",
    "* **``mode``** (str): Whether to display a `'toolbar'` or a `'bubble'` menu on highlight.\n",
    "* **``on_keyup``** (boolean): Whether to update `value` on every key press or only upon loss of focus / hotkeys (`Ctrl+Enter` or `Cmd+Enter`). Defaults to `True`.\n",
    "* **``placeholder``** (str): Placeholder output to render when the editor is empty.\n",
    "* **``selection``** (dict): The current text selection in the editor, as `{\"text\": \"...\"}` when the user has a non-empty selection, else `{}`. Updates live.\n",
    "* **``toolbar``** (boolean or list): Toolbar configuration either as a boolean toggle or a configuration specified as a list.\n",
    "* **``value``** (str): The current HTML output of the widget if `on_keyup`. Otherwise, only updated on loss of focus or on `Ctrl+Enter` / `Cmd+Enter`.\n",
    "* **``value_input``** (str): The current HTML output updated on every key press. Identical to `value` if `on_keyup`.\n",
    "\n",
    "\n",
    "##### Display\n",
    "\n",
    "* **`label`** (str): The title of the widget\n",
    "* **`name`** (str): Deprecated alias for ``label``; use ``label`` instead.\n",
    "\n",
    "___"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To construct a `TextEditor` editor widget we must declare it explicitly and may provide a `placeholder` as pure text or a `value` as HTML:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "wysiwyg = pn.widgets.TextEditor(placeholder='Enter some text')\n",
    "wysiwyg"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The current state of the editor output is reflected on the `value` parameter:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "wysiwyg.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `value` may also be set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "wysiwyg.value = '<h1>A title</h1>'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Toolbar\n",
    "\n",
    "The toolbar of the editor can be configured in a variety of ways, the simplest of which is simply toggling it on and off by setting `toolbar=True/False`. Beyond that we can provide the formatting options to display in a number of configurations which are explained in the [quill.js documentation](https://quilljs.com/docs/modules/toolbar/#container). The examples below"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.config.sizing_mode = 'stretch_width'\n",
    "\n",
    "# Flat list of options\n",
    "flat = pn.widgets.TextEditor(toolbar=['bold', 'italic', 'underline'])\n",
    "\n",
    "# Grouped options\n",
    "grouped = pn.widgets.TextEditor(toolbar=[['bold', 'italic'], ['link', 'image']])\n",
    "\n",
    "# Dropdown of options\n",
    "dropdown = pn.widgets.TextEditor(toolbar=[{'size': [ 'small', False, 'large', 'huge' ]}])\n",
    "\n",
    "# Full configuration\n",
    "full_config = pn.widgets.TextEditor(toolbar=[\n",
    "  ['bold', 'italic', 'underline', 'strike'],        # toggled buttons\n",
    "  ['blockquote', 'code-block'],\n",
    "\n",
    "  [{ 'header': 1 }, { 'header': 2 }],               # custom button values\n",
    "  [{ 'list': 'ordered'}, { 'list': 'bullet' }],\n",
    "  [{ 'script': 'sub'}, { 'script': 'super' }],      # superscript/subscript\n",
    "  [{ 'indent': '-1'}, { 'indent': '+1' }],          # outdent/indent\n",
    "  [{ 'direction': 'rtl' }],                         # text direction\n",
    "\n",
    "  [{ 'size': ['small', False, 'large', 'huge'] }],  # custom dropdown\n",
    "  [{ 'header': [1, 2, 3, 4, 5, 6, False] }],\n",
    "\n",
    "  [{ 'color': [] }, { 'background': [] }],          # dropdown with defaults from theme\n",
    "  [{ 'font': [] }],\n",
    "  [{ 'align': [] }],\n",
    "\n",
    "  ['clean']                                         # remove formatting button\n",
    "])\n",
    "\n",
    "pn.Column(\n",
    "    pn.Row(flat, grouped, dropdown),\n",
    "    full_config\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Instead of a toolbar we can also switch to `'bubble'` mode which displays a hover menu when highlighting the text:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.widgets.TextEditor(mode='bubble', value='Highlight me to edit formatting', margin=(40, 0, 0, 0), height=200, width=400)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Deferred commits with `on_keyup`\n",
    "\n",
    "By default `value` updates on every keystroke. Setting `on_keyup=False` defers updates to `value` until the editor loses focus or the user presses `Ctrl+Enter` / `Cmd+Enter`. Use this when downstream callbacks are expensive or when you want to batch edits. The `value_input` parameter always streams live, so you can bind to it for draft-state previews while committing only finalized content through `value`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "deferred = pn.widgets.TextEditor(\n",
    "    placeholder='Type, then click away or press Ctrl/Cmd+Enter',\n",
    "    on_keyup=False,\n",
    "    height=150,\n",
    ")\n",
    "\n",
    "def show_value(v):\n",
    "    return f'**Committed `value`:**\\n\\n```html\\n{v}\\n```'\n",
    "\n",
    "def show_value_input(v):\n",
    "    return f'**Live `value_input`:**\\n\\n```html\\n{v}\\n```'\n",
    "\n",
    "pn.Column(\n",
    "    deferred,\n",
    "    pn.Row(\n",
    "        pn.bind(show_value, deferred.param.value),\n",
    "        pn.bind(show_value_input, deferred.param.value_input),\n",
    "    ),\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tracking text selection\n",
    "\n",
    "The `selection` parameter reflects the current text selection in the editor. It updates live as the user highlights text, and becomes an empty dict when the selection is cleared:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "selectable = pn.widgets.TextEditor(\n",
    "    value='<p>Highlight any part of this sentence to see the selection update below.</p>',\n",
    "    height=150,\n",
    ")\n",
    "\n",
    "def show_selection(sel):\n",
    "    if not sel:\n",
    "        return '*(nothing selected)*'\n",
    "    return f'**Selected text:** `{sel.get(\"text\", \"\")}`'\n",
    "\n",
    "pn.Column(\n",
    "    selectable,\n",
    "    pn.bind(show_selection, selectable.param.selection),\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Controls\n",
    "\n",
    "The `TextEditor` widget exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "editor = pn.widgets.TextEditor(placeholder='Enter some text')\n",
    "\n",
    "pn.Row(editor.controls(jslink=True, sizing_mode='fixed'), editor)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
