1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-17 22:47:59 +00:00
seahub/media/aloha-0.22.7/lib/aloha/ephemera.js
llj 720ac28c22 [aloha] upgraded to 0.22.7 and added textcolor plugin
* textcolor: fixed plugin bugs, added translation for zh
* image: fixed default.jpg src bug
* added 'ru' support for seaf edit
* rm aloha-0.22.3 and ununsed files in aloha-0.22.7
2013-01-15 14:48:04 +08:00

457 lines
14 KiB
JavaScript

/* ephemera.js is part of Aloha Editor project http://aloha-editor.org
*
* Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
* Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
* Contributors http://aloha-editor.org/contribution.php
*
* Aloha Editor is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* Aloha Editor 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* As an additional permission to the GNU GPL version 2, you may distribute
* non-source (e.g., minimized or compacted) forms of the Aloha-Editor
* source code without the copy of the GNU GPL normally required,
* provided you include this license notice and a URL through which
* recipients can access the Corresponding Source.
*/
/**
* Provides functions to mark the contents of editables as ephemeral. An
* editable's ephemeral content will be pruned before it is being
* returned by editable.getContents().
*
* It is planned to replace most instances of makeClean() with this
* implementation for improved performance and more importantly, in
* order to have a centralized place that has the control over all
* ephemeral content, which can be leveraged by plugins to provide more
* advanced functionality.
*
* Some examples that would be possible:
* * a HTML source code text box, an interactive tree structure, or
* other kind of DOM visualization, next to the editable, that
* contains just the content of the editable (without ephemeral data)
* and which is updated efficiently in real time after each keystroke.
*
* * change detection algorithms that are able to intelligently ignore
* ephemeral data and which would not trigger unless non-ephemeral
* data is added to the editable.
*
* * When a plugin provides very general functionality over all nodes of
* the DOM, somtimes the plugin may not know what is and what isn't
* supposed to be real content. The functionality provided here makes
* it possible for the plugin to exaclty distinguish real content from
* ephemeral content.
*
* TODO: currently only simple transformations are suppored, like
* marking classes, attributes and elements as ephemeral and removing
* them during the pruning process.
* In the future, support for the block-plugin and custom pruning
* functions should be added. This may be done by letting implementations
* completely control the pruning of a DOM element through a
* function that takes the content+ephemeral-data and returns only
* content - similar to make clean, but for single elements to reduce
* overhead.
*/
define([
'jquery',
'aloha/core',
'aloha/console',
'util/strings',
'util/trees',
'util/arrays',
'util/maps',
'util/dom2',
'util/functions',
'util/misc',
'PubSub'
], function (
$,
Aloha,
console,
Strings,
Trees,
Arrays,
Maps,
Dom,
Functions,
Misc,
PubSub
) {
'use strict';
var ephemeraMap = {
classMap: {
'aloha-cleanme': true,
'aloha-ui-wrapper': true,
'aloha-ui-filler': true,
'aloha-ui-attr': true
},
attrMap: {
'hidefocus': true,
'hideFocus': true,
'tabindex': true,
'tabIndex': true,
'TABLE.contenteditable': true,
'TABLE.contentEditable': true
},
attrRxs: [/^(?:nodeIndex|sizcache|sizset|jquery)[\w\d]*$/i],
pruneFns: []
};
var commonClsSubstr = 'aloha-';
/**
* Checks whether the given classes contain the substring common to
* all ephemeral classes. If the check fails, an warning will be
* logged and the substring will be set to the empty string which
* voids the performance improvement the common substring would
* otherwise have gained.
*/
function checkCommonSubstr(clss) {
var i, len;
for (i = 0, len = clss.length; i < len; i++) {
if (-1 === clss[i].indexOf(commonClsSubstr)) {
console.warn('Class "' + clss[i] + '" was set to be ephemeral,' + 'which hurts peformance.' + ' Add the common substring "' + commonClsSubstr + '" to the class to fix this problem.');
commonClsSubstr = '';
}
}
}
/**
* Registers ephemeral classes.
*
* An ephemeral class is a non-content class that will be pruned
* from the from the result of editable.getContents().
*
* The given classes should contain the string 'aloha-' to get the
* benefit of a performance optimization.
*
* Returns a map that contains all classes that were ever registered
* with this function.
*
* Multiple classes may be specified. If none are specified, just
* returns the current ephemeral classes map without modifying it.
*
* Also see ephemera().
*/
function classes() {
var clss = Array.prototype.slice.call(arguments);
Maps.fillKeys(ephemeraMap.classMap, clss, true);
checkCommonSubstr(clss);
PubSub.pub('aloha.ephemera.classes', {
ephemera: ephemeraMap,
newClasses: clss
});
}
/**
* Registers ephemeral attributes by attribute name.
*
* Similar to classes() except applies to entire attributes instead
* of individual classes in the class attribute.
*/
function attributes() {
var attrs = Array.prototype.slice.call(arguments);
Maps.fillKeys(ephemeraMap.attrMap, attrs, true);
PubSub.pub('aloha.ephemera.attributes', {
ephemera: ephemeraMap,
newAttributes: attrs
});
}
/**
* Merges a map containing values to identify ephemeral content into
* a global registry.
*
* The given map may have the following entries
* classMap - a map from class name to the value true
* attrMap - a map from attribute name to the value true; attribute
* names may be optionally prefixed with "ELEMENT.",
* where ELEMENT is the name of an element in uppercase,
* to prune only from specific elements. An element name prefix
* should always be specified if it is known, and if
* multiple are known, multiple entries with separate
* element prefixes should be made instead of a single
* entry without - preserve information for refactoring.
* attrRxs - an array of regexes in object form (/[a-z].../ and not "[a-z]...")
* pruneFns - an array of functions that will be called at each pruning step.
*
* Returns the global registry, which has the same structure as above.
*
* When a DOM tree is pruned with prune(elem) without an emap
* argument, the global registry maintained with classes()
* attributes() and ephemera() is used as a default map. If an emap
* argument is specified, the global registry will be ignored and
* the emap argument will be used instead.
*
* When a DOM tree is pruned with prune()
* * classes specified by classMap will be removed
* * attributes specified by attrMap or attrRxs will be removed
* * functions specified by pruneFns will be called as the DOM tree
* is descended into (pre-order), with each node (element, text,
* etc.) as a single argument. The function is free to modify the
* element and return it, or return a new element which will
* replace the given element in the pruned tree. If null or
* undefined is returned, the element will be removed from the
* tree. As per contract of Maps.walkDomInplace, it is allowed to
* insert/remove children in the parent node as long as the given
* node is not removed.
*
* Also see classes() and attributes().
*
* Note that removal of attributes doesn't always work on IE7 (in
* rare special cases). The dom-to-xhtml plugin can reliably remove
* ephemeral attributes during the serialization step.
*/
function ephemera(emap) {
if (emap) {
if (emap.classMap) {
$.extend(ephemeraMap.classMap, emap.classMap);
}
if (emap.attrMap) {
$.extend(ephemeraMap.attrMap, emap.attrMap);
}
if (emap.attrRxs) {
ephemeraMap.attrRxs = ephemeraMap.attrRxs.concat(emap.attrRxs);
}
if (emap.pruneFns) {
ephemeraMap.pruneFns = ephemeraMap.pruneFns.concat(emap.pruneFns);
}
PubSub.pub('aloha.ephemera', {
ephemera: ephemeraMap,
newEphemera: emap
});
}
return ephemeraMap;
}
/**
* Marks an element as ephemeral.
*
* The element will be completely removed when the prune function is
* called on it.
*/
function markElement(elem) {
$(elem).addClass('aloha-cleanme');
}
/**
* Marks the attribute of an element as ephemeral.
*
* The attribute will be removed from the element when the prune
* function is called on it.
*
* Multiple attributes can be passed at the same time be separating
* them with a space.
*/
function markAttribute(elem, attr) {
elem = $(elem);
var data = elem.attr('data-aloha-ui-attr');
data = (null == data || '' === data ? attr : data + ' ' + attr);
elem.attr('data-aloha-ui-attr', data);
elem.addClass('aloha-ui-attr');
}
/**
* Marks an element as a ephemeral, excluding subnodes.
*
* The element will be removed when the prune function is called on
* it, but any children of the wrapper element will remain in its
* place.
*
* A wrapper is an element that wraps a single non-ephemeral
* element. A filler is an element that is wrapped by a single
* non-ephemeral element. This distinction is not important for the
* prune function, which behave the same for both wrappers and
* fillers, but it makes it easier to build more advanced content
* inspection algorithms (also see note at the header of ephemeral.js).
*
* NB: a wrapper element must not wrap a filler element. Wrappers
* and fillers are ephermeral. A wrapper must always wrap a
* single _non-ephemeral_ element, and a filler must always fill
* a single _non-ephemeral_ element.
*/
function markWrapper(elem) {
$(elem).addClass('aloha-ui-wrapper');
}
/**
* Marks an element as ephemeral, excluding subnodes.
*
* See wrapper()
*/
function markFiller(elem) {
$(elem).addClass('aloha-ui-filler');
}
/**
* Prunes attributes marked as ephemeral with Ephemera.attributes()
* from the given element.
*/
function pruneMarkedAttrs(elem) {
var $elem = $(elem);
var data = $elem.attr('data-aloha-ui-attr');
var i;
var attrs;
$elem.removeAttr('data-aloha-ui-attr');
if (typeof data === 'string') {
attrs = Strings.words(data);
for (i = 0; i < attrs.length; i++) {
$elem.removeAttr(attrs[i]);
}
}
}
/**
* Determines whether the given attribute of the given element is
* ephemeral according to the given emap.
* See Ephemera.ephemera() for an explanation of attrMap and attrRxs.
*/
function isAttrEphemeral(elem, attrName, attrMap, attrRxs) {
return attrMap[attrName] || Misc.anyRx(attrRxs, attrName) || attrMap[elem.nodeName + '.' + attrName];
}
/**
* Prunes attributes specified with either emap.attrMap or emap.attrRxs.
* See ephemera().
*/
function pruneEmapAttrs(elem, emap) {
var $elem = null,
attrs = Dom.attrNames(elem),
name,
i,
len;
for (i = 0, len = attrs.length; i < len; i++) {
name = attrs[i];
if (isAttrEphemeral(elem, name, emap.attrMap, emap.attrRxs)) {
$elem = $elem || $(elem);
$elem.removeAttr(name);
}
}
}
/**
* Prunes an element of attributes and classes or removes the
* element by returning false.
*
* Elements attributes and classes can either be marked as
* ephemeral, in which case the element itself will contain the
* prune-info, or they can be specified as ephemeral with the given
* emap.
*
* See ephemera() for an explanation of the emap argument.
*/
function pruneElem(elem, emap) {
var className = elem.className;
if (className && -1 !== className.indexOf(commonClsSubstr)) {
var classes = Strings.words(className);
// Ephemera.markElement()
if (-1 !== Arrays.indexOf(classes, 'aloha-cleanme')) {
$.removeData(elem); // avoids memory leak
return false; // removes the element
}
// Ephemera.markWrapper() and Ephemera.markFiller()
if (-1 !== Arrays.indexOf(classes, 'aloha-ui-wrapper') || -1 !== Arrays.indexOf(classes, 'aloha-ui-filler')) {
Dom.moveNextAll(elem.parentNode, elem.firstChild, elem.nextSibling);
$.removeData(elem);
return false;
}
// Ephemera.markAttribute()
if (-1 !== Arrays.indexOf(classes, 'aloha-ui-attr')) {
pruneMarkedAttrs(elem);
}
// Ephemera.classes() and Ehpemera.ephemera({ classMap: {} })
var persistentClasses = Arrays.filter(classes, function (cls) {
return !emap.classMap[cls];
});
if (persistentClasses.length !== classes.length) {
if (0 === persistentClasses.length) {
// Removing the attributes is dangerous. Aloha has a
// jquery patch in place to fix some issue.
$(elem).removeAttr('class');
} else {
elem.className = persistentClasses.join(' ');
}
}
}
// Ephemera.attributes() and Ephemera.ephemera({ attrMap: {}, attrRxs: {} })
pruneEmapAttrs(elem, emap);
return true;
}
/**
* Called for each node during the pruning of a DOM tree.
*/
function pruneStep(emap, step, node) {
if (1 === node.nodeType) {
if (!pruneElem(node, emap)) {
return [];
}
node = Trees.walkDomInplace(node, step);
}
// Ephemera.ephemera({ pruneFns: [] })
node = Arrays.reduce(emap.pruneFns, node, Arrays.applyNotNull);
if (!node) {
return [];
}
return [node];
}
/**
* Prunes the given element of all ephemeral data.
*
* Elements marked with Ephemera.markElement() will be removed.
* Attributes marked with Ephemera.markAttribute() will be removed.
* Elements marked with Ephemera.markWrapper() or
* Ephemera.markFiller() will be replaced with their children.
*
* See ephemera() for an explanation of the emap argument.
*
* All properties of emap, if specified, are required, but may be
* empty.
*
* The element is modified in-place and returned.
*/
function prune(elem, emap) {
emap = emap || ephemeraMap;
function pruneStepClosure(node) {
return pruneStep(emap, pruneStepClosure, node);
}
return pruneStepClosure(elem)[0];
}
if (Aloha.settings.ephemera) {
ephemera(Aloha.settings.ephemera);
}
return {
ephemera: ephemera,
classes: classes,
attributes: attributes,
markElement: markElement,
markAttribute: markAttribute,
markWrapper: markWrapper,
markFiller: markFiller,
prune: prune,
isAttrEphemeral: isAttrEphemeral
};
});