Textbox.io for IBM WCM : Customising Textbox.io

Please contact IBM if you require support.

Customization Steps

Please refer to the following IBM Knowledge Center article:

https://www.ibm.com/support/knowledgecenter/SSYJ99_8.5.0/wcm/wcm_config_ephox_custom.html

Examples

Info:

For more information regarding the customization of Textbox.io, please see:

http://docs.ephox.com/display/tbio/configuration

Caution:

Many items in the default configuration have WCM specific values. Altering these values may have a detrimental impact on your editing experience.

Ephox advises that changes only be made to the following items:

  • paste
  • ui
  • isSpellCheckingEnabled
  • isImageProxyEnabled
  • isLinkValidationEnabled
  • isMediaEmbedEnabled
  • isToneEnabled

 

The sample configuration provided by IBM contains the following base configuration object:

...
  var config = {
    css: {
      stylesheets: styleSheetUrl ? [styleSheetUrl] : []
    },
    images: {
      editing: {
        proxy: isImageProxyEnabled ? urlImageProxyService : undefined
      },
      allowLocal: !isLimitedToLibraryImagePicker
    },
    ui: {
      locale: locale,
      toolbar: {
        items: items
      }
    },
    codeview: {
      enabled: isCodeViewEnabled
    },
    spelling: {
      url: isSpellCheckingEnabled ? urlSpellingService : undefined
    },
    links: {
      validation: {
        url: isLinkValidationEnabled ? urlLinkService : undefined
      },
      embed: {
        url: isMediaEmbedEnabled ? urlLinkService : undefined
      }
    },
    cognitive: {
      tone: {
        url: isToneEnabled ? urlToneService : undefined
      }
    },
    textboxioExtensions: [
    ]
  };
...

 

This base object will be used in the examples.

Once the customizations have been applied, the configuration must be installed as per IBM's documentation.

Enabling/Disabling Service Functionality

Textbox.io makes use of various server-side services to provide spell checking, proxying of images for editing, validation of links, generating media embeds from links, and tone analysis.

These features can be enabled/disabled in the configuration using the following true/false values, which are defined directly above the config object:

 // Disable/Enable services
  var isSpellCheckingEnabled = true;
  var isImageProxyEnabled = true;
  var isLinkValidationEnabled = false;
  var isMediaEmbedEnabled = false;
  var isToneEnabled = false;

To enable a feature set the value to true, to disable set the value to false.

Note that changing the value to true/false only enables/disabled the service; it does not turn the actual service on/off.

To turn the desired service on or off, please see the instructions in the service documentation.

By default the spell checking and image proxy are enabled but the link validation, media embedding and tone analysis need to be enabled. This is because the link validation, media embedding and tone analysis will need additional configuration of the Ephox services to allow access to websites using SSL and to work correctly with proxy servers. Additionally the tone analysis service will need an IBM Watson tone analysis service key.

Modifying Paste Functionality

Textbox.io provides multiple different ways to handle the pasting of content, which can be changed modifying the value of the "paste" configuration property.  The different behaviors are explained here: http://docs.ephox.com/display/tbio/paste

The default value for the "paste" configuration is "retain".  If plain-text pasting is preferred you need to add the "paste" configuration property and set the value to "plain". Like this:

    ,
    paste: {
	  style: 'plain'
	}

So the following configuration would be specified:

...
  var config = {
    css: {
      stylesheets: styleSheetUrl ? [styleSheetUrl] : []
    },
    images: {
      editing: {
        proxy: isImageProxyEnabled ? urlImageProxyService : undefined
      },
      allowLocal: !isLimitedToLibraryImagePicker
    },
    ui: {
      locale: locale,
      toolbar: {
        items: items
      }
    },
    codeview: {
      enabled: isCodeViewEnabled
    },
    spelling: {
      url: isSpellCheckingEnabled ? urlSpellingService : undefined
    },
    links: {
      validation: {
        url: isLinkValidationEnabled ? urlLinkService : undefined
      },
      embed: {
        url: isMediaEmbedEnabled ? urlLinkService : undefined
      }
    },
    cognitive: {
      tone: {
        url: isToneEnabled ? urlToneService : undefined
      }
    },
    textboxioExtensions: [
    ],
    paste: {
	  style: 'plain'
	}
  };
...

Adding Additional Buttons

Adding buttons to the editor provides a convenient way of exposing new functionality to users.

The IBM sample configuration contains a helper function for building buttons.  It has the following signature: 

button(id, tooltipText, iconPath, action);

The action parameter is a function with the following signature:

function(wcmEditorId) {
  // do action here
}

The wcmEditorId can be used to access Textbox.io and modify its contents.

Example snippets

Refer to the API reference for full details but here are a few examples.

Get editor content

Get the body of the editor document as HTML.

var content = ephox.wcm.api.getEditor(wcmEditorId).getBody();

Get selected text

Get the currently selected text. All HTML tags will be removed from the returned value.

var selectedText = ephox.wcm.api.getEditor(wcmEditorId).getSelection().getText();

Get selected HTML

Get the currently selected HTML. Partially selected tags are automatically closed at the selection boundaries.

var selectedHtml = ephox.wcm.api.getEditor(wcmEditorId).getSelection().getHtml()

Replace editor content

Completely replace the body of the editor document with the new HTML.

Side effects

  • This will clear the current selection.
  • This will clear the undo list.
ephox.wcm.api.getEditor(wcmEditorId).setBody(content);

Replace selection

Overwrite the current selection with the HTML snippet or insert it at the current cursor position if no selection has been made.

Side effects

  • This will clear the current selection.
ephox.wcm.api.getEditor(wcmEditorId).insertHtmlAtCursor(htmlSnippet);

Get text direction

 Query the text direction.

var dir = ephox.wcm.api.getEditor(wcmEditorId).getDirection();

Basic Example

The following example:

  • Creates a new button
  • Creates a new button group containing the new button
  • Adds the new button group to the default toolbar.

 

...
 
  // Create a new button using the helper function.
  var newButton = button("newButton", "New button tooltip",
                         "<path-to-an icon>", function(wcmEditorId) {alert("Click!");});
  // Create a new button group
  var newGroup = {
    label: "New Button Group",
    items: [newButton]
  };
  // Append the new button to the existing array of toolbar items.
  items.push(newGroup);
 
  var config = {
    css: {
      stylesheets: styleSheetUrl ? [styleSheetUrl] : []
    },
    images: {
      editing: {
        proxy: isImageProxyEnabled ? urlImageProxyService : undefined
      },
      allowLocal: !isLimitedToLibraryImagePicker
    },
    ui: {
      locale: locale,
      toolbar: {
        items: items
      }
    },
    codeview: {
      enabled: isCodeViewEnabled
    },
    spelling: {
      url: isSpellCheckingEnabled ? urlSpellingService : undefined
    },
    links: {
      validation: {
        url: isLinkValidationEnabled ? urlLinkService : undefined
      },
      embed: {
        url: isMediaEmbedEnabled ? urlLinkService : undefined
      }
    },    
	cognitive: {
      tone: {
        url: isToneEnabled ? urlToneService : undefined
      }
    },
    textboxioExtensions: [
    ]
  };
 
...

Advanced Example

The following example:

  • Creates two buttons.
  • Creates a new item group.
  • Adds the new group to the toolbar.
  • Shows how you might change the document with buttons

...
 
  // ROT13 encode some English alphabet text leaving all other characters untouched
  var rot13Text = function(text) {
    var out = '';
    var char_a = 'a'.charCodeAt(0);
    var char_z = 'z'.charCodeAt(0);
    var char_A = 'A'.charCodeAt(0);
    var char_Z = 'Z'.charCodeAt(0);
    for (var i = 0; i < text.length; i++) {
      var char = text.charCodeAt(i);
      var outChar;
      if (char >= char_a && char <= char_z) {
        out += String.fromCharCode((((char - char_a) + 13) % 26) + char_a);
      } else if (char >= char_A && char <= char_Z) {
        out += String.fromCharCode((((char - char_A) + 13) % 26) + char_A);
      } else {
        out += text.charAt(i);
      }
    }
    return out;
  };
 
  // ROT13 encode the text nodes in some HTML, leaving the HTML untouched
  var rot13Html = function(html) {
    var container = document.createElement('div');
    container.innerHTML = html;
    // depth first traversal
    var node = container.firstChild;
    while (node !== null && node !== container) {
      // check for a text node
      if (node.nodeType === 3) {
        node.nodeValue = rot13Text(node.nodeValue);
      }
      // transition to next node in depth first traversal
      if (node.firstChild) {
        node = node.firstChild;
      } else if (node.nextSibling) {
        node = node.nextSibling;
      } else {
        // find the first parent that has a sibling and transition to it
        node = node.parentNode;
        while (node !== null && node !== container && node.nextSibling === null) {
          node = node.parentNode;
        }
        if (node !== null && node !== container) {
          node = node.nextSibling;
		}
      }
    }
    return container.innerHTML;
  };
 
  // ROT13 encode the entire editor contents
  // Side effects:
  //   - Will clear the undos
  //   - Will loose selection
  var rot13Body = function(wcmEditorId) {
    var editor = ephox.wcm.api.getEditor(wcmEditorId);
    editor.setBody(rot13Html(editor.getBody()));
  };
 
  // ROT13 encode the currently selected text
  // Side effects:
  //   - Will loose selection
  var rot13Selection = function(wcmEditorId) {
	var editor = ephox.wcm.api.getEditor(wcmEditorId);
    var selection = editor.getSelection();
    editor.insertHtmlAtCursor(rot13Html(selection.getHtml()));
  };
 
  // Create 2 new buttons using the helper function.
  var encryptDocumentButton = button("encryptDocument", "Encrypt the entire document",
                                     "<path-to-an icon>", rot13Body);
  var encryptSelectionButton = button("encryptSelection", "Encrypt the current selection",
                                      "<path-to-an icon>", rot13Selection);

  // Create a group to hold the buttons
  var encryptionButtonGroup = {
    label: 'Encryption',
    items: [encryptDocumentButton, encryptSelectionButton]
  };
 
  // Append the new group to the existing array of toolbar items.
  items.push(encryptionButtonGroup);
 
  var config = {
    css: {
      stylesheets: styleSheetUrl ? [styleSheetUrl] : []
    },
    images: {
      editing: {
        proxy: isImageProxyEnabled ? urlImageProxyService : undefined
      },
      allowLocal: !isLimitedToLibraryImagePicker
    },
    ui: {
      locale: locale,
      toolbar: {
        items: items
      }
    },
    codeview: {
      enabled: isCodeViewEnabled
    },
    spelling: {
      url: isSpellCheckingEnabled ? urlSpellingService : undefined
    },
    links: {
      validation: {
        url: isLinkValidationEnabled ? urlLinkService : undefined
      },
      embed: {
        url: isMediaEmbedEnabled ? urlLinkService : undefined
      }
    },
    cognitive: {
      tone: {
        url: isToneEnabled ? urlToneService : undefined
      }
    },
    textboxioExtensions: [
    ]
  };
 
...

 

Modifying Existing Editor Configurations

It may be desirable to remove functionality from the editor in certain situations.

The following example:

  • Creates a new toolbar configuration without the "Insert" button group.
  • Adds the new configuration to the toolbar.

...
 
  // This is the default definition for the toolbar items in the sample config
  var items = flatten([
    ['undo', insertGroup, 'style', 'emphasis', 'align', 'listindent'],
    styleGroup('format'),
    [languageGroup, toolsGroup]
  ]);
 
  // Creating a new array of items, filtering out the one  
  var newItems = items.filter(function(item) {return item !== insertGroup});
 
  var config = {
    css: {
      stylesheets: styleSheetUrl ? [styleSheetUrl] : []
    },
    images: {
      editing: {
        proxy: isImageProxyEnabled ? urlImageProxyService : undefined
      },
      allowLocal: !isLimitedToLibraryImagePicker
    },
    ui: {
      locale: locale,
      toolbar: {
        items: items
      }
    },
    codeview: {
      enabled: isCodeViewEnabled
    },
    spelling: {
      url: isSpellCheckingEnabled ? urlSpellingService : undefined
    },
    links: {
      validation: {
        url: isLinkValidationEnabled ? urlLinkService : undefined
      },
      embed: {
        url: isMediaEmbedEnabled ? urlLinkService : undefined
      }
    },
    cognitive: {
      tone: {
        url: isToneEnabled ? urlToneService : undefined
      }
    },
    textboxioExtensions: [
    ]
  };
 
...

Attachments: