diff --git a/CHANGELOG.md b/CHANGELOG.md index 92f7a31c..3dac1735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased + +## Changes +* Quill editor supports a toolbar and theme set at runtime. + https://github.com/anvilistas/anvil-extras/pull/80 # v1.4 07-June-2021 ## New Features diff --git a/client_code/Quill/__init__.py b/client_code/Quill/__init__.py index ba7f72d8..6a1d17e0 100644 --- a/client_code/Quill/__init__.py +++ b/client_code/Quill/__init__.py @@ -40,30 +40,76 @@ } +def _quill_init_prop(propname): + def getter(self): + return self._props[propname] + + def setter(self, val): + self._props[propname] = val + self._init_quill() + + return property(getter, setter) + + class Quill(QuillTemplate): _quill = None # otherwise we get a recursion error from __getattr__ def __init__(self, **properties): # Set Form properties and Data Bindings. - dom_node = self._dom_node = _get_dom_node(self) - self._rt = None - ## remove html that was used for the designer - prevents script tags loading - while dom_node.firstElementChild: - dom_node.removeChild(dom_node.firstElementChild) + self._dom_node = _get_dom_node(self) self._spacer = _Spacer() self._el = _get_dom_node(self._spacer) - self.add_component(self._spacer) + self._quill = None + self._rt = None + self._min_height = None + + def click_guard(e): + q = self._quill + if not q.hasFocus(): + q.focus() + q.setSelection(len(q.getText())) + + self._el.addEventListener("click", click_guard) - properties = _defaults | properties + self._props = props = _defaults | properties + props_to_init = { + key: props[key] + for key in ( + "height", + "content", + "auto_expand", + "spacing_above", + "spacing_below", + ) + } + init_if_false = { + key: props[key] for key in ("enabled", "visible") if not props[key] + } + self._init_quill() + self.init_components(**props_to_init, **init_if_false) + + @staticmethod + def _clear_elements(el): + while el.firstElementChild: + el.removeChild(el.firstElementChild) + + def _init_quill(self): + html = self.get_html() + + self._spacer.remove_from_parent() + self._clear_elements(self._dom_node) + self._clear_elements(self._el) + self.add_component(self._spacer) # these properties have to be set for initialization and cannot be changed + # If they are changed at runtime we need to create a new quill object q = self._quill = _Quill( self._el, { - "modules": {"toolbar": properties["toolbar"]}, - "theme": properties["theme"], - "placeholder": properties["placeholder"], - "readOnly": properties["readonly"], + "modules": {"toolbar": self._props["toolbar"]}, + "theme": self._props["theme"], + "placeholder": self._props["placeholder"], + "readOnly": self._props["readonly"], "bounds": self._dom_node, }, ) @@ -82,14 +128,8 @@ def __init__(self, **properties): ), ) - def click_guard(e): - if not q.hasFocus(): - q.focus() - q.setSelection(len(q.getText())) - - self._el.addEventListener("click", click_guard) - # Any code you write here will run when the form opens. - self.init_components(**properties) + if html: + self.set_html(html) def __getattr__(self, name): init, *rest = name.split("_") @@ -99,12 +139,12 @@ def __getattr__(self, name): #### Properties #### @property def enabled(self): - return self._enabled + return self._props["enabled"] @enabled.setter def enabled(self, value): self._quill.enable(bool(value)) - self._enabled = value + self._props["enabled"] = value @property def content(self): @@ -120,19 +160,20 @@ def content(self, value): @property def auto_expand(self): - return self._auto_expand + return self._props["auto_expand"] @auto_expand.setter def auto_expand(self, value): - self._auto_expand = value + self._props["auto_expand"] = value self._spacer.height = "auto" if value else self._min_height @property def height(self): - return self._min_height + return self._props["height"] @height.setter def height(self, value): + self._props["height"] = value if isinstance(value, (int, float)) or value.isdigit(): value = f"{value}px" self._el.style.minHeight = value @@ -142,6 +183,12 @@ def height(self, value): spacing_below = _spacing_property("below") visible = _HtmlPanel.visible + #### QUILL INIT PROPS #### + toolbar = _quill_init_prop("toolbar") + readonly = _quill_init_prop("readonly") + theme = _quill_init_prop("theme") + placeholder = _quill_init_prop("placeholder") + #### ANVIL METHODS #### def get_markdown(self): """Not yet implemented""" @@ -153,7 +200,7 @@ def get_html(self): """convert the contents of the quill object to html which can be used as the content to a RichText editor in 'restricted_html' format Can also be used as a classmethod by calling it with a simple object Quill.to_html(content)""" - return self._quill.root.innerHTML + return self._quill and self._quill.root.innerHTML def set_html(self, html): """set the content to html. This method sanitizes the html @@ -161,4 +208,4 @@ def set_html(self, html): self._rt = self._rt or _RT(visible=False, format="restricted_html") self._rt.content = html html = _get_dom_node(self._rt).innerHTML - self._quill.clipboard.dangerouslyPasteHTML(html) + self._quill.root.innerHTML = html diff --git a/docs/guides/components/quill.rst b/docs/guides/components/quill.rst index a44bb844..3582fb5e 100644 --- a/docs/guides/components/quill.rst +++ b/docs/guides/components/quill.rst @@ -33,7 +33,7 @@ Properties :readonly: Boolean - Check the Quill docs. This property cannot be updated after the Quill editor has been instantiated. + Check the Quill docs. :spacing_above: String @@ -45,12 +45,11 @@ Properties :theme: String - Quill supports ``"snow"`` or ``"bubble"`` theme. (Cannot be updated after instantiation). + Quill supports ``"snow"`` or ``"bubble"`` theme. :toolbar: Boolean or Object - Check the Quill docs. This property cannot be updated after the Quill editor has been instantiated. - If you want to use an Object you should construct the Quill instance in code. + Check the Quill docs. If you want to use an Object you can set this at runtime. See quill docs for examples. :visible: Boolean