/****************************************************************************** * Copyright (C) 2008 Low Heng Sin * * This program is free software; you can redistribute it and/or modify it * * under the terms version 2 of the GNU General Public License as published * * by the Free Software Foundation. This program is distributed in the hope * * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * *****************************************************************************/ package org.adempiere.webui.window; import java.util.HashMap; import java.util.Map; import org.adempiere.util.Callback; import org.adempiere.webui.ClientInfo; import org.adempiere.webui.component.ConfirmPanel; import org.adempiere.webui.component.Label; import org.adempiere.webui.component.Tab; import org.adempiere.webui.component.Tabbox; import org.adempiere.webui.component.Tabpanel; import org.adempiere.webui.component.Tabpanels; import org.adempiere.webui.component.Tabs; import org.adempiere.webui.component.Textbox; import org.adempiere.webui.component.Window; import org.adempiere.webui.session.SessionManager; import org.adempiere.webui.theme.ThemeManager; import org.adempiere.webui.util.ZKUpdateUtil; import org.compiere.model.MSysConfig; import org.compiere.util.Env; import org.compiere.util.Language; import org.compiere.util.Msg; import org.owasp.html.PolicyFactory; import org.owasp.html.Sanitizers; import org.zkforge.ckez.CKeditor; import org.zkoss.zk.au.out.AuScript; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.util.Clients; import org.zkoss.zul.Div; import org.zkoss.zul.Html; import org.zkoss.zul.Separator; import org.zkoss.zul.Vlayout; /** * Text editor dialog with plain text and HTML editor tab * @author Low Heng Sin * */ public class WTextEditorDialog extends Window implements EventListener{ /** * generated serial id */ private static final long serialVersionUID = -1857623453350849161L; private boolean editable; private int maxSize; private String text; private boolean cancelled; private Tabbox tabbox; private Textbox textBox; private CKeditor editor; private Label status; private Tab htmlTab; private boolean isShowHTMLTab = true; /* SysConfig USE_ESC_FOR_TAB_CLOSING */ private boolean isUseEscForTabClosing = MSysConfig.getBooleanValue(MSysConfig.USE_ESC_FOR_TAB_CLOSING, false, Env.getAD_Client_ID(Env.getCtx())); /** * @param title * @param text * @param editable * @param maxSize * @param IsHtml - select the html tab at start */ public WTextEditorDialog(String title, String text, boolean editable, int maxSize, boolean IsHtml) { this(title, text, editable, maxSize, IsHtml, true); } /** * @param title * @param text * @param editable * @param maxSize * @param IsHtml - select the html tab at start * @param IsShowHTMLTab - true to shown HTML tab */ public WTextEditorDialog(String title, String text, boolean editable, int maxSize,boolean IsHtml, boolean IsShowHTMLTab) { super(); setTitle(title); this.editable = editable; this.maxSize = maxSize; this.text = text; this.isShowHTMLTab = IsShowHTMLTab; init(); if (IsHtml && IsShowHTMLTab) tabbox.setSelectedTab(htmlTab); } /** * @param title * @param text * @param editable * @param maxSize */ public WTextEditorDialog(String title, String text, boolean editable, int maxSize) { this(title, text, editable, maxSize, false); } /** * Layout dialog */ private void init() { setBorder("normal"); if (!ThemeManager.isUseCSSForWindowSize()) { ZKUpdateUtil.setWindowHeightX(this, 450); ZKUpdateUtil.setWindowWidthX(this, 800); } else { addCallback(AFTER_PAGE_ATTACHED, t -> { ZKUpdateUtil.setCSSHeight(this); ZKUpdateUtil.setCSSWidth(this); }); } setStyle("position: absolute;"); setSizable(false); setSclass("popup-dialog text-editor-dialog"); Vlayout vbox = new Vlayout(); appendChild(vbox); ZKUpdateUtil.setWidth(vbox, "100%"); ZKUpdateUtil.setVflex(vbox, "true"); vbox.setSclass("dialog-content"); tabbox = new Tabbox(); vbox.appendChild(tabbox); Tabs tabs = new Tabs(); tabbox.appendChild(tabs); Tabpanels tabPanels = new Tabpanels(); tabbox.appendChild(tabPanels); ZKUpdateUtil.setVflex(tabbox, "1"); ZKUpdateUtil.setHflex(tabbox, "1"); Tab tab = new Tab(Msg.getMsg(Env.getCtx(), "Text")); tabs.appendChild(tab); Tabpanel tabPanel = new Tabpanel(); tabPanels.appendChild(tabPanel); textBox = new Textbox(text); textBox.setCols(80); textBox.setMultiline(true); ZKUpdateUtil.setHeight(textBox, "100%"); textBox.setEnabled(editable); ZKUpdateUtil.setHflex(textBox, "1"); ZKUpdateUtil.setVflex(textBox, "1"); tabPanel.appendChild(textBox); if (isShowHTMLTab) { htmlTab = new Tab("HTML"); tabs.appendChild(htmlTab); tabPanel = new Tabpanel(); tabPanels.appendChild(tabPanel); if (editable) { createEditor(tabPanel); } else { Div div = new Div(); ZKUpdateUtil.setHeight(div, "100%"); ZKUpdateUtil.setWidth(div, "100%"); div.setStyle("overflow: auto; border: 1px solid"); tabPanel.appendChild(div); Html html = new Html(); div.appendChild(html); html.setContent(text); } } vbox.appendChild(new Separator()); Div footer = new Div(); footer.setSclass("dialog-footer"); ConfirmPanel confirmPanel = new ConfirmPanel(true); footer.appendChild(confirmPanel); confirmPanel.addButton(confirmPanel.createButton(ConfirmPanel.A_RESET)); confirmPanel.addActionListener(this); appendChild(footer); if (maxSize > 0) { status = new Label(); footer.appendChild(status); updateStatus(text.length()); status.setStyle("margin-top:10px;"); textBox.addEventListener(Events.ON_CHANGE, this); if (isShowHTMLTab && editor != null) editor.addEventListener(Events.ON_CHANGE, this); } tabbox.addEventListener(Events.ON_SELECT, this); //Bug IDEMPIERE-1992 Ensure that text editor dialog has a close button setClosable(true); setSizable(true); setMaximizable(true); addEventListener(Events.ON_CANCEL, e -> onCancel()); addEventListener(Events.ON_SIZE, e -> onSize()); addEventListener(Events.ON_MAXIMIZE, e -> onSize()); } /** * Create html editor (ckeditor) and add it to tabPanel * @param tabPanel */ private void createEditor(org.zkoss.zul.Tabpanel tabPanel) { editor = new CKeditor(); if (ClientInfo.isMobile()) editor.setCustomConfigurationsPath("/js/ckeditor/config-min.js"); else editor.setCustomConfigurationsPath("/js/ckeditor/config.js"); editor.setToolbar("MyToolbar"); Map lang = new HashMap(); lang.put("language", Language.getLoginLanguage().getAD_Language()); editor.setConfig(lang); tabPanel.appendChild(editor); editor.setVflex("1"); editor.setWidth("100%"); editor.setValue(text); } /** * Call back event for Ok button (from html editor) * @param event */ public void onEditorCallback(Event event) { text = sanitize((String) event.getData()); detach(); } /** * @param event */ public void onEvent(Event event) throws Exception { if (event.getTarget().getId().equals(ConfirmPanel.A_CANCEL)) { onCancel(); } else if (event.getTarget().getId().equals(ConfirmPanel.A_OK)) { if (editable) { if (maxSize > 0) { int currentSize = 0; if (tabbox.getSelectedIndex() == 0) currentSize = textBox.getText().length(); else currentSize = editor.getValue().length(); if (currentSize > maxSize) { Dialog.error(0, "Error", Msg.getMsg(Env.getCtx(), "TextEditorDialogCurrentSizeExceedMaxSize", new Object[] {currentSize, maxSize})); return; } } if (tabbox.getSelectedIndex() == 0) { text = textBox.getText(); detach(); } else { String script = "(function(){let w=zk('#"+editor.getUuid()+"').$();let d=w.getEditor().getData();let t=zk('#" + this.getUuid()+"').$();let e=new zk.Event(t,'onEditorCallback',d,{toServer:true});zAu.send(e);})()"; Clients.response(new AuScript(script)); } } } else if (event.getTarget().getId().equals(ConfirmPanel.A_RESET)) { Dialog.ask(0, "TextEditorDialogResetConfirmation", new Callback() { @Override public void onCallback(Boolean result) { if (result) { textBox.setText(text); if (editor != null) editor.setValue(text); } } }); } else if (event.getName().equals(Events.ON_SELECT)) { if (editable) { if (tabbox.getSelectedIndex() == 0) { textBox.setText(editor.getValue()); updateStatus(textBox.getText().length()); } else { editor.setValue(textBox.getText()); updateStatus(editor.getValue().length()); } } } else if (event.getName().equals(Events.ON_CHANGE)) { if (event.getTarget() == textBox) { updateStatus(textBox.getText().length()); } else if (event.getTarget() == editor) { updateStatus(editor.getValue().length()); } } } /** * Handle onCancel event */ private void onCancel() { // do not allow to close tab for Events.ON_CTRL_KEY event if(isUseEscForTabClosing) SessionManager.getAppDesktop().setCloseTabWithShortcut(false); cancelled = true; detach(); } /** * Handle onSize event */ private void onSize() { if(editor != null) editor.invalidate(); } /** * Update status text (for text length) * @param newLength */ private void updateStatus(int newLength) { if (status != null && maxSize > 0) { StringBuilder msg = new StringBuilder(); msg.append(newLength); if (newLength == maxSize) msg.append(" = "); else if (newLength < maxSize) msg.append(" < "); else msg.append(" > "); msg.append(maxSize); status.setValue(msg.toString()); } } /** * @return true if dialog is cancel by user */ public boolean isCancelled() { return cancelled; } /** * @return text */ public String getText() { return text; } /** * @param untrustedHTML * @return sanitized html content */ public static String sanitize(String untrustedHTML) { final PolicyFactory policy = Sanitizers.BLOCKS .and(Sanitizers.FORMATTING) .and(Sanitizers.IMAGES) .and(Sanitizers.LINKS) .and(Sanitizers.STYLES) .and(Sanitizers.TABLES); String ret = policy.sanitize(untrustedHTML); ret = ret.replaceAll("#", "#"); ret = ret.replaceAll("@", "@"); return ret; } }