Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic Content Handler #1078

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build/ctct-generic-content-handler-changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- **ENHANCEMENT**: Enhances the generic content handler removeComments function to remove comments that appear
as text nodes in Firefox. This change also refactors the code in the generic content handler to
be configuration driven. Each sub-handler run by this content handler can be enabled or disabled
via the Aloha.settings.contentHandler.handler.generic configuration.
21 changes: 18 additions & 3 deletions doc/guides/source/plugin_contenthandler.textile
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ h4. Writing your own Content Handler
['aloha', 'jquery', 'aloha/contenthandlermanager'],
function(Aloha, jQuery, ContentHandlerManager) {
"use strict";

var MyContentHandler = ContentHandlerManager.createHandler({
handleContent: function( content ) {

// do something with the content

return content; // return as HTML text not jQuery/DOM object
}
});
Expand Down Expand Up @@ -157,6 +157,21 @@ The Generic Content Handler is a bit less generic than his name might suggest as
** +<s>+ and +<strike>+ to +<del>+
** and by removing +<u>+ tags (as underline text decoration should solely be used for links)

You may specify which of the above cleaning actions will e run bgy supplying your own configuration based on these default settings:

<javascript>
Aloha.settings.contentHandler.generic = {
prepareTableContents: true,
cleanLists: true,
removeComments: true,
unwrapTags: true,
removeStyles: true,
removeNamespacedElements: true,
transformFormattings: true,
transformLinks: false
}
</javascript>

h4. Sanitize Content Handler

WARNING: The Sanitize Content Handler does not work reliably on IE7, and will therefore just do nothing, if this browser is detected.
Expand Down
307 changes: 166 additions & 141 deletions src/plugins/common/contenthandler/lib/genericcontenthandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,128 +29,156 @@ define([
'aloha',
'aloha/contenthandlermanager',
'contenthandler/contenthandler-utils'
], function (
$,
Aloha,
Manager,
Utils
) {
], function ($, Aloha, Manager, Utils ) {
'use strict';

/**
* Tags used for semantic formatting
* @type {Array.<String>}
* @see GenericContentHandler#transformFormattings
*/
var formattingTags = ['strong', 'em', 's', 'u', 'strike'];

/**
* Checks whether the markup describes a paragraph that is propped by
* a <br> tag but is otherwise empty.
*
* Will return true for:
*
* <p id="foo"><br class="bar" /></p>
*
* as well as:
*
* <p><br></p>
*
* @param {string} html Markup
* @return {boolean} True if html describes a propped paragraph.
*/
function isProppedParagraph(html) {
var trimmed = $.trim(html);
if (!trimmed) {
return false;
}
var node = $('<div>' + trimmed + '</div>')[0];
var containsSingleP = node.firstChild === node.lastChild
&& 'p' === node.firstChild.nodeName.toLowerCase();
if (containsSingleP) {
var kids = node.firstChild.children;
return (kids && 1 === kids.length &&
'br' === kids[0].nodeName.toLowerCase());
}
return false;
}


/**
* Transforms all tables in the given content to make them ready to for
* use with Aloha's table handling.
*
* Cleans tables of their unwanted attributes.
* Normalizes table cells.
*
* @param {jQuery.<HTMLElement>} $content
*/
function prepareTables($content) {
// Because Aloha does not provide a way for the editor to
// manipulate borders, cellspacing, cellpadding in tables.
// @todo what about width, height?
$content.find('table')
.removeAttr('cellpadding')
.removeAttr('cellspacing')
.removeAttr('border')
.removeAttr('border-top')
.removeAttr('border-bottom')
.removeAttr('border-left')
.removeAttr('border-right');

$content.find('td').each(function () {
var td = this;

// Because cells with a single empty <p> are rendered to appear
// like empty cells, it simplifies the handeling of cells to
// normalize these table cells to contain actual white space
// instead.
if (isProppedParagraph(td.innerHTML)) {
td.innerHTML = '&nbsp;';
}

// Because a single <p> wrapping the contents of a <td> is
// initially superfluous and should be stripped out.
var $p = $('>p', td);
if (1 === $p.length) {
$p.contents().unwrap();
}
});

// Because Aloha does not provide a means for editors to manipulate
// these properties.
$content.find('table,th,td,tr')
.removeAttr('width')
.removeAttr('height')
.removeAttr('valign');

// Because Aloha table handling simply does not regard colgroups.
// @TODO Use sanitize.js?
$content.find('colgroup').remove();
}

/**
* Return true if the nodeType is allowed in the settings,
* Aloha.settings.contentHandler.allows.elements
*
* @param {String} nodeType The tag name of the element to evaluate
*
* @return {Boolean}
*/
function isAllowedNodeName(nodeType){
return !!(
Aloha.settings.contentHandler
&& Aloha.settings.contentHandler.allows
&& Aloha.settings.contentHandler.allows.elements
&& ($.inArray(
nodeType.toLowerCase(),
Aloha.settings.contentHandler.allows.elements
) !== -1
)
);
}

var GenericContentHandler = Manager.createHandler({
var defaultConfig = {
handlers: {
prepareTableContents: true,
cleanLists: true,
removeComments: true,
unwrapTags: true,
removeStyles: true,
removeNamespacedElements: true,
transformFormattings: true,
transformLinks: false
}
};

var handlers = (function() {
var config = {};
var init = function() {
if (Aloha.settings.contentHandler
&& Aloha.settings.contentHandler.handler
&& Aloha.settings.contentHandler.handler.generic) {
config = jQuery.extend({}, defaultConfig.handlers, Aloha.settings.contentHandler.handler.generic);
} else {
config = defaultConfig.handlers;
}
};

return {
config: function() {
init();
return config;
}
};
})();

/**
* Tags used for semantic formatting
* @type {Array.<String>}
* @see GenericContentHandler#transformFormattings
*/
var formattingTags = ['strong', 'em', 's', 'u', 'strike'];

/**
* Checks whether the markup describes a paragraph that is propped by
* a <br> tag but is otherwise empty.
*
* Will return true for:
*
* <p id="foo"><br class="bar" /></p>
*
* as well as:
*
* <p><br></p>
*
* @param {string} html Markup
* @return {boolean} True if html describes a propped paragraph.
*/
function isProppedParagraph(html) {
var trimmed = $.trim(html);
if (!trimmed) {
return false;
}
var node = $('<div>' + trimmed + '</div>')[0];
var containsSingleP = node.firstChild === node.lastChild
&& 'p' === node.firstChild.nodeName.toLowerCase();
if (containsSingleP) {
var kids = node.firstChild.children;
return (kids && 1 === kids.length &&
'br' === kids[0].nodeName.toLowerCase());
}
return false;
}


/**
* Transforms all tables in the given content to make them ready to for
* use with Aloha's table handling.
*
* Cleans tables of their unwanted attributes.
* Normalizes table cells.
*
* @param {jQuery.<HTMLElement>} $content
*/
function prepareTables($content) {
// Because Aloha does not provide a way for the editor to
// manipulate borders, cellspacing, cellpadding in tables.
// @todo what about width, height?
$content.find('table')
.removeAttr('cellpadding')
.removeAttr('cellspacing')
.removeAttr('border')
.removeAttr('border-top')
.removeAttr('border-bottom')
.removeAttr('border-left')
.removeAttr('border-right');

$content.find('td').each(function () {
var td = this;

// Because cells with a single empty <p> are rendered to appear
// like empty cells, it simplifies the handeling of cells to
// normalize these table cells to contain actual white space
// instead.
if (isProppedParagraph(td.innerHTML)) {
td.innerHTML = '&nbsp;';
}

// Because a single <p> wrapping the contents of a <td> is
// initially superfluous and should be stripped out.
var $p = $('>p', td);
if (1 === $p.length) {
$p.contents().unwrap();
}
});

// Because Aloha does not provide a means for editors to manipulate
// these properties.
$content.find('table,th,td,tr')
.removeAttr('width')
.removeAttr('height')
.removeAttr('valign');

// Because Aloha table handling simply does not regard colgroups.
// @TODO Use sanitize.js?
$content.find('colgroup').remove();
}

/**
* Return true if the nodeType is allowed in the settings,
* Aloha.settings.contentHandler.allows.elements
*
* @param {String} nodeType The tag name of the element to evaluate
*
* @return {Boolean}
*/
function isAllowedNodeName(nodeType){
return !!(
Aloha.settings.contentHandler
&& Aloha.settings.contentHandler.allows
&& Aloha.settings.contentHandler.allows.elements
&& ($.inArray(
nodeType.toLowerCase(),
Aloha.settings.contentHandler.allows.elements
) !== -1
)
);
}

var GenericContentHandler = Manager.createHandler({

/**
* Transforms pasted content to make it safe and ready to be used in
Expand All @@ -173,31 +201,25 @@ define([
return $content.html();
}

prepareTables($content);
this.cleanLists($content);
this.removeComments($content);
this.unwrapTags($content);
this.removeStyles($content);
this.removeNamespacedElements($content);
//this.transformLinks($content);

var transformFormatting = true;

if (Aloha.settings.contentHandler
&& Aloha.settings.contentHandler.handler
&& Aloha.settings.contentHandler.handler.generic
&& typeof Aloha.settings.contentHandler.handler.generic.transformFormattings !== 'undefinded'
&& !Aloha.settings.contentHandler.handler.generic.transformFormattings ) {
transformFormatting = false;
}

if (transformFormatting) {
this.transformFormattings($content);
}
// Run each configured handler.
var handler, config = handlers.config();
for (handler in config) {
if (config[handler]) {
this[handler]($content);
}
}

return $content.html();
},

/**
* Prepares table contents
* @param $content
*/
prepareTableContents: function($content) {
prepareTables($content);
},

/**
* Cleans lists.
* The only allowed children of ol or ul elements are li's. Everything
Expand Down Expand Up @@ -274,6 +296,9 @@ define([
content.contents().each(function () {
if (this.nodeType === 8) {
$(this).remove();
} else if (this.nodeType == 3) {
// In FF, comments appear as text nodes
this.textContent = this.textContent.replace(/<!--[\s\S]*?-->/g, '');
} else {
// do recursion
that.removeComments($(this));
Expand Down