From cebf6910c37a4915fa8a85bf5c46f3538f781eee Mon Sep 17 00:00:00 2001 From: jh Date: Sat, 20 May 2023 01:44:55 +0200 Subject: [PATCH 1/2] feat: Add Editor component This adds a Rich Text Editor --- pynecone/components/forms/editor.py | 206 ++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 pynecone/components/forms/editor.py diff --git a/pynecone/components/forms/editor.py b/pynecone/components/forms/editor.py new file mode 100644 index 0000000000..d6a8308d1e --- /dev/null +++ b/pynecone/components/forms/editor.py @@ -0,0 +1,206 @@ +"""A Rich Text Editor based on SunEditor.""" + +import enum +from typing import Dict, List, Optional + +from pynecone.base import Base +from pynecone.components.component import Component +from pynecone.event import EVENT_ARG +from pynecone.vars import Var + + +class EditorButtonList(list, enum.Enum): + """List enum that provides three predefined button lists.""" + + BASIC = [ + ["font", "fontSize"], + ["fontColor"], + ["horizontalRule"], + ["link", "image"], + ] + FORMATTING = [ + ["undo", "redo"], + ["bold", "underline", "italic", "strike", "subscript", "superscript"], + ["removeFormat"], + ["outdent", "indent"], + ["fullScreen", "showBlocks", "codeView"], + ["preview", "print"], + ] + COMPLEX = [ + ["undo", "redo"], + ["font", "fontSize", "formatBlock"], + ["bold", "underline", "italic", "strike", "subscript", "superscript"], + ["removeFormat"], + "/", + ["fontColor", "hiliteColor"], + ["outdent", "indent"], + ["align", "horizontalRule", "list", "table"], + ["link", "image", "video"], + ["fullScreen", "showBlocks", "codeView"], + ["preview", "print"], + ["save", "template"], + ] + + +class EditorOptions(Base): + """Some of the additional options to configure the Editor. + Complete list of options found here: + https://github.com/JiHong88/SunEditor/blob/master/README.md#options. + """ + + # Specifies default tag name of the editor. + # default: 'p' {String} + default_tag: Optional[str] = None + + # The mode of the editor ('classic', 'inline', 'balloon', 'balloon-always'). + # default: 'classic' {String} + mode: Optional[str] = None + + # If true, the editor is set to RTL(Right To Left) mode. + # default: false {Boolean} + rtl: Optional[bool] = None + + # List of buttons to use in the toolbar. + button_list: Optional[List[List[str] | str]] + + +class Editor(Component): + """A Rich Text Editor component based on SunEditor. + Not every JS prop is listed here (some are not easily usable from python), + refer to the library docs for a complete list. + """ + + library = "suneditor-react" + + tag = "SunEditor" + + # Language of the editor. + # Alternatively to a string, a dict of your language can be passed to this prop. + # Please refer to the library docs for this. + # options: "en" | "da" | "de" | "es" | "fr" | "ja" | "ko" | "pt_br" | + # "ru" | "zh_cn" | "ro" | "pl" | "ckb" | "lv" | "se" | "ua" | "he" | "it" + # default : "en" + lang: Var[str | dict] + + # This is used to set the HTML form name of the editor. + # This means on HTML form submission, + # it will be submitted together with contents of the editor by the name provided. + name: Var[str] + + # Sets the default value of the editor. + # This is useful if you don't want the on_change method to be called on render. + # If you want the on_change method to be called on render please use the set_contents prop + default_value: Var[str] + + # Sets the width of the editor. + # px and percentage values are accepted, eg width="100%" or width="500px" + # default: 100% + width: Var[str] + + # Sets the height of the editor. + # px and percentage values are accepted, eg height="100%" or height="100px" + height: Var[str] + + # Sets the placeholder of the editor. + placeholder: Var[str] + + # Should the editor receive focus when initialized? + auto_focus: Var[bool] + + # Pass an EditorOptions instance to modify the behaviour of Editor even more. + set_options: Var[Dict] + + # Whether all SunEditor plugins should be loaded. + # default: True + set_all_plugins: Var[bool] + + # Set the content of the editor. + # Note: To set the initial contents of the editor + # without calling the on_change event, + # please use the default_value prop. + # set_contents is used to set the contents of the editor programmatically. + # You must be aware that, when the set_contents's prop changes, + # the on_change event is triggered. + set_contents: Var[str] + + # Append editor content + append_contents: Var[str] + + # Sets the default style of the editor's edit area + set_default_style: Var[str] + + # Disable the editor + # default: False + disable: Var[bool] + + # Hide the editor + # default: False + hide: Var[bool] + + # Hide the editor toolbar + # default: False + hide_toolbar: Var[bool] + + # Disable the editor toolbar + # default: False + disable_toolbar: Var[bool] + + def _get_imports(self): + return {} + + def _get_custom_code(self) -> str: + return """import dynamic from 'next/dynamic'; +import 'suneditor/dist/css/suneditor.min.css'; +const SunEditor = dynamic(() => import('suneditor-react'), { ssr: false });""" + + def get_controlled_triggers(self): + """Get the event triggers that pass the component's value to the handler. + + Returns: + A dict mapping the event trigger to the var that is passed to the handler. + """ + return { + "on_change": EVENT_ARG.target.value, + "on_scroll": None, + "on_click": None, + "on_mouse_down": None, + "on_input": None, + "on_key_up": None, + "on_key_down": None, + "on_focus": None, + "on_blur": None, + "on_drop": None, + "on_image_upload_before": None, + "on_image_upload": None, + "on_image_upload_error": None, + "on_video_upload_before": None, + "on_video_upload": None, + "on_video_upload_error": None, + "on_audio_upload_before": None, + "on_audio_upload": None, + "on_audio_upload_error": None, + "on_resize_editor": None, + "on_copy": None, + "on_cut": None, + "on_paste": None, + "image_upload_handler": None, + "toggle_code_view": None, + "toggle_full_screen": None, + "show_inline": None, + "show_controller": None, + } + + @classmethod + def create(cls, set_options: Optional[EditorOptions] = None, **props) -> Component: + """Create an instance of Editor. No children allowed. + + Args: + set_options(Optional[EditorOptions]): Configuration object to further configure the instance. + **props: Any properties to be passed to the Editor + + Returns: + An Editor instance. + """ + if set_options is not None: + props["set_options"] = set_options.dict() + return super().create(*[], **props) From d9f892e9c3cae8f73f347480b7d257cb0a6e5c38 Mon Sep 17 00:00:00 2001 From: jh Date: Sat, 20 May 2023 01:45:23 +0200 Subject: [PATCH 2/2] chore: Add Editor and related classes to __init__.py files --- pynecone/components/__init__.py | 3 +++ pynecone/components/forms/__init__.py | 1 + 2 files changed, 4 insertions(+) diff --git a/pynecone/components/__init__.py b/pynecone/components/__init__.py index 7dfb7cd5e2..aa840f8d33 100644 --- a/pynecone/components/__init__.py +++ b/pynecone/components/__init__.py @@ -210,3 +210,6 @@ script = ScriptTag.create aspect_ratio = AspectRatio.create kbd = KeyboardKey.create +editor = Editor.create +editor_options = EditorOptions +editor_button_list = EditorButtonList diff --git a/pynecone/components/forms/__init__.py b/pynecone/components/forms/__init__.py index 4fd13b9337..7920732fc2 100644 --- a/pynecone/components/forms/__init__.py +++ b/pynecone/components/forms/__init__.py @@ -4,6 +4,7 @@ from .checkbox import Checkbox, CheckboxGroup from .copytoclipboard import CopyToClipboard from .editable import Editable, EditableInput, EditablePreview, EditableTextarea +from .editor import Editor, EditorButtonList, EditorOptions from .formcontrol import Form, FormControl, FormErrorMessage, FormHelperText, FormLabel from .iconbutton import IconButton from .input import Input, InputGroup, InputLeftAddon, InputRightAddon