editor.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. """A Rich Text Editor based on SunEditor."""
  2. from __future__ import annotations
  3. import enum
  4. from typing import Any, Literal
  5. from reflex.base import Base
  6. from reflex.components.component import Component, NoSSRComponent
  7. from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
  8. from reflex.utils.format import to_camel_case
  9. from reflex.utils.imports import ImportDict, ImportVar
  10. from reflex.vars.base import Var
  11. class EditorButtonList(list, enum.Enum):
  12. """List enum that provides three predefined button lists."""
  13. BASIC = [
  14. ["font", "fontSize"],
  15. ["fontColor"],
  16. ["horizontalRule"],
  17. ["link", "image"],
  18. ]
  19. FORMATTING = [
  20. ["undo", "redo"],
  21. ["bold", "underline", "italic", "strike", "subscript", "superscript"],
  22. ["removeFormat"],
  23. ["outdent", "indent"],
  24. ["fullScreen", "showBlocks", "codeView"],
  25. ["preview", "print"],
  26. ]
  27. COMPLEX = [
  28. ["undo", "redo"],
  29. ["font", "fontSize", "formatBlock"],
  30. ["bold", "underline", "italic", "strike", "subscript", "superscript"],
  31. ["removeFormat"],
  32. "/",
  33. ["fontColor", "hiliteColor"],
  34. ["outdent", "indent"],
  35. ["align", "horizontalRule", "list", "table"],
  36. ["link", "image", "video"],
  37. ["fullScreen", "showBlocks", "codeView"],
  38. ["preview", "print"],
  39. ["save", "template"],
  40. ]
  41. class EditorOptions(Base):
  42. """Some of the additional options to configure the Editor.
  43. Complete list of options found here:
  44. https://github.com/JiHong88/SunEditor/blob/master/README.md#options.
  45. """
  46. # Specifies default tag name of the editor.
  47. # default: 'p' {String}
  48. default_tag: str | None = None
  49. # The mode of the editor ('classic', 'inline', 'balloon', 'balloon-always').
  50. # default: 'classic' {String}
  51. mode: str | None = None
  52. # If true, the editor is set to RTL(Right To Left) mode.
  53. # default: false {Boolean}
  54. rtl: bool | None = None
  55. # List of buttons to use in the toolbar.
  56. button_list: list[list[str] | str] | None
  57. def on_blur_spec(e: Var, content: Var[str]) -> tuple[Var[str]]:
  58. """A helper function to specify the on_blur event handler.
  59. Args:
  60. e: The event.
  61. content: The content of the editor.
  62. Returns:
  63. A tuple containing the content of the editor.
  64. """
  65. return (content,)
  66. def on_paste_spec(
  67. e: Var, clean_data: Var[str], max_char_count: Var[bool]
  68. ) -> tuple[Var[str], Var[bool]]:
  69. """A helper function to specify the on_paste event handler.
  70. Args:
  71. e: The event.
  72. clean_data: The clean data.
  73. max_char_count: The maximum character count.
  74. Returns:
  75. A tuple containing the clean data and the maximum character count.
  76. """
  77. return (clean_data, max_char_count)
  78. class Editor(NoSSRComponent):
  79. """A Rich Text Editor component based on SunEditor.
  80. Not every JS prop is listed here (some are not easily usable from python),
  81. refer to the library docs for a complete list.
  82. """
  83. library = "suneditor-react"
  84. tag = "SunEditor"
  85. is_default = True
  86. lib_dependencies: list[str] = ["suneditor"]
  87. # Language of the editor.
  88. # Alternatively to a string, a dict of your language can be passed to this prop.
  89. # Please refer to the library docs for this.
  90. # options: "en" | "da" | "de" | "es" | "fr" | "ja" | "ko" | "pt_br" |
  91. # "ru" | "zh_cn" | "ro" | "pl" | "ckb" | "lv" | "se" | "ua" | "he" | "it"
  92. # default: "en".
  93. lang: Var[
  94. Literal[
  95. "en",
  96. "da",
  97. "de",
  98. "es",
  99. "fr",
  100. "ja",
  101. "ko",
  102. "pt_br",
  103. "ru",
  104. "zh_cn",
  105. "ro",
  106. "pl",
  107. "ckb",
  108. "lv",
  109. "se",
  110. "ua",
  111. "he",
  112. "it",
  113. ]
  114. | dict
  115. ]
  116. # This is used to set the HTML form name of the editor.
  117. # This means on HTML form submission,
  118. # it will be submitted together with contents of the editor by the name provided.
  119. name: Var[str]
  120. # Sets the default value of the editor.
  121. # This is useful if you don't want the on_change method to be called on render.
  122. # If you want the on_change method to be called on render please use the set_contents prop
  123. default_value: Var[str]
  124. # Sets the width of the editor.
  125. # px and percentage values are accepted, eg width="100%" or width="500px"
  126. # default: 100%
  127. width: Var[str]
  128. # Sets the height of the editor.
  129. # px and percentage values are accepted, eg height="100%" or height="100px"
  130. height: Var[str]
  131. # Sets the placeholder of the editor.
  132. placeholder: Var[str]
  133. # Should the editor receive focus when initialized?
  134. auto_focus: Var[bool]
  135. # Pass an EditorOptions instance to modify the behaviour of Editor even more.
  136. set_options: Var[dict]
  137. # Whether all SunEditor plugins should be loaded.
  138. # default: True.
  139. set_all_plugins: Var[bool]
  140. # Set the content of the editor.
  141. # Note: To set the initial contents of the editor
  142. # without calling the on_change event,
  143. # please use the default_value prop.
  144. # set_contents is used to set the contents of the editor programmatically.
  145. # You must be aware that, when the set_contents's prop changes,
  146. # the on_change event is triggered.
  147. set_contents: Var[str]
  148. # Append editor content
  149. append_contents: Var[str]
  150. # Sets the default style of the editor's edit area
  151. set_default_style: Var[str]
  152. # Disable the editor
  153. # default: False.
  154. disable: Var[bool]
  155. # Hide the editor
  156. # default: False.
  157. hide: Var[bool]
  158. # Hide the editor toolbar
  159. # default: False.
  160. hide_toolbar: Var[bool]
  161. # Disable the editor toolbar
  162. # default: False.
  163. disable_toolbar: Var[bool]
  164. # Fired when the editor content changes.
  165. on_change: EventHandler[passthrough_event_spec(str)]
  166. # Fired when the something is inputted in the editor.
  167. on_input: EventHandler[no_args_event_spec]
  168. # Fired when the editor loses focus.
  169. on_blur: EventHandler[on_blur_spec]
  170. # Fired when the editor is loaded.
  171. on_load: EventHandler[passthrough_event_spec(bool)]
  172. # Fired when the editor content is copied.
  173. on_copy: EventHandler[no_args_event_spec]
  174. # Fired when the editor content is cut.
  175. on_cut: EventHandler[no_args_event_spec]
  176. # Fired when the editor content is pasted.
  177. on_paste: EventHandler[on_paste_spec]
  178. # Fired when the code view is toggled.
  179. toggle_code_view: EventHandler[passthrough_event_spec(bool)]
  180. # Fired when the full screen mode is toggled.
  181. toggle_full_screen: EventHandler[passthrough_event_spec(bool)]
  182. def add_imports(self) -> ImportDict:
  183. """Add imports for the Editor component.
  184. Returns:
  185. The import dict.
  186. """
  187. return {
  188. "": ImportVar(tag="suneditor/dist/css/suneditor.min.css", install=False)
  189. }
  190. @classmethod
  191. def create(
  192. cls, set_options: EditorOptions | None = None, **props: Any
  193. ) -> Component:
  194. """Create an instance of Editor. No children allowed.
  195. Args:
  196. set_options: Configuration object to further configure the instance.
  197. **props: Any properties to be passed to the Editor
  198. Returns:
  199. An Editor instance.
  200. Raises:
  201. ValueError: If set_options is a state Var.
  202. """
  203. if set_options is not None:
  204. if isinstance(set_options, Var):
  205. raise ValueError("EditorOptions cannot be a state Var")
  206. props["set_options"] = {
  207. to_camel_case(k): v
  208. for k, v in set_options.dict().items()
  209. if v is not None
  210. }
  211. return super().create(*[], **props)