io.devbench.uibuilder.components.richtext.UIBuilderRichTextEditor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of uibuilder-rich-text-editor Show documentation
Show all versions of uibuilder-rich-text-editor Show documentation
A rich text component for the UIBuilder Framework
/*
*
* Copyright © 2018 Webvalto Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.devbench.uibuilder.components.richtext;
import com.vaadin.flow.component.*;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.dom.DisabledUpdateMode;
import com.vaadin.flow.dom.DomListenerRegistration;
import elemental.json.Json;
import elemental.json.JsonObject;
import elemental.json.JsonType;
import elemental.json.JsonValue;
import io.devbench.quilldelta.Delta;
import io.devbench.quilldelta.FormatterManager;
import io.devbench.quilldelta.Ops;
import io.devbench.quilldelta.formatter.DeltaFormatter;
import io.devbench.quilldelta.formatter.HtmlFormatter;
import io.devbench.uibuilder.api.listeners.BackendAttachListener;
import io.devbench.uibuilder.components.richtext.exception.UIBuilderRichTextEditorException;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
@Tag("uibuilder-rich-text-editor")
@JsModule("./uibuilder-rich-text-editor/src/uibuilder-rich-text-editor.js")
@NpmPackage(value = "quill", version = "1.3.7")
@CssImport(value = "quill/dist/quill.snow.css")
@CssImport(value = "quill/dist/quill.bubble.css")
@CssImport(value = "quill/dist/quill.snow.css", themeFor = "uibuilder-rich-text-editor")
@CssImport(value = "quill/dist/quill.bubble.css", themeFor = "uibuilder-rich-text-editor")
public class UIBuilderRichTextEditor extends AbstractSinglePropertyField implements HasComponents, BackendAttachListener {
static final String ATTR_HTML_RENDER_MODE = "html-render-mode";
static final String ATTR_VALUE_MODE = "value-mode";
static final String ATTR_FORMATTER = "formatter";
static final String EVENT_DETAIL_OPS = "event.detail.ops";
static final String EVENT_DETAIL_HTML = "event.detail.html";
static final String EVENT_DETAIL_RESET = "event.detail.reset";
private static final String HTML_RENDER_MODE = "htmlRenderMode";
private static final String VALUE_MODE = "valueMode";
private static final String FORMATTER = ATTR_FORMATTER;
private static final String DEFAULT_FORMATTER = "HTML";
private static final String VALUE = "value";
private static final String CHANGED_SUFFIX = "-changed";
private static final String VALUE_CHANGED = VALUE + CHANGED_SUFFIX;
private static final PropertyDescriptor PROP_HTML_RENDER_MODE =
PropertyDescriptors.propertyWithDefault(HTML_RENDER_MODE, HtmlRenderMode.FRONTEND.name().toLowerCase());
private static final PropertyDescriptor PROP_VALUE_MODE =
PropertyDescriptors.propertyWithDefault(VALUE_MODE, ValueMode.HTML.name().toLowerCase());
private static final PropertyDescriptor PROP_FORMATTER =
PropertyDescriptors.propertyWithDefault(FORMATTER, DEFAULT_FORMATTER);
private final Delta delta;
private final HtmlFormatter htmlFormatter;
private String htmlText;
public UIBuilderRichTextEditor() {
super(VALUE, "", true);
delta = new Delta();
htmlFormatter = new HtmlFormatter();
}
@Synchronize(property = HTML_RENDER_MODE, value = {ATTR_HTML_RENDER_MODE + CHANGED_SUFFIX, VALUE_CHANGED}, allowUpdates = DisabledUpdateMode.ALWAYS)
public HtmlRenderMode getHtmlRenderMode() {
return HtmlRenderMode.valueOf(get(PROP_HTML_RENDER_MODE).toUpperCase());
}
public void setHtmlRenderMode(HtmlRenderMode htmlRenderMode) {
set(PROP_HTML_RENDER_MODE, htmlRenderMode.name().toLowerCase());
}
@Synchronize(property = VALUE_MODE, value = {ATTR_VALUE_MODE + CHANGED_SUFFIX, VALUE_CHANGED}, allowUpdates = DisabledUpdateMode.ALWAYS)
public ValueMode getValueMode() {
return ValueMode.valueOf(get(PROP_VALUE_MODE).toUpperCase());
}
public void setValueMode(ValueMode valueMode) {
set(PROP_VALUE_MODE, valueMode.name().toLowerCase());
}
@Synchronize(property = FORMATTER, value = {ATTR_FORMATTER + CHANGED_SUFFIX, VALUE_CHANGED}, allowUpdates = DisabledUpdateMode.ALWAYS)
public String getFormatterName() {
return get(PROP_FORMATTER);
}
public void setFormatterName(String formatterName) {
set(PROP_FORMATTER, FormatterManager.getInstance()
.findFormatter(formatterName)
.map(DeltaFormatter::getFormatterName)
.orElseThrow(() -> new UIBuilderRichTextEditorException("Formatter not found: " + formatterName)));
}
public String getValueAsPlainText() {
return delta.getText();
}
public void setValueAsPlainText(String plainText) {
getElement().executeJs("$0.setValueAsPlainText($1)", this, plainText);
}
public String getValueAsHtmlText() {
return getHtmlRenderMode() == HtmlRenderMode.BACKEND ? htmlFormatter.render(delta) : htmlText;
}
public void setValueAsHtmlText(String htmlAsText) {
getElement().executeJs("$0.setValueAsHtmlText($1)", this, htmlAsText);
}
public Ops getValueAsOps() {
return delta.getOps();
}
public void setValueAsOps(Ops ops) {
getElement().executeJs("$0.setValueAsOps($1)", this, ops.toJson());
}
public String getFormattedText() {
return getFormatterIfRendererPresent()
.map(deltaFormatter -> deltaFormatter.render(delta))
.orElseThrow(() -> new UIBuilderRichTextEditorException("Formatter or format renderer not found"));
}
public void setFormattedText(String renderedValue) {
setValueAsOps(
getFormatterIfParserPresent()
.orElseThrow(() -> new UIBuilderRichTextEditorException("Formatter or format parser not found"))
.parse(renderedValue).getOps());
}
private Optional getFormatterIfRendererPresent() {
DeltaFormatter formatter = getFormatter();
return formatter != null && formatter.getRenderer() != null ? Optional.of(formatter) : Optional.empty();
}
private Optional getFormatterIfParserPresent() {
DeltaFormatter formatter = getFormatter();
return formatter != null && formatter.getParser() != null ? Optional.of(formatter) : Optional.empty();
}
@Nullable
private DeltaFormatter getFormatter() {
return FormatterManager.getInstance().findFormatter(getFormatterName()).orElse(null);
}
@Override
public void onAttached() {
DomListenerRegistration deltaEventRegistration = getElement().addEventListener("delta", event -> {
JsonObject jsonOps = event.getEventData().getObject(EVENT_DETAIL_OPS);
JsonValue jsonHtml = event.getEventData().get(EVENT_DETAIL_HTML);
String oldValue = getValue();
if (event.getEventData().getBoolean(EVENT_DETAIL_RESET)) {
delta.clear();
}
htmlText = jsonHtml.getType() == JsonType.STRING ? jsonHtml.asString() : null;
Ops ops = Ops.fromJson(jsonOps);
delta.apply(ops);
ComponentUtil.fireEvent(this, new ComponentValueChangeEvent<>(this, this, oldValue, true));
});
deltaEventRegistration
.addEventData(EVENT_DETAIL_OPS)
.addEventData(EVENT_DETAIL_HTML)
.addEventData(EVENT_DETAIL_RESET);
}
@Override
public String getValue() {
ValueMode valueMode = getValueMode();
switch (valueMode) {
case HTML:
return getValueAsHtmlText();
case PLAIN:
return getValueAsPlainText();
case DELTA:
return getValueAsOps().toJson().toJson();
case FORMATTED:
return getFormattedText();
}
return super.getValue();
}
@Override
public void setValue(String value) {
ValueMode valueMode = getValueMode();
switch (valueMode) {
case HTML:
setValueAsHtmlText(value);
return;
case PLAIN:
setValueAsPlainText(value);
return;
case DELTA:
setValueAsOps(Ops.fromJson(Json.parse(value)));
return;
case FORMATTED:
setFormattedText(value);
return;
}
delta.clear();
super.setValue(value);
}
}