All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hyperscala.ui.widgets.RichEditor.scala Maven / Gradle / Ivy

There is a newer version: 0.10.3
Show newest version
package org.hyperscala.ui.widgets

import org.hyperscala.html._
import org.hyperscala.web._
import org.hyperscala.realtime.{RealtimePage, Realtime}
import org.hyperscala.module.Module
import org.powerscala.{Version, StorageComponent}
import org.hyperscala.jquery.jQuery
import org.hyperscala.web.WrappedComponent
import org.powerscala.event.Intercept
import org.powerscala.property.Property
import org.hyperscala.javascript.JavaScriptContent
import org.hyperscala.{Container, IdentifiableTag}
import org.hyperscala.html.attributes.ContentEditable
import org.hyperscala.io.HTMLToScala
import org.hyperscala.css.Style
import org.hyperscala.css.attributes.{Length, Alignment, FontSize}
import org.hyperscala.ui.clipboard.{ClipType, Clipboard}
import org.powerscala.enum.{Enumerated, EnumEntry}
import org.hyperscala.javascript.dsl.JSFunction1
import com.outr.net.http.session.Session

/**
 * @author Matt Hicks 
 */
object RichEditor extends Module with StorageComponent[RichEditor, HTMLTag] {
  val BoldStyle = RichEditorStyle("strong", overrides = List(Override("b")))
  val ItalicStyle = RichEditorStyle("em", overrides = List(Override("i")))
  val UnderlineStyle = RichEditorStyle("u")
  val StrikeStyle = RichEditorStyle("s", overrides = List(Override("strike")))
  val SubscriptStyle = RichEditorStyle("sub")
  val SuperscriptStyle = RichEditorStyle("sup")
  def FontSizeStyle(size: FontSize) = RichEditorStyle("span", styles = Map("font-size" -> size), overrides = List(Override("font", Map("size" -> null))))
  def TextAlignStyle(alignment: Alignment) = RichEditorStyle("span", styles = Map("text-align" -> alignment.value))
  def LineHeightStyle(length: Length) = RichEditorStyle("span", styles = Map("line-height" -> length.value))

  def name = "RichEditor"
  def version = Version(2)

  override def dependencies = List(jQuery.LatestWithDefault, Realtime, CKEditor)

  override def init[S <: Session](website: Website[S]) = {
    website.register("/js/rich_editor.js", "rich_editor.js")
  }

  override def load[S <: Session](webpage: Webpage[S]) = {
    webpage.head.contents += new tag.Script(mimeType = "text/javascript", src = "/js/rich_editor.js")
  }

  override def apply(t: HTMLTag) = {
    t.require(this)
    super.apply(t)
  }

  protected def create(t: HTMLTag) = new RichEditor(t)
}

class RichEditor private(val wrapped: HTMLTag, val autoInit: Boolean = true) extends WrappedComponent[HTMLTag] {
  import RichEditor._

  /**
   * Configures the Clipboard module to integrate with this editor.
   */
  def enableClipboard() = {
    wrapped.require(Clipboard)

    Clipboard.connect(wrapped)
    wrapped.connected[Webpage[_ <: Session]] {
      case webpage => {
        Clipboard(webpage).configureDefaultHandling()

        Clipboard(webpage).clientEvent.on {
          case evt => if (evt.element.getOrElse(null) == wrapped) {
            evt.clipType match {
              case ClipType.Cut => delete()
              case ClipType.Copy => // Default handling will take care of this
              case ClipType.Paste => {
                Clipboard(webpage).headOption match {
                  case Some(entry) => {
                    insert(entry.value.toString, InsertMode.UnfilteredHTML)
                  }
                  case None => // Nothing in the clipboard to paste
                }
              }
            }
          }
        }
      }
    }

  }

  /**
   * Whether this editor should be inlined (use contenteditable) or the standard ckeditor. If using the standard
   * ckeditor an iframe will be used for the content and the toolbar will appear above the editor. When inlined the
   * content will be edited directly and the toolbar will float when editing.
   *
   * This property will not do anything after the editor has been instantiated.
   *
   * Defaults to true.
   */
  lazy val inline = Property[Boolean](default = Some(true))

  /**
   * The frequency at which the content is validated for changes in milliseconds. If this is set to 0 the content
   * will only be validated upon blur.
   *
   * Defaults to 1000.
   */
  lazy val validateFrequency = property[Long]("validateFrequency", 1000)

  /**
   * Whether this editor is read-only.
   *
   * Defaults to false.
   */
  lazy val readOnly = property[Boolean]("readOnly", false)

  /**
   * Reflects the HTML content of the editor.
   *
   * Defaults to the innerHTML value of wrapped.
   */
  lazy val html = property[String]("html", IdentifiableTag.ignoreIds(wrapped.innerHTML))

  /**
   * Updates the wrapped tag's content when the content is modified if this is set to true.
   *
   * Defaults to false.
   */
  lazy val updateWrapped = Property[Boolean](default = Some(false))

  /**
   * Defines whether the native toolbar should be displayed.
   *
   * Defaults to false.
   */
  lazy val showToolbar = property[Boolean]("showToolbar", false)

  /**
   * Defines whether the path navigation in the bottom bar should be displayed. This is only applicable when not
   * inline.
   *
   * Defaults to true.
   */
  lazy val showPath = property[Boolean]("showPath", true)

  /**
   * Defines whether the resizer in the bottom bar should be displayed. This is only applicable when not inline.
   *
   * Defaults to true.
   */
  lazy val showResizer = property[Boolean]("showResizer", true)

  lazy val showSaveButton = property[Boolean]("showToolbarButtonsave", true)
  lazy val showNewPageButton = property[Boolean]("showToolbarButtonnewpage", true)
  lazy val showPreviewButton = property[Boolean]("showToolbarButtonpreview", true)
  lazy val showPrintButton = property[Boolean]("showToolbarButtonprint", true)
  lazy val showTemplatesButton = property[Boolean]("showToolbarButtontemplates", true)
  lazy val showCutButton = property[Boolean]("showToolbarButtoncut", true)
  lazy val showCopyButton = property[Boolean]("showToolbarButtoncopy", true)
  lazy val showPasteButton = property[Boolean]("showToolbarButtonpaste", true)
  lazy val showPasteTextButton = property[Boolean]("showToolbarButtonpastetext", true)
  lazy val showPasteFromWordButton = property[Boolean]("showToolbarButtonpastefromword", true)
  lazy val showUndoButton = property[Boolean]("showToolbarButtonundo", true)
  lazy val showRedoButton = property[Boolean]("showToolbarButtonredo", true)
  lazy val showFindButton = property[Boolean]("showToolbarButtonfind", true)
  lazy val showReplaceButton = property[Boolean]("showToolbarButtonreplace", true)
  lazy val showSelectAllButton = property[Boolean]("showToolbarButtonselectall", true)
  lazy val showSpellCheckButton = property[Boolean]("showToolbarButtonscayt", true)
  lazy val showFormButton = property[Boolean]("showToolbarButtonform", true)
  lazy val showCheckBoxButton = property[Boolean]("showToolbarButtoncheckbox", true)
  lazy val showRadioButton = property[Boolean]("showToolbarButtonradio", true)
  lazy val showTextFieldButton = property[Boolean]("showToolbarButtontextfield", true)
  lazy val showTextAreaButton = property[Boolean]("showToolbarButtontextarea", true)
  lazy val showSelectButton = property[Boolean]("showToolbarButtonselect", true)
  lazy val showButtonButton = property[Boolean]("showToolbarButtonbutton", true)
  lazy val showImageButtonButton = property[Boolean]("showToolbarButtonimagebutton", true)
  lazy val showHiddenFieldButton = property[Boolean]("showToolbarButtonhiddenfield", true)
  lazy val showAboutButton = property[Boolean]("showToolbarButtonabout", true)

  def showFormButtons(visible: Boolean) = {
    showFormButton := false
    showCheckBoxButton := false
    showRadioButton := false
    showTextFieldButton := false
    showTextAreaButton := false
    showSelectButton := false
    showButtonButton := false
    showImageButtonButton := false
    showHiddenFieldButton := false
  }

  /**
   * Toggles the visibility of the context menu.
   */
  def contextMenu() = execCommand("contextMenu")

  /**
   * Displays CKEDITOR's About dialog.
   */
  def about() = execCommand("about")

  /**
   * Displays accessibility instructions.
   */
  def a11yHelp() = execCommand("a11yHelp")

  /**
   * Applies the specified font size to the selected content.
   *
   * @param size represents the size to apply to the selection.
   */
  def fontSize(size: FontSize) = execApplyStyle(FontSizeStyle(size))

  /**
   * Invokes the supplied action when the value of the font-size changes in the selectoin.
   *
   * @param action the action function that is called when font-size changes with the size as a String or null.
   */
  def onFontSize(action: JSFunction1[String, Unit]) = onStyleValueChange(action, Style.fontSize)

  /**
   * Toggles bold state on the selected content.
   */
  def bold() = execCommand("bold")

  /**
   * Invokes the supplied action when the state of bold in the selection changes.
   *
   * @param action the action to invoke taking a Boolean of the current status.
   */
  def onBold(action: JSFunction1[Boolean, Unit]) = onStyleChange(action, BoldStyle)

  /**
   * Toggles italic state on the selected content.
   */
  def italic() = execCommand("italic")

  /**
   * Invokes the supplied action when the state of italic in the selection changes.
   *
   * @param action the action to invoke taking a Boolean of the current status.
   */
  def onItalic(action: JSFunction1[Boolean, Unit]) = onStyleChange(action, ItalicStyle)

  /**
   * Toggles the underline state on the selected content.
   */
  def underline() = execCommand("underline")

  /**
   * Invokes the supplied action when the state of underline in the selection changes.
   *
   * @param action the action to invoke taking a Boolean of the current status.
   */
  def onUnderline(action: JSFunction1[Boolean, Unit]) = onStyleChange(action, UnderlineStyle)

  /**
   * Toggles the strike-through state on the selected content.
   */
  def strike() = execCommand("strike")

  /**
   * Invokes the supplied action when the state of strike-through in the selection changes.
   *
   * @param action the action to invoke taking a Boolean of the current status.
   */
  def onStrike(action: JSFunction1[Boolean, Unit]) = onStyleChange(action, StrikeStyle)

  /**
   * Toggles the subscript state on the selected content.
   */
  def subscript() = execCommand("subscript")

  /**
   * Invokes the supplied action when the state of subscript in the selection changes.
   *
   * @param action the action to invoke taking a Boolean of the current status.
   */
  def onSubscript(action: JSFunction1[Boolean, Unit]) = onStyleChange(action, SubscriptStyle)

  /**
   * Toggles the superscript state on the selected content.
   */
  def superscript() = execCommand("superscript")

  /**
   * Invokes the supplied action when the state of superscript in the selection changes.
   *
   * @param action the action to invoke taking a Boolean of the current status.
   */
  def onSuperscript(action: JSFunction1[Boolean, Unit]) = onStyleChange(action, SuperscriptStyle)

  /**
   * Specifies the content within the selected range should represent language left-to-right.
   */
  def bidiltr() = execCommand("bidiltr")

  /**
   * Specifies the content within the selected range should represent language right-to-left.
   */
  def bidirtl() = execCommand("bidirtl")

  /**
   * Toggle blockquote status.
   */
  def blockquote() = execCommand("blockquote")

  /**
   * Cuts the selected range and stores in the clipboard. This requires browser security permissions set to allow
   * access to the clipboard from JavaScript.
   */
  def cut() = execCommand("cut")

  /**
   * Copies the selected range and stores in the clipboard. This requires browser security permissions set to allow
   * access to the clipboard from JavaScript.
   */
  def copy() = execCommand("copy")

  /**
   * Pastes the selected range from the clipboard. This requires browser security permissions set to allow
   * access to the clipboard from JavaScript.
   */
  def paste() = execCommand("paste")

  /**
   * Displays the color dialog.
   *
   * TODO: figure out how to leverage the resulting color.
   */
  def colorDialog() = execCommand("colordialog")

  /**
   * Display templates dialog. This allows changing the layout of the content area to reflect a predefined template.
   */
  def templates() = execCommand("templates")

  /**
   * Displays the create div dialog.
   *
   * TODO: figure out if this actually inserts the div?
   */
  def createDiv() = execCommand("creatediv")

  /**
   * TODO: figure out how this works
   */
  def editDiv() = execCommand("editdiv")

  /**
   * TODO: figure out how this works
   */
  def removeDiv() = execCommand("removediv")

  /**
   * Gives focus to the toolbar.
   */
  def toolbarFocus() = execCommand("toolbarFocus")

  /**
   * Mimics pressing the enter key on the keyboard in the editor.
   */
  def enter() = execCommand("enter")

  /**
   * Mimics pressing the enter key on the keyboard in the editor while holding down the shift key.
   */
  def shiftEnter() = execCommand("shiftEnter")

  /**
   * Displays the find dialog to search the editor for specific content.
   */
  def find() = execCommand("find")

  /**
   * Displays the replace dialog to search the editor for specific content and replace it.
   */
  def replace() = execCommand("replace")

  def flash() = execCommand("flash")

  /**
   * Display form editor popup for the current selection. If a form doesn't already exist a new one will be created.
   */
  def form() = execCommand("form")

  /**
   * Displays the create/edit checkbox dialog.
   */
  def checkbox() = execCommand("checkbox")

  /**
   * Displays the create/edit radio dialog.
   */
  def radio() = execCommand("radio")

  /**
   * Displays the create/edit text field dialog.
   */
  def textField() = execCommand("textfield")

  /**
   * Displays the create/edit text area dialog.
   */
  def textArea() = execCommand("textarea")

  /**
   * Displays the create/edit select dialog.
   */
  def select() = execCommand("select")

  /**
   * Displays the create/edit button dialog.
   */
  def button() = execCommand("button")

  /**
   * Displays the create/edit image button dialog.
   */
  def imageButton() = execCommand("imagebutton")

  /**
   * Displays the create/edit hidden field dialog.
   */
  def hiddenField() = execCommand("hiddenfield")

  /**
   * Inserts a horizontal rule at the selected area.
   */
  def horizontalRule() = execCommand("horizontalrule")

  /**
   * Displays the create/edit iframe dialog.
   */
  def iframe() = execCommand("iframe")

  /**
   * Displays the create/edit image dialog.
   */
  def image() = execCommand("image")

  /**
   * Increases the indentation of the selection.
   */
  def indent() = execCommand("indent")

  /**
   * Decreases the indentation of the selection.
   */
  def outdent() = execCommand("outdent")

  /**
   * Displays the insert smiley dialog.
   */
  def smiley() = execCommand("smiley")

  /**
   * Sets the justification to left for the selection.
   */
  def justifyLeft() = execCommand("justifyleft")

  /**
   * Sets the justification to center for the selection.
   */
  def justifyCenter() = execCommand("justifycenter")

  /**
   * Sets the justification to right for the selection.
   */
  def justifyRight() = execCommand("justifyright")

  /**
   * Sets the justification to block forthe selection.
   */
  def justifyBlock() = execCommand("justifyblock")

  /**
   * Displays the link editor dialog.
   */
  def link() = execCommand("link")

  /**
   * Unlinks the selection.
   */
  def unlink() = execCommand("unlink")

  /**
   * Displays the anchor editor dialog (creates a named anchor on the page that can be linked to).
   */
  def anchor() = execCommand("anchor")

  /**
   * Removes the anchor from the selection.
   */
  def removeAnchor() = execCommand("removeAnchor")

  /**
   * Toggles the selected content within a numbered list.
   */
  def numberedList() = execCommand("numberedlist")

  /**
   * Displays a dialog for styling the current numbered list (will not create one).
   */
  def numberedListStyle() = execCommand("numberedListStyle")

  /**
   * Toggles the selected content within a bulleted list.
   */
  def bulletedList() = execCommand("bulletedlist")

  /**
   * Displays a dialog for styling the current bulleted list (will not create one).
   */
  def bulletedListStyle() = execCommand("bulletedListStyle")

  /**
   * Toggles the maximized state of the editor.
   */
  def maximize() = execCommand("maximize")

  /**
   * Clears the editor of all content and resets.
   */
  def newPage() = execCommand("newpage")

  /**
   * Inserts a page break.
   */
  def pageBreak() = execCommand("pagebreak")

  /**
   * Displays the paste as plain text dialog.
   */
  def pasteText() = execCommand("pastetext")

  /**
   * Displays the paste from Word dialog.
   */
  def pastFromWord() = execCommand("pastefromword")

  /**
   * Previews the content.
   */
  def preview() = execCommand("preview")

  /**
   * Displays the print dialog.
   */
  def print() = execCommand("print")

  /**
   * Removes formatting from the selected content.
   */
  def removeFormat() = execCommand("removeFormat")

  def save() = execCommand("save")

  def selectAll() = execCommand("selectAll")

  /**
   * Toggles showing of container blocks.
   */
  def showBlocks() = execCommand("showblocks")

  /**
   * Toggles showing of borders on all elements.
   */
  def showBorders() = execCommand("showborders")

  /**
   * Toggles display of source in editor instead of rich content.
   */
  def source() = execCommand("source")

  /**
   * Displays the insert special character dialog.
   */
  def specialChar() = execCommand("specialchar")

  /**
   * Enables spell checking addon.
   */
  def scaytCheck() = execCommand("scaytcheck")

  def blur() = execCommand("blur")

  def blurBack() = execCommand("blurBack")

  def selectNextCell() = execCommand("selectNextCell")

  def selectPreviousCell() = execCommand("selectPreviousCell")

  def table() = execCommand("table")

  def tableProperties() = execCommand("tableProperties")

  def tableDelete() = execCommand("tableDelete")

  def cellProperties() = execCommand("cellProperties")

  def rowDelete() = execCommand("rowDelete")

  def rowInsertBefore() = execCommand("rowInsertBefore")

  def rowInsertAfter() = execCommand("rowInsertAfter")

  def columnDelete() = execCommand("columnDelete")

  def columnInsertBefore() = execCommand("columnInsertBefore")

  def columnInsertAfter() = execCommand("columnInsertAfter")

  def cellDelete() = execCommand("cellDelete")

  def cellMerge() = execCommand("cellMerge")

  def cellMergeRight() = execCommand("cellMergeRight")

  def cellMergeDown() = execCommand("cellMergeDown")

  def cellVerticalSplit() = execCommand("cellVerticalSplit")

  def cellHorizontalSplit() = execCommand("cellHorizontalSplit")

  def cellInsertBefore() = execCommand("cellInsertBefore")

  def cellInsertAfter() = execCommand("cellInsertAfter")

  def undo() = execCommand("undo")

  def redo() = execCommand("redo")

  def checkSpell() = execCommand("checkspell")

  def elementsPathFocus() = execCommand("elementsPathFocus")

  def accessPreviousSpace() = execCommand("accessPreviousSpace")

  def accessNextSpace() = execCommand("accessNextSpace")

  /**
   * Deletes the current selection
   */
  def delete() = execCommand("delete")

  // TODO: getCommand('bold').state - 0 = disabled, 1 = true, 2 = false

  initEditor()

  private def initEditor() = {
    if (inline()) {
      wrapped.contentEditable := ContentEditable.True
    }
    wrapped.eventReceived.on {
      case evt => if (evt.event == "editorChanged") {
        val content = evt.json.string("value")
        changeValue(content)

        Intercept.Stop
      } else {
        Intercept.Continue
      }
    }
    html.change.on {
      case evt => if (updateWrapped()) {
        update(fireChanges = false)
      }
    }
  }

  /**
   * Updates the HTML content of the wrapped component with the contents of the editor.
   */
  def update(fireChanges: Boolean) = {
    val f = () => HTMLToScala.replaceChildren(wrapped.asInstanceOf[HTMLTag with Container[HTMLTag]], html())
    if (fireChanges) {
      f()
    } else {
      RealtimePage.ignoreStructureChanges {
        f()
      }
    }
  }

  protected def initializeComponent(values: Map[String, Any]) = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => {
      Realtime.sendJavaScript(webpage, s"createRichEditor('${wrapped.identity}', ${inline()});", onlyRealtime = false)
      values.foreach {
        case (key, value) => modify(key, value)
      }
    }
  }

  protected def modify(key: String, value: Any) = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => Realtime.sendJavaScript(webpage, s"richEditorOption('${wrapped.identity}', '$key', ${JavaScriptContent.toJS(value)});", onlyRealtime = false)
  }

  /**
   * Inserts the supplied content at the current selection. The selected text will be replaced with this content.
   *
   * @param content the HTML content to insert.
   * @param mode the insertion mode to use.
   */
  def insert(content: String, mode: InsertMode) = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => Realtime.sendJavaScript(webpage, s"richEditorInsert('${wrapped.identity}', '${mode.value}', content);", onlyRealtime = false, content = Option(content))
  }

  /**
   * Changes 'value' without triggering an event back to the client.
   *
   * @param content to set value to
   */
  private def changeValue(content: String) = html.applyChange(content)

  def execCommand(command: String, value: Any = null): Unit = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => Realtime.sendJavaScript(webpage, s"richEditorExecCommand('${wrapped.identity}', '$command', ${JavaScriptContent.toJS(value)});")
  }

  def execApplyStyle(style: RichEditorStyle) = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => {
      val js = s"richEditorApplyStyle('${wrapped.identity}', ${style.toJSString});"
      Realtime.sendJavaScript(webpage, js)
    }
  }

  def execRemoveStyle(style: RichEditorStyle) = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => {
      val js = s"richEditorRemoveStyle('${wrapped.identity}', ${style.toJSString});"
      Realtime.sendJavaScript(webpage, js)
    }
  }

  def execToggleStyle(style: RichEditorStyle) = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => {
      val js = s"richEditorToggleStyle('${wrapped.identity}', ${style.toJSString});"
      Realtime.sendJavaScript(webpage, js)
    }
  }

  /**
   * Invokes the supplied action when the state of the supplied style changes on the current selection.
   *
   * For example, passing "b" as the element invokes action when the selection status of being wrapped in a bold tag
   * changes.
   *
   * @param action the JavaScript action to take when the state changes
   * @param style the style to listen to
   */
  def onStyleChange(action: JSFunction1[Boolean, Unit], style: RichEditorStyle): Unit = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => {
      val instruction = s"richEditorAttachStyleStateChange('${wrapped.identity}', ${style.toJSString}, ${action.content});"
      Realtime.sendJavaScript(webpage, instruction, onlyRealtime = false)
    }
  }

  /**
   * Invokes the supplied action when the state of the supplied css style changes on the current selection.
   *
   * @param action the JavaScript action to take when the state changes
   * @param cssStyle the Style to listen to
   */
  def onStyleValueChange(action: JSFunction1[String, Unit], cssStyle: Style[_]): Unit = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => {
      val instruction = s"richEditorAttachStyleValueChange('${wrapped.identity}', '${cssStyle.cssName}', ${action.content});"
      Realtime.sendJavaScript(webpage, instruction, onlyRealtime = false)
    }
  }

  /**
   * Invokes the supplied JavaScript after this editor has completed initialization.
   *
   * @param f the content to execute
   * @param onlyRealtime true if this should only be executed after page rendering is complete
   */
  def afterInit(f: JavaScriptContent, onlyRealtime: Boolean = false): Unit = wrapped.connected[Webpage[_ <: Session]] {
    case webpage => {
      val s =
        s"""
          |var f = function() { ${f.content} };
          |invokeAfterRichEditorInit('${wrapped.identity}', f);
        """.stripMargin
      println(s"AfterInit[$s]")
      Realtime.sendJavaScript(webpage, s, onlyRealtime = onlyRealtime)
    }
  }
}

case class RichEditorStyle(element: String,
                           styles: Map[String, Any] = Map.empty,
                           attributes: Map[String, Any] = Map.empty,
                           overrides: List[Override] = Nil) {
  def toJSString = {
    val styleString = styles.map {
      case (key, value) => s"'$key': ${JavaScriptContent.toJS(value)}"
    }.mkString(", ")
    val attributesString = attributes.map {
      case (key, value) => s"'$key': ${JavaScriptContent.toJS(value)}"
    }.mkString(", ")
    val overridesString = overrides.map {
      case o => {
        val attributes = o.attributes.map {
          case (key, value) => s"'$key': ${JavaScriptContent.toJS(value)}"
        }.mkString(", ")
        s"{ element: '${o.element}', attributes: {$attributes} }"
      }
    }.mkString(", ")
    s"new CKEDITOR.style({element: '$element', styles: {$styleString}, attributes: {$attributesString}, overrides: [$overridesString]})"
  }
}

case class Override(element: String, attributes: Map[String, String] = Map.empty)

class InsertMode private(val value: String) extends EnumEntry

object InsertMode extends Enumerated[InsertMode] {
  val HTML = new InsertMode("html")
  val UnfilteredHTML = new InsertMode("unfiltered_html")
  val Text = new InsertMode("text")
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy