X-Git-Url: http://git.roojs.org/?p=roojs1;a=blobdiff_plain;f=roojs-debug.js;h=f903c90a4242a29c4f6985780eace68dd011b17d;hp=d2b15ccb1ab0888b3f4c086b3e19e2dcdcb63aa6;hb=d6d1e1fe78ba08b1a873583bb12bf05588ff45ae;hpb=18480d449e889bafc18e683dca94b2ff4729dbd4 diff --git a/roojs-debug.js b/roojs-debug.js index d2b15ccb1a..f903c90a42 100644 --- a/roojs-debug.js +++ b/roojs-debug.js @@ -4907,468 +4907,7 @@ Roo.lib.Easing = { } }; })(); -/** - * Originally based of this code... - refactored for Roo... - * https://github.com/aaalsaleh/undo-manager - - * undo-manager.js - * @author Abdulrahman Alsaleh - * @copyright 2015 Abdulrahman Alsaleh - * @license MIT License (c) - * - * Hackily modifyed by alan@roojs.com - * - * - * - * - * TOTALLY UNTESTED... - * - * Documentation to be done.... - */ - - -/** -* @class Roo.lib.UndoManager -* An undo manager implementation in JavaScript. It follows the W3C UndoManager and DOM Transaction -* Draft and the undocumented and disabled Mozilla Firefox's UndoManager implementation. - - * Usage: - *
-
-
-editor.undoManager = new Roo.lib.UndoManager(1000, editor);
-
-
-
-* For more information see this blog post with examples:
-* DomHelper
- - Create Elements using DOM, HTML fragments and Templates.
-* @constructor
-* @param {Number} limit how far back to go ... use 1000?
-* @param {Object} scope usually use document..
-*/
-
-Roo.lib.UndoManager = function (limit, undoScopeHost)
-{
- this.stack = [];
- this.limit = limit;
- this.scope = undoScopeHost;
- this.fireEvent = typeof CustomEvent != 'undefined' && undoScopeHost && undoScopeHost.dispatchEvent;
- if (this.fireEvent) {
- this.bindEvents();
- }
- this.reset();
-
-};
-
-Roo.lib.UndoManager.prototype = {
-
- limit : false,
- stack : false,
- scope : false,
- fireEvent : false,
- position : 0,
- length : 0,
-
-
- /**
- * To push and execute a transaction, the method undoManager.transact
- * must be called by passing a transaction object as the first argument, and a merge
- * flag as the second argument. A transaction object has the following properties:
- *
- * Usage:
-
-undoManager.transact({
- label: 'Typing',
- execute: function() { ... },
- undo: function() { ... },
- // redo same as execute
- redo: function() { this.execute(); }
-}, false);
-
-// merge transaction
-undoManager.transact({
- label: 'Typing',
- execute: function() { ... }, // this will be run...
- undo: function() { ... }, // what to do when undo is run.
- // redo same as execute
- redo: function() { this.execute(); }
-}, true);
-
- *
- *
- * @param {Object} transaction The transaction to add to the stack.
- * @return {String} The HTML fragment
- */
-
-
- transact : function (transaction, merge)
- {
- if (arguments.length < 2) {
- throw new TypeError('Not enough arguments to UndoManager.transact.');
- }
-
- transaction.execute();
-
- this.stack.splice(0, this.position);
- if (merge && this.length) {
- this.stack[0].push(transaction);
- } else {
- this.stack.unshift([transaction]);
- }
-
- this.position = 0;
-
- if (this.limit && this.stack.length > this.limit) {
- this.length = this.stack.length = this.limit;
- } else {
- this.length = this.stack.length;
- }
-
- if (this.fireEvent) {
- this.scope.dispatchEvent(
- new CustomEvent('DOMTransaction', {
- detail: {
- transactions: this.stack[0].slice()
- },
- bubbles: true,
- cancelable: false
- })
- );
- }
-
- //Roo.log("transaction: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length);
-
-
- },
-
- undo : function ()
- {
- //Roo.log("undo: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length);
-
- if (this.position < this.length) {
- for (var i = this.stack[this.position].length - 1; i >= 0; i--) {
- this.stack[this.position][i].undo();
- }
- this.position++;
-
- if (this.fireEvent) {
- this.scope.dispatchEvent(
- new CustomEvent('undo', {
- detail: {
- transactions: this.stack[this.position - 1].slice()
- },
- bubbles: true,
- cancelable: false
- })
- );
- }
- }
- },
-
- redo : function ()
- {
- if (this.position > 0) {
- for (var i = 0, n = this.stack[this.position - 1].length; i < n; i++) {
- this.stack[this.position - 1][i].redo();
- }
- this.position--;
-
- if (this.fireEvent) {
- this.scope.dispatchEvent(
- new CustomEvent('redo', {
- detail: {
- transactions: this.stack[this.position].slice()
- },
- bubbles: true,
- cancelable: false
- })
- );
- }
- }
- },
-
- item : function (index)
- {
- if (index >= 0 && index < this.length) {
- return this.stack[index].slice();
- }
- return null;
- },
-
- clearUndo : function () {
- this.stack.length = this.length = this.position;
- },
-
- clearRedo : function () {
- this.stack.splice(0, this.position);
- this.position = 0;
- this.length = this.stack.length;
- },
- /**
- * Reset the undo - probaly done on load to clear all history.
- */
- reset : function()
- {
- this.stack = [];
- this.position = 0;
- this.length = 0;
- this.current_html = this.scope.innerHTML;
- if (this.timer !== false) {
- clearTimeout(this.timer);
- }
- this.timer = false;
- this.merge = false;
- this.addEvent();
-
- },
- current_html : '',
- timer : false,
- merge : false,
-
-
- // this will handle the undo/redo on the element.?
- bindEvents : function()
- {
- var el = this.scope;
- el.undoManager = this;
-
-
- this.scope.addEventListener('keydown', function(e) {
- if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) {
- if (e.shiftKey) {
- el.undoManager.redo(); // Ctrl/Command + Shift + Z
- } else {
- el.undoManager.undo(); // Ctrl/Command + Z
- }
-
- e.preventDefault();
- }
- });
- /// ignore keyup..
- this.scope.addEventListener('keyup', function(e) {
- if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) {
- e.preventDefault();
- }
- });
-
-
-
- var t = this;
-
- el.addEventListener('input', function(e) {
- if(el.innerHTML == t.current_html) {
- return;
- }
- // only record events every second.
- if (t.timer !== false) {
- clearTimeout(t.timer);
- t.timer = false;
- }
- t.timer = setTimeout(function() { t.merge = false; }, 1000);
-
- t.addEvent(t.merge);
- t.merge = true; // ignore changes happening every second..
- });
- },
- /**
- * Manually add an event.
- * Normall called without arguements - and it will just get added to the stack.
- *
- */
-
- addEvent : function(merge)
- {
- //Roo.log("undomanager +" + (merge ? 'Y':'n'));
- // not sure if this should clear the timer
- merge = typeof(merge) == 'undefined' ? false : merge;
-
- this.scope.undoManager.transact({
- scope : this.scope,
- oldHTML: this.current_html,
- newHTML: this.scope.innerHTML,
- // nothing to execute (content already changed when input is fired)
- execute: function() { },
- undo: function() {
- this.scope.innerHTML = this.current_html = this.oldHTML;
- },
- redo: function() {
- this.scope.innerHTML = this.current_html = this.newHTML;
- }
- }, false); //merge);
-
- this.merge = merge;
-
- this.current_html = this.scope.innerHTML;
- }
-
-
-
-
-
-
-};
-/**
- * @class Roo.lib.Range
- * @constructor
- * This is a toolkit, normally used to copy features into a Dom Range element
- * Roo.lib.Range.wrap(x);
- *
- *
- *
- */
-Roo.lib.Range = function() { };
-
-/**
- * Wrap a Dom Range object, to give it new features...
- * @static
- * @param {Range} the range to wrap
- */
-Roo.lib.Range.wrap = function(r) {
- return Roo.apply(r, Roo.lib.Range.prototype);
-};
-/**
- * find a parent node eg. LI / OL
- * @param {string|Array} node name or array of nodenames
- * @return {DomElement|false}
- */
-Roo.apply(Roo.lib.Range.prototype,
-{
-
- closest : function(str)
- {
- if (typeof(str) != 'string') {
- // assume it's a array.
- for(var i = 0;i < str.length;i++) {
- var r = this.closest(str[i]);
- if (r !== false) {
- return r;
- }
-
- }
- return false;
- }
- str = str.toLowerCase();
- var n = this.commonAncestorContainer; // might not be a node
- while (n.nodeType != 1) {
- n = n.parentNode;
- }
-
- if (n.nodeName.toLowerCase() == str ) {
- return n;
- }
- if (n.nodeName.toLowerCase() == 'body') {
- return false;
- }
-
- return n.closest(str) || false;
-
- },
- cloneRange : function()
- {
- return Roo.lib.Range.wrap(Range.prototype.cloneRange.call(this));
- }
-});/**
- * @class Roo.lib.Selection
- * @constructor
- * This is a toolkit, normally used to copy features into a Dom Selection element
- * Roo.lib.Selection.wrap(x);
- *
- *
- *
- */
-Roo.lib.Selection = function() { };
-
-/**
- * Wrap a Dom Range object, to give it new features...
- * @static
- * @param {Range} the range to wrap
- */
-Roo.lib.Selection.wrap = function(r, doc) {
- Roo.apply(r, Roo.lib.Selection.prototype);
- r.ownerDocument = doc; // usefull so we dont have to keep referening to it.
- return r;
-};
-/**
- * find a parent node eg. LI / OL
- * @param {string|Array} node name or array of nodenames
- * @return {DomElement|false}
- */
-Roo.apply(Roo.lib.Selection.prototype,
-{
- /**
- * the owner document
- */
- ownerDocument : false,
-
- getRangeAt : function(n)
- {
- return Roo.lib.Range.wrap(Selection.prototype.getRangeAt.call(this,n));
- },
-
- /**
- * insert node at selection
- * @param {DomElement|string} node
- * @param {string} cursor (after|in|none) where to place the cursor after inserting.
- */
- insertNode: function(node, cursor)
- {
- if (typeof(node) == 'string') {
- node = this.ownerDocument.createElement(node);
- if (cursor == 'in') {
- node.innerHTML = ' ';
- }
- }
-
- var range = this.getRangeAt(0);
-
- if (this.type != 'Caret') {
- range.deleteContents();
- }
- var sn = node.childNodes[0]; // select the contents.
-
-
-
- range.insertNode(node);
- if (cursor == 'after') {
- node.insertAdjacentHTML('afterend', ' ');
- sn = node.nextSibling;
- }
-
- if (cursor == 'none') {
- return;
- }
-
- this.cursorText(sn);
- },
-
- cursorText : function(n)
- {
-
- //var range = this.getRangeAt(0);
- range = Roo.lib.Range.wrap(new Range());
- //range.selectNode(n);
-
- var ix = Array.from(n.parentNode.childNodes).indexOf(n);
- range.setStart(n.parentNode,ix);
- range.setEnd(n.parentNode,ix+1);
- //range.collapse(false);
-
- this.removeAllRanges();
- this.addRange(range);
-
- Roo.log([n, range, this,this.baseOffset,this.extentOffset, this.type]);
- },
- cursorAfter : function(n)
- {
- if (!n.nextSibling || n.nextSibling.nodeValue != ' ') {
- n.insertAdjacentHTML('afterend', ' ');
- }
- this.cursorText (n.nextSibling);
- }
-
-
-});/*
+/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
@@ -45066,4377 +44605,7 @@ Roo.extend(Roo.form.Radio, Roo.form.Checkbox, {
}
-});Roo.rtf = {}; // namespace
-Roo.rtf.Hex = function(hex)
-{
- this.hexstr = hex;
-};
-Roo.rtf.Paragraph = function(opts)
-{
- this.content = []; ///??? is that used?
-};Roo.rtf.Span = function(opts)
-{
- this.value = opts.value;
-};
-
-Roo.rtf.Group = function(parent)
-{
- // we dont want to acutally store parent - it will make debug a nightmare..
- this.content = [];
- this.cn = [];
-
-
-
-};
-
-Roo.rtf.Group.prototype = {
- ignorable : false,
- content: false,
- cn: false,
- addContent : function(node) {
- // could set styles...
- this.content.push(node);
- },
- addChild : function(cn)
- {
- this.cn.push(cn);
- },
- // only for images really...
- toDataURL : function()
- {
- var mimetype = false;
- switch(true) {
- case this.content.filter(function(a) { return a.value == 'pngblip' } ).length > 0:
- mimetype = "image/png";
- break;
- case this.content.filter(function(a) { return a.value == 'jpegblip' } ).length > 0:
- mimetype = "image/jpeg";
- break;
- default :
- return 'about:blank'; // ?? error?
- }
-
-
- var hexstring = this.content[this.content.length-1].value;
-
- return 'data:' + mimetype + ';base64,' + btoa(hexstring.match(/\w{2}/g).map(function(a) {
- return String.fromCharCode(parseInt(a, 16));
- }).join(""));
- }
-
-};
-// this looks like it's normally the {rtf{ .... }}
-Roo.rtf.Document = function()
-{
- // we dont want to acutally store parent - it will make debug a nightmare..
- this.rtlch = [];
- this.content = [];
- this.cn = [];
-
-};
-Roo.extend(Roo.rtf.Document, Roo.rtf.Group, {
- addChild : function(cn)
- {
- this.cn.push(cn);
- switch(cn.type) {
- case 'rtlch': // most content seems to be inside this??
- case 'listtext':
- case 'shpinst':
- this.rtlch.push(cn);
- return;
- default:
- this[cn.type] = cn;
- }
-
- },
-
- getElementsByType : function(type)
- {
- var ret = [];
- this._getElementsByType(type, ret, this.cn, 'rtf');
- return ret;
- },
- _getElementsByType : function (type, ret, search_array, path)
- {
- search_array.forEach(function(n,i) {
- if (n.type == type) {
- n.path = path + '/' + n.type + ':' + i;
- ret.push(n);
- }
- if (n.cn.length > 0) {
- this._getElementsByType(type, ret, n.cn, path + '/' + n.type+':'+i);
- }
- },this);
- }
-
-});
-
-Roo.rtf.Ctrl = function(opts)
-{
- this.value = opts.value;
- this.param = opts.param;
-};
-/**
- *
- *
- * based on this https://github.com/iarna/rtf-parser
- * it's really only designed to extract pict from pasted RTF
- *
- * usage:
- *
- * var images = new Roo.rtf.Parser().parse(a_string).filter(function(g) { return g.type == 'pict'; });
- *
- *
- */
-
-
-
-
-
-Roo.rtf.Parser = function(text) {
- //super({objectMode: true})
- this.text = '';
- this.parserState = this.parseText;
-
- // these are for interpeter...
- this.doc = {};
- ///this.parserState = this.parseTop
- this.groupStack = [];
- this.hexStore = [];
- this.doc = false;
-
- this.groups = []; // where we put the return.
-
- for (var ii = 0; ii < text.length; ++ii) {
- ++this.cpos;
-
- if (text[ii] === '\n') {
- ++this.row;
- this.col = 1;
- } else {
- ++this.col;
- }
- this.parserState(text[ii]);
- }
-
-
-
-};
-Roo.rtf.Parser.prototype = {
- text : '', // string being parsed..
- controlWord : '',
- controlWordParam : '',
- hexChar : '',
- doc : false,
- group: false,
- groupStack : false,
- hexStore : false,
-
-
- cpos : 0,
- row : 1, // reportin?
- col : 1, //
-
-
- push : function (el)
- {
- var m = 'cmd'+ el.type;
- if (typeof(this[m]) == 'undefined') {
- Roo.log('invalid cmd:' + el.type);
- return;
- }
- this[m](el);
- //Roo.log(el);
- },
- flushHexStore : function()
- {
- if (this.hexStore.length < 1) {
- return;
- }
- var hexstr = this.hexStore.map(
- function(cmd) {
- return cmd.value;
- }).join('');
-
- this.group.addContent( new Roo.rtf.Hex( hexstr ));
-
-
- this.hexStore.splice(0)
-
- },
-
- cmdgroupstart : function()
- {
- this.flushHexStore();
- if (this.group) {
- this.groupStack.push(this.group);
- }
- // parent..
- if (this.doc === false) {
- this.group = this.doc = new Roo.rtf.Document();
- return;
-
- }
- this.group = new Roo.rtf.Group(this.group);
- },
- cmdignorable : function()
- {
- this.flushHexStore();
- this.group.ignorable = true;
- },
- cmdendparagraph : function()
- {
- this.flushHexStore();
- this.group.addContent(new Roo.rtf.Paragraph());
- },
- cmdgroupend : function ()
- {
- this.flushHexStore();
- var endingGroup = this.group;
-
-
- this.group = this.groupStack.pop();
- if (this.group) {
- this.group.addChild(endingGroup);
- }
-
-
-
- var doc = this.group || this.doc;
- //if (endingGroup instanceof FontTable) {
- // doc.fonts = endingGroup.table
- //} else if (endingGroup instanceof ColorTable) {
- // doc.colors = endingGroup.table
- //} else if (endingGroup !== this.doc && !endingGroup.get('ignorable')) {
- if (endingGroup.ignorable === false) {
- //code
- this.groups.push(endingGroup);
- // Roo.log( endingGroup );
- }
- //Roo.each(endingGroup.content, function(item)) {
- // doc.addContent(item);
- //}
- //process.emit('debug', 'GROUP END', endingGroup.type, endingGroup.get('ignorable'))
- //}
- },
- cmdtext : function (cmd)
- {
- this.flushHexStore();
- if (!this.group) { // an RTF fragment, missing the {\rtf1 header
- //this.group = this.doc
- }
- this.group.addContent(new Roo.rtf.Span(cmd));
- },
- cmdcontrolword : function (cmd)
- {
- this.flushHexStore();
- if (!this.group.type) {
- this.group.type = cmd.value;
- return;
- }
- this.group.addContent(new Roo.rtf.Ctrl(cmd));
- // we actually don't care about ctrl words...
- return ;
- /*
- var method = 'ctrl$' + cmd.value.replace(/-(.)/g, (_, char) => char.toUpperCase())
- if (this[method]) {
- this[method](cmd.param)
- } else {
- if (!this.group.get('ignorable')) process.emit('debug', method, cmd.param)
- }
- */
- },
- cmdhexchar : function(cmd) {
- this.hexStore.push(cmd);
- },
- cmderror : function(cmd) {
- throw new Exception (cmd.value);
- },
-
- /*
- _flush (done) {
- if (this.text !== '\u0000') this.emitText()
- done()
- }
- */
-
-
- parseText : function(c)
- {
- if (c === '\\') {
- this.parserState = this.parseEscapes;
- } else if (c === '{') {
- this.emitStartGroup();
- } else if (c === '}') {
- this.emitEndGroup();
- } else if (c === '\x0A' || c === '\x0D') {
- // cr/lf are noise chars
- } else {
- this.text += c;
- }
- },
-
- parseEscapes: function (c)
- {
- if (c === '\\' || c === '{' || c === '}') {
- this.text += c;
- this.parserState = this.parseText;
- } else {
- this.parserState = this.parseControlSymbol;
- this.parseControlSymbol(c);
- }
- },
- parseControlSymbol: function(c)
- {
- if (c === '~') {
- this.text += '\u00a0'; // nbsp
- this.parserState = this.parseText
- } else if (c === '-') {
- this.text += '\u00ad'; // soft hyphen
- } else if (c === '_') {
- this.text += '\u2011'; // non-breaking hyphen
- } else if (c === '*') {
- this.emitIgnorable();
- this.parserState = this.parseText;
- } else if (c === "'") {
- this.parserState = this.parseHexChar;
- } else if (c === '|') { // formula cacter
- this.emitFormula();
- this.parserState = this.parseText;
- } else if (c === ':') { // subentry in an index entry
- this.emitIndexSubEntry();
- this.parserState = this.parseText;
- } else if (c === '\x0a') {
- this.emitEndParagraph();
- this.parserState = this.parseText;
- } else if (c === '\x0d') {
- this.emitEndParagraph();
- this.parserState = this.parseText;
- } else {
- this.parserState = this.parseControlWord;
- this.parseControlWord(c);
- }
- },
- parseHexChar: function (c)
- {
- if (/^[A-Fa-f0-9]$/.test(c)) {
- this.hexChar += c;
- if (this.hexChar.length >= 2) {
- this.emitHexChar();
- this.parserState = this.parseText;
- }
- return;
- }
- this.emitError("Invalid character \"" + c + "\" in hex literal.");
- this.parserState = this.parseText;
-
- },
- parseControlWord : function(c)
- {
- if (c === ' ') {
- this.emitControlWord();
- this.parserState = this.parseText;
- } else if (/^[-\d]$/.test(c)) {
- this.parserState = this.parseControlWordParam;
- this.controlWordParam += c;
- } else if (/^[A-Za-z]$/.test(c)) {
- this.controlWord += c;
- } else {
- this.emitControlWord();
- this.parserState = this.parseText;
- this.parseText(c);
- }
- },
- parseControlWordParam : function (c) {
- if (/^\d$/.test(c)) {
- this.controlWordParam += c;
- } else if (c === ' ') {
- this.emitControlWord();
- this.parserState = this.parseText;
- } else {
- this.emitControlWord();
- this.parserState = this.parseText;
- this.parseText(c);
- }
- },
-
-
-
-
- emitText : function () {
- if (this.text === '') {
- return;
- }
- this.push({
- type: 'text',
- value: this.text,
- pos: this.cpos,
- row: this.row,
- col: this.col
- });
- this.text = ''
- },
- emitControlWord : function ()
- {
- this.emitText();
- if (this.controlWord === '') {
- this.emitError('empty control word');
- } else {
- this.push({
- type: 'controlword',
- value: this.controlWord,
- param: this.controlWordParam !== '' && Number(this.controlWordParam),
- pos: this.cpos,
- row: this.row,
- col: this.col
- });
- }
- this.controlWord = '';
- this.controlWordParam = '';
- },
- emitStartGroup : function ()
- {
- this.emitText();
- this.push({
- type: 'groupstart',
- pos: this.cpos,
- row: this.row,
- col: this.col
- });
- },
- emitEndGroup : function ()
- {
- this.emitText();
- this.push({
- type: 'groupend',
- pos: this.cpos,
- row: this.row,
- col: this.col
- });
- },
- emitIgnorable : function ()
- {
- this.emitText();
- this.push({
- type: 'ignorable',
- pos: this.cpos,
- row: this.row,
- col: this.col
- });
- },
- emitHexChar : function ()
- {
- this.emitText();
- this.push({
- type: 'hexchar',
- value: this.hexChar,
- pos: this.cpos,
- row: this.row,
- col: this.col
- });
- this.hexChar = ''
- },
- emitError : function (message)
- {
- this.emitText();
- this.push({
- type: 'error',
- value: message,
- row: this.row,
- col: this.col,
- char: this.cpos //,
- //stack: new Error().stack
- });
- },
- emitEndParagraph : function () {
- this.emitText();
- this.push({
- type: 'endparagraph',
- pos: this.cpos,
- row: this.row,
- col: this.col
- });
- }
-
-} ;
-Roo.htmleditor = {};
-
-/**
- * @class Roo.htmleditor.Filter
- * Base Class for filtering htmleditor stuff. - do not use this directly - extend it.
- * @cfg {DomElement} node The node to iterate and filter
- * @cfg {boolean|String|Array} tag Tags to replace
- * @constructor
- * Create a new Filter.
- * @param {Object} config Configuration options
- */
-
-
-
-Roo.htmleditor.Filter = function(cfg) {
- Roo.apply(this.cfg);
- // this does not actually call walk as it's really just a abstract class
-}
-
-
-Roo.htmleditor.Filter.prototype = {
-
- node: false,
-
- tag: false,
-
- // overrride to do replace comments.
- replaceComment : false,
-
- // overrride to do replace or do stuff with tags..
- replaceTag : false,
-
- walk : function(dom)
- {
- Roo.each( Array.from(dom.childNodes), function( e ) {
- switch(true) {
-
- case e.nodeType == 8 && this.replaceComment !== false: // comment
- this.replaceComment(e);
- return;
-
- case e.nodeType != 1: //not a node.
- return;
-
- case this.tag === true: // everything
- case typeof(this.tag) == 'object' && this.tag.indexOf(e.tagName) > -1: // array and it matches.
- case typeof(this.tag) == 'string' && this.tag == e.tagName: // array and it matches.
- if (this.replaceTag && false === this.replaceTag(e)) {
- return;
- }
- if (e.hasChildNodes()) {
- this.walk(e);
- }
- return;
-
- default: // tags .. that do not match.
- if (e.hasChildNodes()) {
- this.walk(e);
- }
- }
-
- }, this);
-
- }
-};
-
-/**
- * @class Roo.htmleditor.FilterAttributes
- * clean attributes and styles including http:// etc.. in attribute
- * @constructor
-* Run a new Attribute Filter
-* @param {Object} config Configuration options
- */
-Roo.htmleditor.FilterAttributes = function(cfg)
-{
- Roo.apply(this, cfg);
- this.attrib_black = this.attrib_black || [];
- this.attrib_white = this.attrib_white || [];
-
- this.attrib_clean = this.attrib_clean || [];
- this.style_white = this.style_white || [];
- this.style_black = this.style_black || [];
- this.walk(cfg.node);
-}
-
-Roo.extend(Roo.htmleditor.FilterAttributes, Roo.htmleditor.Filter,
-{
- tag: true, // all tags
-
- attrib_black : false, // array
- attrib_clean : false,
- attrib_white : false,
-
- style_white : false,
- style_black : false,
-
-
- replaceTag : function(node)
- {
- if (!node.attributes || !node.attributes.length) {
- return true;
- }
-
- for (var i = node.attributes.length-1; i > -1 ; i--) {
- var a = node.attributes[i];
- //console.log(a);
- if (this.attrib_white.length && this.attrib_white.indexOf(a.name.toLowerCase()) < 0) {
- node.removeAttribute(a.name);
- continue;
- }
-
-
-
- if (a.name.toLowerCase().substr(0,2)=='on') {
- node.removeAttribute(a.name);
- continue;
- }
-
-
- if (this.attrib_black.indexOf(a.name.toLowerCase()) > -1) {
- node.removeAttribute(a.name);
- continue;
- }
- if (this.attrib_clean.indexOf(a.name.toLowerCase()) > -1) {
- this.cleanAttr(node,a.name,a.value); // fixme..
- continue;
- }
- if (a.name == 'style') {
- this.cleanStyle(node,a.name,a.value);
- continue;
- }
- /// clean up MS crap..
- // tecnically this should be a list of valid class'es..
-
-
- if (a.name == 'class') {
- if (a.value.match(/^Mso/)) {
- node.removeAttribute('class');
- }
-
- if (a.value.match(/^body$/)) {
- node.removeAttribute('class');
- }
- continue;
- }
-
-
- // style cleanup!?
- // class cleanup?
-
- }
- return true; // clean children
- },
-
- cleanAttr: function(node, n,v)
- {
-
- if (v.match(/^\./) || v.match(/^\//)) {
- return;
- }
- if (v.match(/^(http|https):\/\//)
- || v.match(/^mailto:/)
- || v.match(/^ftp:/)
- || v.match(/^data:/)
- ) {
- return;
- }
- if (v.match(/^#/)) {
- return;
- }
- if (v.match(/^\{/)) { // allow template editing.
- return;
- }
-// Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
- node.removeAttribute(n);
-
- },
- cleanStyle : function(node, n,v)
- {
- if (v.match(/expression/)) { //XSS?? should we even bother..
- node.removeAttribute(n);
- return;
- }
-
- var parts = v.split(/;/);
- var clean = [];
-
- Roo.each(parts, function(p) {
- p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
- if (!p.length) {
- return true;
- }
- var l = p.split(':').shift().replace(/\s+/g,'');
- l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
-
- if ( this.style_black.length && (this.style_black.indexOf(l) > -1 || this.style_black.indexOf(l.toLowerCase()) > -1)) {
- return true;
- }
- //Roo.log()
- // only allow 'c whitelisted system attributes'
- if ( this.style_white.length && style_white.indexOf(l) < 0 && style_white.indexOf(l.toLowerCase()) < 0 ) {
- return true;
- }
-
-
- clean.push(p);
- return true;
- },this);
- if (clean.length) {
- node.setAttribute(n, clean.join(';'));
- } else {
- node.removeAttribute(n);
- }
-
- }
-
-
-
-
-});/**
- * @class Roo.htmleditor.FilterBlack
- * remove blacklisted elements.
- * @constructor
- * Run a new Blacklisted Filter
- * @param {Object} config Configuration options
- */
-
-Roo.htmleditor.FilterBlack = function(cfg)
-{
- Roo.apply(this, cfg);
- this.walk(cfg.node);
-}
-
-Roo.extend(Roo.htmleditor.FilterBlack, Roo.htmleditor.Filter,
-{
- tag : true, // all elements.
-
- replaceTag : function(n)
- {
- n.parentNode.removeChild(n);
- }
-});
-/**
- * @class Roo.htmleditor.FilterComment
- * remove comments.
- * @constructor
-* Run a new Comments Filter
-* @param {Object} config Configuration options
- */
-Roo.htmleditor.FilterComment = function(cfg)
-{
- this.walk(cfg.node);
-}
-
-Roo.extend(Roo.htmleditor.FilterComment, Roo.htmleditor.Filter,
-{
-
- replaceComment : function(n)
- {
- n.parentNode.removeChild(n);
- }
-});/**
- * @class Roo.htmleditor.FilterKeepChildren
- * remove tags but keep children
- * @constructor
- * Run a new Keep Children Filter
- * @param {Object} config Configuration options
- */
-
-Roo.htmleditor.FilterKeepChildren = function(cfg)
-{
- Roo.apply(this, cfg);
- if (this.tag === false) {
- return; // dont walk.. (you can use this to use this just to do a child removal on a single tag )
- }
- this.walk(cfg.node);
-}
-
-Roo.extend(Roo.htmleditor.FilterKeepChildren, Roo.htmleditor.FilterBlack,
-{
-
-
- replaceTag : function(node)
- {
- // walk children...
- //Roo.log(node);
- var ar = Array.from(node.childNodes);
- //remove first..
- for (var i = 0; i < ar.length; i++) {
- if (ar[i].nodeType == 1) {
- if (
- (typeof(this.tag) == 'object' && this.tag.indexOf(ar[i].tagName) > -1)
- || // array and it matches
- (typeof(this.tag) == 'string' && this.tag == ar[i].tagName)
- ) {
- this.replaceTag(ar[i]); // child is blacklisted as well...
- continue;
- }
- }
- }
- ar = Array.from(node.childNodes);
- for (var i = 0; i < ar.length; i++) {
-
- node.removeChild(ar[i]);
- // what if we need to walk these???
- node.parentNode.insertBefore(ar[i], node);
- if (this.tag !== false) {
- this.walk(ar[i]);
-
- }
- }
- node.parentNode.removeChild(node);
- return false; // don't walk children
-
-
- }
-});/**
- * @class Roo.htmleditor.FilterParagraph
- * paragraphs cause a nightmare for shared content - this filter is designed to be called ? at various points when editing
- * like on 'push' to remove the tags and replace them with line breaks.
- * @constructor
- * Run a new Paragraph Filter
- * @param {Object} config Configuration options
- */
-
-Roo.htmleditor.FilterParagraph = function(cfg)
-{
- // no need to apply config.
- this.walk(cfg.node);
-}
-
-Roo.extend(Roo.htmleditor.FilterParagraph, Roo.htmleditor.Filter,
-{
-
-
- tag : 'P',
-
-
- replaceTag : function(node)
- {
-
- if (node.childNodes.length == 1 &&
- node.childNodes[0].nodeType == 3 &&
- node.childNodes[0].textContent.trim().length < 1
- ) {
- // remove and replace with '
';
- node.parentNode.replaceChild(node.ownerDocument.createElement('BR'),node);
- return false; // no need to walk..
- }
- var ar = Array.from(node.childNodes);
- for (var i = 0; i < ar.length; i++) {
- node.removeChild(ar[i]);
- // what if we need to walk these???
- node.parentNode.insertBefore(ar[i], node);
- }
- // now what about this?
- //
- - // double BR. - node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node); - node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node); - node.parentNode.removeChild(node); - - return false; - - } - -});/** - * @class Roo.htmleditor.FilterSpan - * filter span's with no attributes out.. - * @constructor - * Run a new Span Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterSpan = function(cfg) -{ - // no need to apply config. - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterSpan, Roo.htmleditor.FilterKeepChildren, -{ - - tag : 'SPAN', - - - replaceTag : function(node) - { - if (node.attributes && node.attributes.length > 0) { - return true; // walk if there are any. - } - Roo.htmleditor.FilterKeepChildren.prototype.replaceTag.call(this, node); - return false; - - } - -});/** - * @class Roo.htmleditor.FilterTableWidth - try and remove table width data - as that frequently messes up other stuff. - * - * was cleanTableWidths. - * - * Quite often pasting from word etc.. results in tables with column and widths. - * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them.. - * - * @constructor - * Run a new Table Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterTableWidth = function(cfg) -{ - // no need to apply config. - this.tag = ['TABLE', 'TD', 'TR', 'TH', 'THEAD', 'TBODY' ]; - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterTableWidth, Roo.htmleditor.Filter, -{ - - - - replaceTag: function(node) { - - - - if (node.hasAttribute('width')) { - node.removeAttribute('width'); - } - - - if (node.hasAttribute("style")) { - // pretty basic... - - var styles = node.getAttribute("style").split(";"); - var nstyle = []; - Roo.each(styles, function(s) { - if (!s.match(/:/)) { - return; - } - var kv = s.split(":"); - if (kv[0].match(/^\s*(width|min-width)\s*$/)) { - return; - } - // what ever is left... we allow. - nstyle.push(s); - }); - node.setAttribute("style", nstyle.length ? nstyle.join(';') : ''); - if (!nstyle.length) { - node.removeAttribute('style'); - } - } - - return true; // continue doing children.. - } -});/** - * @class Roo.htmleditor.FilterWord - * try and clean up all the mess that Word generates. - * - * This is the 'nice version' - see 'Heavy' that white lists a very short list of elements, and multi-filters - - * @constructor - * Run a new Span Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterWord = function(cfg) -{ - // no need to apply config. - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterWord, Roo.htmleditor.Filter, -{ - tag: true, - - - /** - * Clean up MS wordisms... - */ - replaceTag : function(node) - { - - // no idea what this does - span with text, replaceds with just text. - if( - node.nodeName == 'SPAN' && - !node.hasAttributes() && - node.childNodes.length == 1 && - node.firstChild.nodeName == "#text" - ) { - var textNode = node.firstChild; - node.removeChild(textNode); - if (node.getAttribute('lang') != 'zh-CN') { // do not space pad on chinese characters.. - node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node); - } - node.parentNode.insertBefore(textNode, node); - if (node.getAttribute('lang') != 'zh-CN') { // do not space pad on chinese characters.. - node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node); - } - - node.parentNode.removeChild(node); - return false; // dont do chidren - we have remove our node - so no need to do chdhilren? - } - - - - if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) { - node.parentNode.removeChild(node); - return false; // dont do chidlren - } - //Roo.log(node.tagName); - // remove - but keep children.. - if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) { - //Roo.log('-- removed'); - while (node.childNodes.length) { - var cn = node.childNodes[0]; - node.removeChild(cn); - node.parentNode.insertBefore(cn, node); - // move node to parent - and clean it.. - this.replaceTag(cn); - } - node.parentNode.removeChild(node); - /// no need to iterate chidlren = it's got none.. - //this.iterateChildren(node, this.cleanWord); - return false; // no need to iterate children. - } - // clean styles - if (node.className.length) { - - var cn = node.className.split(/\W+/); - var cna = []; - Roo.each(cn, function(cls) { - if (cls.match(/Mso[a-zA-Z]+/)) { - return; - } - cna.push(cls); - }); - node.className = cna.length ? cna.join(' ') : ''; - if (!cna.length) { - node.removeAttribute("class"); - } - } - - if (node.hasAttribute("lang")) { - node.removeAttribute("lang"); - } - - if (node.hasAttribute("style")) { - - var styles = node.getAttribute("style").split(";"); - var nstyle = []; - Roo.each(styles, function(s) { - if (!s.match(/:/)) { - return; - } - var kv = s.split(":"); - if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) { - return; - } - // what ever is left... we allow. - nstyle.push(s); - }); - node.setAttribute("style", nstyle.length ? nstyle.join(';') : ''); - if (!nstyle.length) { - node.removeAttribute('style'); - } - } - return true; // do children - - - - } -}); -/** - * @class Roo.htmleditor.FilterStyleToTag - * part of the word stuff... - certain 'styles' should be converted to tags. - * eg. - * font-weight: bold -> bold - * ?? super / subscrit etc.. - * - * @constructor -* Run a new style to tag filter. -* @param {Object} config Configuration options - */ -Roo.htmleditor.FilterStyleToTag = function(cfg) -{ - - this.tags = { - B : [ 'fontWeight' , 'bold'], - I : [ 'fontStyle' , 'italic'], - //pre : [ 'font-style' , 'italic'], - // h1.. h6 ?? font-size? - SUP : [ 'verticalAlign' , 'super' ], - SUB : [ 'verticalAlign' , 'sub' ] - - - }; - - Roo.apply(this, cfg); - - - this.walk(cfg.node); - - - -} - - -Roo.extend(Roo.htmleditor.FilterStyleToTag, Roo.htmleditor.Filter, -{ - tag: true, // all tags - - tags : false, - - - replaceTag : function(node) - { - - - if (node.getAttribute("style") === null) { - return true; - } - var inject = []; - for (var k in this.tags) { - if (node.style[this.tags[k][0]] == this.tags[k][1]) { - inject.push(k); - node.style.removeProperty(this.tags[k][0]); - } - } - if (!inject.length) { - return true; - } - var cn = Array.from(node.childNodes); - var nn = node; - Roo.each(inject, function(t) { - var nc = node.ownerDocument.createElement(t); - nn.appendChild(nc); - nn = nc; - }); - for(var i = 0;i < cn.length;cn++) { - node.removeChild(cn[i]); - nn.appendChild(cn[i]); - } - return true /// iterate thru - } - -})/** - * @class Roo.htmleditor.FilterLongBr - * BR/BR/BR - keep a maximum of 2... - * @constructor - * Run a new Long BR Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterLongBr = function(cfg) -{ - // no need to apply config. - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterLongBr, Roo.htmleditor.Filter, -{ - - - tag : 'BR', - - - replaceTag : function(node) - { - - var ps = node.nextSibling; - while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) { - ps = ps.nextSibling; - } - - if (!ps && [ 'TD', 'TH', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(node.parentNode.tagName) > -1) { - node.parentNode.removeChild(node); // remove last BR inside one fo these tags - return false; - } - - if (!ps || ps.nodeType != 1) { - return false; - } - - if (!ps || ps.tagName != 'BR') { - - return false; - } - - - - - - if (!node.previousSibling) { - return false; - } - var ps = node.previousSibling; - - while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) { - ps = ps.previousSibling; - } - if (!ps || ps.nodeType != 1) { - return false; - } - // if header or BR before.. then it's a candidate for removal.. - as we only want '2' of these.. - if (!ps || [ 'BR', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(ps.tagName) < 0) { - return false; - } - - node.parentNode.removeChild(node); // remove me... - - return false; // no need to do children - - } - -}); - -/** - * @class Roo.htmleditor.FilterBlock - * removes id / data-block and contenteditable that are associated with blocks - * usage should be done on a cloned copy of the dom - * @constructor -* Run a new Attribute Filter { node : xxxx }} -* @param {Object} config Configuration options - */ -Roo.htmleditor.FilterBlock = function(cfg) -{ - Roo.apply(this, cfg); - var qa = cfg.node.querySelectorAll; - this.removeAttributes('data-block'); - this.removeAttributes('contenteditable'); - this.removeAttributes('id'); - -} - -Roo.apply(Roo.htmleditor.FilterBlock.prototype, -{ - node: true, // all tags - - - removeAttributes : function(attr) - { - var ar = this.node.querySelectorAll('*[' + attr + ']'); - for (var i =0;i
.
- *
- * @method start
- * @param {String} name Name of the element.
- * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
- * @param {Boolean} empty Optional empty state if the tag should end like
.
- */
- start: function(name, attrs, empty, node)
- {
- var i, l, attr, value;
-
- // there are some situations where adding line break && indentation will not work. will not work.
- // -1;
- var in_pre = this.in_pre || Roo.htmleditor.TidyWriter.whitespace_elements.indexOf(name) > -1;
-
- var is_short = empty ? Roo.htmleditor.TidyWriter.shortend_elements.indexOf(name) > -1 : false;
-
- var add_lb = name == 'BR' ? false : in_inline;
-
- if (!add_lb && !this.in_pre && this.lastElementEndsWS()) {
- i_inline = false;
- }
-
- var indentstr = this.indentstr;
-
- // e_inline = elements that can be inline, but still allow \n before and after?
- // only 'BR' ??? any others?
-
- // ADD LINE BEFORE tage
- if (!this.in_pre) {
- if (in_inline) {
- //code
- if (name == 'BR') {
- this.addLine();
- } else if (this.lastElementEndsWS()) {
- this.addLine();
- } else{
- // otherwise - no new line. (and dont indent.)
- indentstr = '';
- }
-
- } else {
- this.addLine();
- }
- } else {
- indentstr = '';
- }
-
- this.html.push(indentstr + '<', name.toLowerCase());
-
- if (attrs) {
- for (i = 0, l = attrs.length; i < l; i++) {
- attr = attrs[i];
- this.html.push(' ', attr.name, '="', this.encode(attr.value, true), '"');
- }
- }
-
- if (empty) {
- if (is_short) {
- this.html[this.html.length] = '/>';
- } else {
- this.html[this.html.length] = '>' + name.toLowerCase() + '>';
- }
- var e_inline = name == 'BR' ? false : this.in_inline;
-
- if (!e_inline && !this.in_pre) {
- this.addLine();
- }
- return;
-
- }
- // not empty..
- this.html[this.html.length] = '>';
-
- // there is a special situation, where we need to turn on in_inline - if any of the imediate chidlren are one of these.
- /*
- if (!in_inline && !in_pre) {
- var cn = node.firstChild;
- while(cn) {
- if (Roo.htmleditor.TidyWriter.inline_elements.indexOf(cn.nodeName) > -1) {
- in_inline = true
- break;
- }
- cn = cn.nextSibling;
- }
-
- }
- */
-
-
- this.pushState({
- indentstr : in_pre ? '' : (this.indentstr + this.indent),
- in_pre : in_pre,
- in_inline : in_inline
- });
- // add a line after if we are not in a
-
- if (!in_inline && !in_pre) {
- this.addLine();
- }
-
-
-
-
- },
-
- lastElementEndsWS : function()
- {
- var value = this.html.length > 0 ? this.html[this.html.length-1] : false;
- if (value === false) {
- return true;
- }
- return value.match(/\s+$/);
-
- },
-
- /**
- * Writes the a end element such as