内容


如何自定义 Ephox Textbox.io for IBM Web Content Manager 8.5

Comments

此文描述了与 Ephox Textbox.io for IBM WCM 8.5 的一些自定义操作相关的案例分析

前提条件

前提条件包括:

  • WPS 管理的基本知识。
  • 使用 WAS 控制台的基本知识。
  • WCM 创作接口(WCM 创作 portlet)的基本知识。
  • Javascript 语言的基本知识。
  • JAVA 语言和 WCM API 的基本知识(可选)。

逐步介绍

简介

Ephox Textbox.io JavaScript HTML Rich Text Editor 是基于 Java 的 Ephox EditLive 富文本编辑器的不错替代产品。它基于 HTML5 JavaScript 和 CSS3 标准,支持各种 Internet 浏览器。有关这些浏览器的完整列表和系统需求,请参考 Ephox 文档 [2]。

Textbox.io 编辑器可嵌入到任何 HTML 页面中,有关运行示例,请参考 Ephox 页面 [1]:

Web Content Manager V8.5 Cumulative Fix 11 (CF11) 开始,Textbox.io 默认安装为 Web 内容创作 portlet"Editor Options"配置下的"Advanced Editor"(参考 Ephox 页面 [3])。

但是,也可以手动在更早的 IBM Web Content Manager 版本中安装它(IBM WCM 8.5.0.0 Cumulative Fix 06 (CF6) 及以后的版本)。

配置文件

要自定义用于 WCM 的 Textbox.io 编辑器,必须修改以下目录下 tbio_config.jsp 配置文件的副本。

[PortalServer root]/wcm/prereq.wcm/wcm/config/templates/shared/app/config/textboxio

可通过添加、更改和删除 Java 和 JavaScript 代码来执行自定义。通过自定义配置文件,可以更改配置对象,插入、删除或更改属性及其值。

配置对象

配置对象是一个收集编辑器所有属性的 JavaScript 对象。它的原始 Ephox 默认代码为:

            var cnfDefault = 
            { 
                  autosubmit: true, 
                  css: 
                  {
                     stylesheets: [''], 
                     styles: 
                     [ 
                        {rule: 'p', text: 'block.p'}, 
                        {rule: 'h1',text: 'block.h1'}, 
                        {rule: 'h2',text: 'block.h2'}, 
                        {rule: 'h3',text: 'block.h3'}, 
                        {rule: 'h4',text: 'block.h4'}, 
                        {rule: 'div',text:'block.div'}, 
                        {rule: 'pre', text: 'block.pre'} 
                      ]
                    },                    
                     codeview: { enabled: true, showButton: true }, 
                     images: { allowLocal: true }, 
                     languages: ['en', 'es', 'fr', 'de', 'pt', 'zh'] 
                     ui: 
                     { 
                        toolbar: { items: ['undo', 'insert', 'style', 'emphasis', 'align', 'listindent', 'format', 'tools'],
                        contextual: ['image-tools', 'table-tools'] 
                                 }                   
                     }                      
               };

该配置包含多个由冒号分隔的属性/值对(比如 autosubmit: true),这些属性/值对也可以嵌套。一些值是标量,其他值是单维数组。所有配置对象属性都是可选的。我们实际上只需将要更改的属性放入配置对象中。

下面列出了配置对象的主要属性:

属性 类型 简要描述
autosubmit 布尔值 指定 Textbox.io 是否应处理表单提交
basePath 字符串 指定 Textbox.io 资源文件夹的路径
css 对象 呈现 CSS 和设置应用程序样式的编辑器
codeview 对象 代码视图特性
images 对象 编辑器图像处理和上传
links 对象 编辑器链接验证
paste 对象 编辑器粘贴行为
spelling 对象 编辑器拼写检查服务
ui 对象 包含工具栏、菜单等的编辑器用户界面。

有关更多细节,请参考 Ephox 页面 [11]。

下面介绍其中一些属性,以及如何自定义它们。

css 属性

在默认配置中,Textbox.io 拥有以下样式下拉菜单:

tbio_config.jsp 文件中的相应代码如下:

            var cnfDefault = 
            { 
                … 
                css: 
                    { 
                        stylesheets: [''], 
                        styles:[ 
                                {rule: 'p', text: 'block.p'},
                                {rule: 'h1',text: 'block.h1'},
                                {rule: 'h2',text: 'block.h2'}, 
                                {rule: 'h3',text: 'block.h3'}, 
                                {rule: 'h4',text: 'block.h4'}, 
                                {rule: 'div',text: 'block.div'}, 
                                {rule: 'pre',text: 'block.pre'} 
                                ] 
                       },
                 …
              };

有关更多细节,请参考 Ephox 页面 [13]。

codeview 属性

编辑器实例默认在右下角包含按钮

,该按钮允许在 WYSIWYG 和 HTML 源代码之间切换。在配置对象中,默认拥有 codeview 属性:

var cnfDefault = 
{ 
… 
codeview: { enabled: true,showButton: true }, 
…
};

有关更多细节,请参考 Ephox 页面 [12]。

ui 属性

用户界面属性 (ui) 定义与编辑器用户界面相关的选项,包括编辑器的工具栏。

下面列出了 ui 属性的子属性:

属性 类型 简要描述
aria-label 布尔值 用于编辑器 ARIA 标签的非默认文本
languages 字符串 指定可应用于内容国际化的可用语言代码
locale 对象 指定编辑器 UI 的语言
fonts 对象 指定可用于编辑器的字体列表
shortcuts 对象 指定是否启用内容键盘快捷键
toolbar 对象 该对象代表了编辑器所期望的工具栏功能

  有关更多细节,请参考 Ephox 页面 [14]。

languages 子属性

语言数组允许开发人员指定一种或多种应用于 HTML 内容国际化的语言代码。添加语言配置不会自动将语言按钮添加到工具栏,需按照下一段中的解释来指定。

此数组直接配置语言菜单中可用于应用程序的各种语言。从语言菜单选择一种语言,会设置编辑器中所选文本的 HTML lang 属性(参考 w3.org 页面 [18])。

设置语言配置数组会覆盖默认的语言数组。

请注意,现在只将部分语言的名称翻译为所有 Textbox.io UI 语言(请参考下面的"已翻译语言代码")。开发人员可选择应用此列表中的语言代码,或者指定任何 2 字母或 4 字母语言代码。指定的语言代码不在已翻译语言代码列表中时(例如'x-klingon'),该语言代码将出现在语言菜单中。

默认语言包括:

语言 语言代码字符串
英语 en
西班牙语 es
法语 fr
德语 de
葡萄牙语 pt
中文 zh

 有关更多细节,请参考 Ephox 页面 [17]。

toolbar 子属性

用户界面的 toolbar 属性定义与编辑器工具栏命令、分组和子菜单相关的选项。上下文工具栏项现在显示在工具栏的末尾(它们仅与图像和表格相关)。

它的主要子属性包括:

子属性 类型 默认值 说明
items 数组 ['undo', 'insert', 'style', 'emphasis', 'align', 'listindent', 'format', 'tools'] 该数字表示了 Textbox.io 工具栏和菜单系统的结构。每一项表示一个工具栏组
contextual 数组 ['table-tools','image-tools'] 该数组列出了根据所选的上下文而显示的各项

因此,与工具栏相关的默认配置对象部分是:

var cnfDefault = 
{
… 
ui:
{ 
toolbar: 
{ 
items: ['undo', 'insert', 'style', 'emphasis', 'align', 'listindent', 'format', 'tools'],
contextual: ['image-tools', 'table-tools'] 
} 
}
};

工具栏由多个项对象组成。各个项代表了编辑器命令、工具栏组或菜单。每个项根据它们在项数组中的位置来推断其用户界面。放在菜单内的项将呈现为菜单项,而放在工具栏组内的项将呈现为按钮。同样,放在菜单内的菜单项将产生一个子菜单。项有 3 种不同类型,表示 Textbox.io 编辑器中的用户界面结构。命令项表示独立的编辑器功能。菜单项表示从根用户界面元素调用的嵌套命令组。组项表示工具栏或菜单内各命令的逻辑分组。下表解释了每种项类型的属性:

项类型 属性 说明
命令 id 字符串 命令的 ID 字符串 命令类型的各项表示独立的编辑器命令,比如 apply bold、insert link 等。

请注意,通过内置命令项的字符串 ID 引用它们,而不是指定为对象
text 字符串 工具提示中显示的各个 命令的友好名称(可选)
icon 字符串 表示命令的图标的路径
action 函数 通过用户操作调用命令时所要执行的函数
菜单 id 字符串 菜单的 ID 字符串 菜单类型的各项表示菜单中的命令组。在呈现时,菜单在主机工具栏上显示为图标,或者在主机菜单上显示为一个图标后跟菜单名
label 字符串 菜单的友好名称,对辅助设备可见(可选)
icon 字符串 表示菜单的图标的路径
items 数组 一个命令或菜单项数组
label 字符串 菜单的友好名称,对辅助设备可见(可选) 组类型的各项表示一个工具栏上或菜单内的命令的逻辑分组。呈现一个编辑器时,通过可视分隔符指定各个组
tems 数组 一个命令或菜单项数组

内置编辑器命令由工具栏配置中的一个预定义的字符串 ID 表示。有关内置编辑器命令 ID 的列表,请参考 Ephox 页面 [16]。工具栏项数组是配置 Textbox.io 编辑器实例的工具栏、菜单和按钮的主要方式。可将项数组设置为一个或多个工具栏组对象。可在这些组对象中填入更多的项,从而在所呈现的编辑器中创建工具栏按钮和菜单。下面是工具栏项数组分层结构的一种模式:

                Toolbar items
                 Toolbar group(s)
                    Command item(s)
                    Menu item(s)
                        Command item(s)
                        Menu item(s)

组项对象由一个字符串名称和一个项数组组成。项数组可能包含命令项 ID 或命令项对象,例如:

          var aItems = 
          [
            // Simple Toolbar group object with 2 function IDs 
            { 
                label: 'Toolbar Group 1', 
                items: ['undo', 'redo']
             }
           ];

命令项对象由一个字符串 ID、一个字符串名称、一个图标资源的字符串路径和一个操作函数组成。将命令项放在工具栏组中时,其功能将在编 辑器工具栏上通过按钮来表示。该按钮包含指定的图标图像。将命令项放在菜单项对象中时,其功能将通过菜单项来表示。菜单项包含指定的图标图像和名称字符串。用户单击按钮(工具栏)或菜单项(菜单)时,将执行操作中指定的函数。示例:

            // Command item object
            var itmCustom =
            { 
                id: 'custom1',
                text: 'Custom Button 1', icon: '/path/to/icon1.png', action: function ()
                {alert('Custom button 1 Clicked');}
            };
            var aItems =
            [ 
                { 
                    // Toolbar group object with custom command 
                    label: 'Toolbar Group 2', 
                    items: ['undo', 'redo', itmCustom] 
                }
            ];

工具栏菜单项对象由一个字符串 ID、一个字符串名称、一个图标资源的字符串路径和一个项数组组成。项数组可能包含命令项 ID 或命令项对象,例如:

           // Menu item object with 2 function IDs
           var itmCustom =
           { 
                id: 'custom1', label: 'Custom Menu', 
                icon: '/path/to/icon1.png', 
                items: ['bold', 'italics']};
            // Items array with one group object containing 2 function IDs and a 
            // custom menu item object
           var aItems = 
            [ 
                { 
                     // Toolbar group object with custom menu item 
                     label: 'Toolbar Group 2', 
                     items: ['undo', 'redo', itmCustom] 
                 }
            ];

嵌套菜单仅支持二级嵌套。有关更多细节,请参考 Ephox 页面 [15]。

示例 1:css 属性自定义

使用两个子属性 documentStyles(将我们的样式应用于编辑器内容)和 styles(在下拉菜单中显示样式列表),通过插入我们自己的样式来更改样式菜单,操作如下:

                …
                // Customize the Style menu
                var aStyles = flatten
                ( 
                    [
                        {rule: 'p.Red', text: 'Red text'}, 
                        {rule: 'h1', text: 'Header 1'}, 
                        // Inline styles (will become span.SuperScript and span.SubScript) 
                        {rule: '.SuperScript', text: 'Superscript'}, 
                        {rule: '.SubScript', text:'Subscript'} 
                     ]);
                var sStyleClasses = 'p.Red{color: #FF0000;} span.SuperScript{vertical-align: super; font-size: 80%;} 
                                     span.SubScript{vertical-align: sub; font-size: 80%;}';
                …
                var cnf = 
                {
                     … 
                    css:{ 
                            // To see the styles applied in the editor content 
                            document Styles:sStyleClasses,
                            // To have the styles list in the drop down menu 
                            styles: aStyles 
                         },
                     …
                  }; 
                 return cnf;
                 …

结果是:

内容源代码是:

                <h1>Header 1</h1>
                <p class="Red">Redtext</p>
                <p>This is <span class="SuperScript">Superscript</span></p>
                <p>This other is <span class="SubScript">Subscript</span></p>

示例 2:自定义代码视图和导入/导出 HTML 标记可视性

在某些情况下,有必要对某些用户组以外的用户隐藏这些功能。为此,有必要确定当前用户是否属于这些组。可将以下代码放入 tbio_config.jsp 文件中来执行此任务:

            …
            <%@page import="com.ibm.wps.puma.*"%>
            <%@page import="java.util.*"%>
            …
            <%! 
            /** 
              * This function returns the WCM workspace for the current user.
              *
              * @param req The HttpServerRequest object.
              * @return The WCM workspace (Workspace type) or null if error occurs.
              */
            public Workspace getWorkspace(HttpServletRequest req){ 
            Principal princ; 
            Workspace ws;
            ws = null;          
                try { 
                     princ = req.getUserPrincipal();
                     if (princ == null) { 
                     // Get the WCM workspace for the anonymous user.
                     ws = WCM_API.getRepository().getAnonymousWorkspace(); 
                      } else { 
                     // Get the WCM workspace for the current logged-in user.
                        ws =WCM_API.getRepository().getWorkspace(princ); 
                       }
                        ws.useUserAccess(true); 
                      }catch (Exception e) { 
                        ws = null; 
                        } 
                        return ws;
              } 
             /** 
               * This function verify if the current user belongs to at least one of the 
               * passed groups.
               *
               * @param ws The WCM workspace.
               * @param vectGroups A Vector object containing the names of the groups to
               * scan.
               * @return true if current user is atleast member of one of the groups, 
               * otherwise false.
               */
             public boolean isCurrentUserBelongingTo(Workspace ws, Vector vectGroups){ 
                 boolean f; 
                 int i; 
                 String sGroup;
                 f = false; 
                 for (i = 0; i < vectGroups.size(); ++i) {
                     sGroup = (String)vectGroups.elementAt(i); 
                     if (ws != null && ws.isMemberOfGroup(sGroup)) { 
                         f = true; break; 
                     } 
                 }
                  return f;
             } 
            %>
            …
            <%
            /** The scope of this piece of code is to verify if the current user 
              * belongs at least to one of the groups passed through a Vector object.
              * It's possible here to modify this list by changing/inserting/deleting
              * the rows of code: 
              * vectGroups.addElement("myGroupName");
              * 
              * The returned fMember flag will be used to set the visibility of the 
              * HTML source switch.
              */
                Workspace ws;
                boolean fMember;
                Vector vectGroups; 
                vectGroups = new Vector();
                vectGroups.addElement("wpsadmins");
                vectGroups.addElement("wcmadmins");
                vectGroups.addElement("anothergroup");
                ws = getWorkspace(request);
                fMember = isCurrentUserBelongingTo(ws,vectGroups); 
            %>
                …
                // Javascript codevar 
                fCodeViewEnabled = <%=fMember%>;
                …
                var grpTools = 
                { 
                    label: 'category.tools', 
                    items: 
                    (
                     fCodeViewEnabled ? [ 'find', 'accessibility', Button ( 'WcmExport', 'EXPORT_BUTTON', 'upload.svg', exportRTFHTML ), Button ( 'WcmImport',
                     'IMPORT_BUTTON', 'download.svg', importRTFHTML ), 'fullscreen','usersettings' ] : [ 'find', 'accessibility', 'fullscreen', 'usersettings' 'insert', 'style', 'emphasis', 'align', 'listindent', 'format' ],
                     [grpTools] ]);
                var cnf = 
                { 
                    … 
                    codeview: 
                    { 
                        enabled: fCodeViewEnabled
                     }, 
                    …
                    ui: 
                    { 
                        … 
                        toolbar: 
                        {
                            items: aItems, 
                            … 
                        }
                     }, 
                    …
                 };

示例 3:language 属性自定义

作为配置示例,假设我们想要英语 (US) 和意大利语,那么配置对象为:

            var cnf = 
            { 
                … 
                ui:
                { 
                    languages: ['en_us', 'it'], 
                … 
                },
                …
             };

示例 4:上下文菜单自定义

假设我们想隐藏表的上下文菜单,但显示图像的上下文菜单:

            // Compute the whole items array
            var aItems =flatten([…]);
            …
            var cfg = { … ui: 
            { 
                … 
                toolbar: 
                { 
                    items: aItems,
                    contextual:['image-tools'] 
                } 
              }, 
                … 
            };

示例 5:自定义菜单

WCM 版的 Textbox.io 编辑器的 add 菜单是一个包含许多菜单项的自定义菜单:

  • 向 WCM 内容插入链接的按钮
  • 插入图像的按钮
  • WCM 标签帮助器按钮
  • 插入外部内容管理器链接
  • 插入媒体
  • 插入表
  • 插入特殊字符
  • 插入水平线

源代码是:

            …
            var grpInsert = 
            { 
                label: 'category.insert', 
                items: 
                [ 
                    {
                        id: 'insert', 
                        // This is required to get the + icon 
                        label: 'insert.menu',
                        // This is required to get the 
                        // translated "Insert" hover text 
                items: 
                [
                    [ Button ( 'WCMInsertLink', 'INSERT_LINK_TO_WCM_CONTENT', 'link.svg', performInsertLinkIntoRTF ), Button ( 'WCMInsertImage', 'INSERT_IMAGE_FROM_WCM_LIBRARY', 'image.svg',
                ephox_performInsertImgIntoRTF ), Button ( 'WCMTagHelper', 'TAG_HELPER_BUTTON', 'tag.svg', performInsertTagIntoField ),
                    { 
                      id: 'WCMInsertECMLink', 
                      text: buttonText('ECM_LINK_BUTTON'), 
                      icon:buttonImage('globe.svg'),
                      action: function () 
                      {
                        var pickerUnavailable = buttonText("FED_DOCS_PICKER_UNAVAILABLE"); 
                        var pickerDialogTitle = buttonText("FED_DOCS_PICKER_DIALOG_TITLE"); 
                        insertEcmLink ( wcmId,function() {
                            elEcmLaunched = false; 
                        }, 
                      pickerUnavailable, pickerDialogTitle ); 
                       } 
                     }, 
                     'media', 'table' 
                     ], [ 'specialchar', 'hr' ] 
                 ] 
                     }
                ]};
                …
                var aItems = flatten( [ ['undo', grpInsert, 'style', 'emphasis', 'align', 'listindent'],
                 … 
                ]);
                …
                var cfg = 
                { 
                    … 
                    ui: 
                    { 
                         … 
                        toolbar: 
                        {
                            items: aItems 
                        } 
                    }, 
                }; 
                return cfg;
                …

这是构建一个完全自定义菜单的不错示例。如果我们想从这个自定义菜单中移除一些项,丢弃一些代码行就行了。假设我们想从此菜单中删除某些项,具体来讲,删除:

  • WCM 标签帮助器按钮
  • 插入外部内容管理器链接
  • 插入特殊字符

用这种方法更改 grpInsert 对象就行了:

            var grpInsert = 
            { 
                label: 'category.insert', 
                items: 
                [ 
                    {
                        id: 'insert',
                        // This is required to get the + icon label: 'insert.menu',
                        // This is required to get the
                        // translated "Insert" hover text items: 
                        [
                            [   
                                Button ( 'WCMInsertLink', 'INSERT_LINK_TO_WCM_CONTENT', 'link.svg', performInsertLinkIntoRTF ), 
                                Button ( 'WCMInsertImage', 'INSERT_IMAGE_FROM_WCM_LIBRARY', 'image.svg', ephox_performInsertImgIntoRTF ), 'media', 'table'
                             ], 
                            ['hr'] 
                        ] 
                    } 
            ]};

结果是:

菜单中显示的水平线将前 4 项与最后一项分开,因为我们让两个不同的子数组作为 grpInsert 对象的 items 属性的值:

            var grpInsert = 
            { 
                label: 'category.insert', 
                items: 
                [ 
                    {
                        id: 'insert', 
                        // This is required to get the + icon 
                        label: 'insert.menu',
                        // This is required to get the 
                        // translated "Insert" hover text 
                        items: [
                                    […], 
                                    ['hr'] 
                                ] 
                        } 
                ]
              };

示例 6:删除其他顶级项

假设我们想删除以下项:

  • 下划线操作
  • 缩进和减少缩进操作
  • 除清除格式外的所有格式项

那么代码为:

            …
            // Removed the 'underline" item 
            var grpEmphasis = 
            {
                label: 'category.emphasis', 
                items: ['bold', 'italic']
            };
            // Removed the "indent" and "outdent" items
            var girpIdent = 
            { 
                label: 'category.indent',
                items: ['ul', 'ol']
            };
              // Removed all format items except the 'removeformat'one
            var grpFormat = 
            { 
                label: 'category.format', 
                items:['removeformat']
            };
            …
              // Compute the whole items array
            var aItems = flatten( 
            [
                ['undo', 'insert', 'style', grpEmphasis', 'align', grpIndent],styleGroup(grpFormat), ['tools'] ]);                  
            …
            var cfg = 
            { 
            … 
                ui: 
                { 
                    toolbar: 
                    { 
                        items: items 
                    } 
                }, 
                …
             }; 
                return cfg;
                …

最后一个示例:融会贯通

通过执行所有上述自定义示例,我们获得了以下结果(一个非常精简的工具栏):

请注意,More 操作

(用于将工具栏扩展到第二行)还不存在,因为现在一行就足以容纳所有工具栏项了。

应用自定义

仅更改配置文件 tbio_config.jsp 的副本无法完全看到我们对 Textbox.io 编辑器的自定义效果;如果需要向 WCM 应用新的自定义内容,就必须执行以下步骤:

将我们的 tbio_config.jsp 文件自定义版本复制到目录

[wp_profile root]/PortalServer/wcm/shared/app/config/textboxio

中(如果该目录不存在,请提前创建它)。

从目录 [wp_profile root]/ConfigEngine 运行命令

ConfigEngine.sh configure-wcm-ephox-editor-custom-configuration -DWasPassword=password -DPortalAdminId=wpsadmin -DPortalAdminPwd=password

这可能需要几分钟时间才能完成。最后,我们应验证所有功能都运行正常(我们必须看到 BUILD SUCCESSFUL,而不是 BUILD FAILED):

如果看到 BUILD FAILED,请验证该命令的语法、命令的参数和配置文件是否位于正确的目录中,然后重新启动该命令,即使未找到任何错误也是如此。

  • 重新启动 WebSphere Portal。

Textbox.io 服务

Texbox.io 附加服务通过 WebSphere 企业应用程序 EphoxTbioServices 以 tbioServices_wcm.ear 文件的形式提供。如果未安装该应用程序,则必须在 WebSphere 控制台中选择 Applications -> Application Types -> WebSphere Enterprise Applications 安装它。可在

[WebSphere Portal root]/installableApps

目录中找到要安装的文件。确认该应用程序已启动同样很重要:

Textbox.io 服务包含以下 3 个模块:

  • ephox-allowed-origins.war 允许所有服务器端组件与指定的域通信,现在已弃用。
  • ephox-spelling.war 管理拼写检查和自动更正功能。
  • ephox-image-proxy.war 允许编辑 Web 图像。

可在 WAS 控制台屏幕上看到这些模块,选择 Enterprise Applications -> EphoxTbioServices -> Manage Modules 可访问该屏幕:

可在 [7] 看到更多细节。

拼写检查
如果 Textbox.io 服务已启动,则会启用拼写检查。当您键入内容时,它包含针对一些常用语言的可选服务器端拼写检查和自动更正功能。

拼写检查和 Web 图像插入需要将多个服务器端组件部署到 WebSphere Application Server 上。不需要更多的配置/自定义活动,除非 EphoxTbioServices 安装在不同服务器上。

可在 [8] 和 [9] 看到更多细节。

参考资料


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=1050085
ArticleTitle=如何自定义 Ephox Textbox.io for IBM Web Content Manager 8.5
publish-date=09212017