mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-09 19:51:42 +09:00
17223554 : xquared upgrade to 0.7
git-svn-id: http://xe-core.googlecode.com/svn/sandbox@4968 201d5d3c-b55e-5fd7-737f-ddc643e51545
This commit is contained in:
parent
5956e254e7
commit
7c3b336e41
59 changed files with 34562 additions and 8454 deletions
|
|
@ -2,13 +2,16 @@ Xquared is copyrighted free software by Alan Kang <jania902@gmail.com>.
|
|||
You can redistribute and/or modify it under the terms of the LGPL.
|
||||
(http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
Following is a list of dependencies:
|
||||
* prototype javascript framework
|
||||
* Homepage: http://prototypejs.org
|
||||
* License: http://dev.rubyonrails.org/browser/spinoffs/prototype/trunk/LICENSE?format=raw
|
||||
While Xquared itself has no dependencies with external libraries, you need following libraries in order to build Xquared from source code:
|
||||
* jsspec
|
||||
* Homepage: http://jania.pe.kr/aw/moin.cgi/JSSpec
|
||||
* License: http://www.gnu.org/licenses/lgpl.html
|
||||
* yui-compressor
|
||||
* Homepage: http://developer.yahoo.com/yui/compressor/
|
||||
* License: http://developer.yahoo.com/yui/license.html
|
||||
* selenium-core
|
||||
* Homepage: http://selenium-core.openqa.org/
|
||||
* License: http://selenium-core.openqa.org/license.jsp
|
||||
* jsdoc_toolkit
|
||||
* Homepage: http://code.google.com/p/jsdoc-toolkit/
|
||||
* License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
|
@ -4,6 +4,6 @@ editor module aim to support major modern web browsers.
|
|||
This software is licensed under the terms you may find in the file
|
||||
named "LICENSE" in this directory.
|
||||
|
||||
For more information, see http://labs.openmaru.com/projects/xquared/
|
||||
For more information, see http://xquared.springbook.playmaru.net/
|
||||
|
||||
Thanks for using Xquared.
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
<!--// 스킨 css 로드 -->
|
||||
<!--%import("css/xq_ui.css")-->
|
||||
<!--%import("css/default.css")-->
|
||||
<!--%import("stylesheets/xq_ui.css")-->
|
||||
<!--%import("stylesheets/default.css")-->
|
||||
<script type="text/javascript">
|
||||
var editor_path = "{$editor_path}";
|
||||
</script>
|
||||
|
||||
<!--// 기본 js/언어파일 로드 -->
|
||||
<!--%import("../../tpl/js/editor_common.js")-->
|
||||
<!--%import("js/xquared-min.js")-->
|
||||
<!--%import("js/xe_interface.js")-->
|
||||
<!--%import("javascripts/module/Full_merged_min.js",optimized=false)-->
|
||||
<!--%import("javascripts/xe_interface.js",optimized=false)-->
|
||||
|
||||
<!-- 자동저장용 폼 -->
|
||||
<!--@if($enable_autosave)-->
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 에디터 출력 -->
|
||||
<div id="xqEditor_{$editor_sequence}"></div>
|
||||
<textarea id="xqEditor_{$editor_sequence}"></textarea>
|
||||
<textarea id="editor_textarea_{$editor_sequence}" class="editor_iframe_textarea" style="display:none; height:{$editor_height}" rows="10" cols="10"></textarea>
|
||||
|
||||
<!-- 에디터 크기 조절 bar -->
|
||||
|
|
|
|||
BIN
modules/editor/skins/xquared/images/content/placeholder.gif
Normal file
BIN
modules/editor/skins/xquared/images/content/placeholder.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 964 B |
BIN
modules/editor/skins/xquared/images/toolbar/iframe.gif
Normal file
BIN
modules/editor/skins/xquared/images/toolbar/iframe.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 B |
BIN
modules/editor/skins/xquared/images/toolbar/movie.gif
Normal file
BIN
modules/editor/skins/xquared/images/toolbar/movie.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 B |
BIN
modules/editor/skins/xquared/images/toolbar/removeLink.gif
Normal file
BIN
modules/editor/skins/xquared/images/toolbar/removeLink.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 B |
110
modules/editor/skins/xquared/javascripts/Browser.js
Normal file
110
modules/editor/skins/xquared/javascripts/Browser.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* @namespace Contains browser detection codes
|
||||
*
|
||||
* @requires Xquared.js
|
||||
*/
|
||||
xq.Browser = new function() {
|
||||
// By Rendering Engines
|
||||
|
||||
/**
|
||||
* True if rendering engine is Trident
|
||||
* @type boolean
|
||||
*/
|
||||
this.isTrident = navigator.appName === "Microsoft Internet Explorer",
|
||||
|
||||
/**
|
||||
* True if rendering engine is Webkit
|
||||
* @type boolean
|
||||
*/
|
||||
this.isWebkit = navigator.userAgent.indexOf('AppleWebKit/') > -1,
|
||||
|
||||
/**
|
||||
* True if rendering engine is Gecko
|
||||
* @type boolean
|
||||
*/
|
||||
this.isGecko = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') === -1,
|
||||
|
||||
/**
|
||||
* True if rendering engine is KHTML
|
||||
* @type boolean
|
||||
*/
|
||||
this.isKHTML = navigator.userAgent.indexOf('KHTML') !== -1,
|
||||
|
||||
/**
|
||||
* True if rendering engine is Presto
|
||||
* @type boolean
|
||||
*/
|
||||
this.isPresto = navigator.appName === "Opera",
|
||||
|
||||
|
||||
|
||||
// By Platforms
|
||||
/**
|
||||
* True if platform is Mac
|
||||
* @type boolean
|
||||
*/
|
||||
this.isMac = navigator.userAgent.indexOf("Macintosh") !== -1,
|
||||
|
||||
/**
|
||||
* True if platform is Ubuntu Linux
|
||||
* @type boolean
|
||||
*/
|
||||
this.isUbuntu = navigator.userAgent.indexOf('Ubuntu') !== -1,
|
||||
|
||||
/**
|
||||
* True if platform is Windows
|
||||
* @type boolean
|
||||
*/
|
||||
this.isWin = navigator.userAgent.indexOf('Windows') !== -1,
|
||||
|
||||
|
||||
|
||||
// By Browsers
|
||||
/**
|
||||
* True if browser is Internet Explorer
|
||||
* @type boolean
|
||||
*/
|
||||
this.isIE = navigator.appName === "Microsoft Internet Explorer",
|
||||
|
||||
/**
|
||||
* True if browser is Internet Explorer 6
|
||||
* @type boolean
|
||||
*/
|
||||
this.isIE6 = navigator.userAgent.indexOf('MSIE 6') !== -1,
|
||||
|
||||
/**
|
||||
* True if browser is Internet Explorer 7
|
||||
* @type boolean
|
||||
*/
|
||||
this.isIE7 = navigator.userAgent.indexOf('MSIE 7') !== -1,
|
||||
|
||||
/**
|
||||
* True if browser is Internet Explorer 8
|
||||
* @type boolean
|
||||
*/
|
||||
this.isIE8 = navigator.userAgent.indexOf('MSIE 8') !== -1,
|
||||
|
||||
/**
|
||||
* True if browser is Firefox
|
||||
* @type boolean
|
||||
*/
|
||||
this.isFF = navigator.userAgent.indexOf('Firefox') !== -1,
|
||||
|
||||
/**
|
||||
* True if browser is Firefox 2
|
||||
* @type boolean
|
||||
*/
|
||||
this.isFF2 = navigator.userAgent.indexOf('Firefox/2') !== -1,
|
||||
|
||||
/**
|
||||
* True if browser is Firefox 3
|
||||
* @type boolean
|
||||
*/
|
||||
this.isFF3 = navigator.userAgent.indexOf('Firefox/3') !== -1,
|
||||
|
||||
/**
|
||||
* True if browser is Safari
|
||||
* @type boolean
|
||||
*/
|
||||
this.isSafari = navigator.userAgent.indexOf('Safari') !== -1
|
||||
};
|
||||
329
modules/editor/skins/xquared/javascripts/DomTree.js
Normal file
329
modules/editor/skins/xquared/javascripts/DomTree.js
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
*/
|
||||
xq.DomTree = xq.Class(/** @lends xq.DomTree.prototype */{
|
||||
/**
|
||||
* Provides various tree operations.
|
||||
*
|
||||
* TODO: Add specs
|
||||
*
|
||||
* @constructs
|
||||
*/
|
||||
initialize: function() {
|
||||
xq.addToFinalizeQueue(this);
|
||||
|
||||
this._blockTags = ["DIV", "DD", "LI", "ADDRESS", "CAPTION", "DT", "H1", "H2", "H3", "H4", "H5", "H6", "HR", "P", "BODY", "BLOCKQUOTE", "PRE", "PARAM", "DL", "OL", "UL", "TABLE", "THEAD", "TBODY", "TR", "TH", "TD"];
|
||||
this._blockContainerTags = ["DIV", "DD", "LI", "BODY", "BLOCKQUOTE", "UL", "OL", "DL", "TABLE", "THEAD", "TBODY", "TR", "TH", "TD"];
|
||||
this._listContainerTags = ["OL", "UL", "DL"];
|
||||
this._tableCellTags = ["TH", "TD"];
|
||||
this._blockOnlyContainerTags = ["BODY", "BLOCKQUOTE", "UL", "OL", "DL", "TABLE", "THEAD", "TBODY", "TR"];
|
||||
this._atomicTags = ["IMG", "OBJECT", "PARAM", "BR", "HR"];
|
||||
},
|
||||
|
||||
getBlockTags: function() {
|
||||
return this._blockTags;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find common ancestor(parent) and his immediate children(left and right).<br />
|
||||
*<br />
|
||||
* A --- B -+- C -+- D -+- E<br />
|
||||
* |<br />
|
||||
* +- F -+- G<br />
|
||||
*<br />
|
||||
* For example:<br />
|
||||
* > findCommonAncestorAndImmediateChildrenOf("E", "G")<br />
|
||||
*<br />
|
||||
* will return<br />
|
||||
*<br />
|
||||
* > {parent:"B", left:"C", right:"F"}
|
||||
*/
|
||||
findCommonAncestorAndImmediateChildrenOf: function(left, right) {
|
||||
if(left.parentNode === right.parentNode) {
|
||||
return {
|
||||
left:left,
|
||||
right:right,
|
||||
parent:left.parentNode
|
||||
};
|
||||
} else {
|
||||
var parentsOfLeft = this.collectParentsOf(left, true);
|
||||
var parentsOfRight = this.collectParentsOf(right, true);
|
||||
var ca = this.getCommonAncestor(parentsOfLeft, parentsOfRight);
|
||||
|
||||
var leftAncestor = parentsOfLeft.find(function(node) {return node.parentNode === ca});
|
||||
var rightAncestor = parentsOfRight.find(function(node) {return node.parentNode === ca});
|
||||
|
||||
return {
|
||||
left:leftAncestor,
|
||||
right:rightAncestor,
|
||||
parent:ca
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find leaves at edge.<br />
|
||||
*<br />
|
||||
* A --- B -+- C -+- D -+- E<br />
|
||||
* |<br />
|
||||
* +- F -+- G<br />
|
||||
*<br />
|
||||
* For example:<br />
|
||||
* > getLeavesAtEdge("A")<br />
|
||||
*<br />
|
||||
* will return<br />
|
||||
*<br />
|
||||
* > ["E", "G"]
|
||||
*/
|
||||
getLeavesAtEdge: function(element) {
|
||||
if(!element.hasChildNodes()) return [null, null];
|
||||
|
||||
var findLeft = function(el) {
|
||||
for (var i = 0; i < el.childNodes.length; i++) {
|
||||
if (el.childNodes[i].nodeType === 1 && this.isBlock(el.childNodes[i])) return findLeft(el.childNodes[i]);
|
||||
}
|
||||
return el;
|
||||
}.bind(this);
|
||||
|
||||
var findRight=function(el) {
|
||||
for (var i = el.childNodes.length; i--;) {
|
||||
if (el.childNodes[i].nodeType === 1 && this.isBlock(el.childNodes[i])) return findRight(el.childNodes[i]);
|
||||
}
|
||||
return el;
|
||||
}.bind(this);
|
||||
|
||||
var left = findLeft(element);
|
||||
var right = findRight(element);
|
||||
|
||||
return [left === element ? null : left, right === element ? null : right];
|
||||
},
|
||||
|
||||
getCommonAncestor: function(parents1, parents2) {
|
||||
for(var i = 0; i < parents1.length; i++) {
|
||||
for(var j = 0; j < parents2.length; j++) {
|
||||
if(parents1[i] === parents2[j]) return parents1[i];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
collectParentsOf: function(node, includeSelf, exitCondition) {
|
||||
var parents = [];
|
||||
if(includeSelf) parents.push(node);
|
||||
|
||||
while((node = node.parentNode) && (node.nodeName !== "HTML") && !(typeof exitCondition === "function" && exitCondition(node))) parents.push(node);
|
||||
return parents;
|
||||
},
|
||||
|
||||
isDescendantOf: function(parent, child) {
|
||||
if(parent.length > 0) {
|
||||
for(var i = 0; i < parent.length; i++) {
|
||||
if(this.isDescendantOf(parent[i], child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(parent === child) return false;
|
||||
|
||||
while (child = child.parentNode)
|
||||
if (child === parent) return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform tree walking (foreward)
|
||||
*/
|
||||
walkForward: function(node) {
|
||||
var target = node.firstChild;
|
||||
if(target) return target;
|
||||
|
||||
// intentional assignment for micro performance turing
|
||||
if(target = node.nextSibling) return target;
|
||||
|
||||
while(node = node.parentNode) {
|
||||
// intentional assignment for micro performance turing
|
||||
if(target = node.nextSibling) return target;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform tree walking (backward)
|
||||
*/
|
||||
walkBackward: function(node) {
|
||||
if(node.previousSibling) {
|
||||
node = node.previousSibling;
|
||||
while(node.hasChildNodes()) {node = node.lastChild;}
|
||||
return node;
|
||||
}
|
||||
|
||||
return node.parentNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform tree walking (to next siblings)
|
||||
*/
|
||||
walkNext: function(node) {return node.nextSibling},
|
||||
|
||||
/**
|
||||
* Perform tree walking (to next siblings)
|
||||
*/
|
||||
walkPrev: function(node) {return node.previousSibling},
|
||||
|
||||
/**
|
||||
* Returns true if target is followed by start
|
||||
*/
|
||||
checkTargetForward: function(start, target) {
|
||||
return this._check(start, this.walkForward, target);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if start is followed by target
|
||||
*/
|
||||
checkTargetBackward: function(start, target) {
|
||||
return this._check(start, this.walkBackward, target);
|
||||
},
|
||||
|
||||
findForward: function(start, condition, exitCondition) {
|
||||
return this._find(start, this.walkForward, condition, exitCondition);
|
||||
},
|
||||
|
||||
findBackward: function(start, condition, exitCondition) {
|
||||
return this._find(start, this.walkBackward, condition, exitCondition);
|
||||
},
|
||||
|
||||
_check: function(start, direction, target) {
|
||||
if(start === target) return false;
|
||||
|
||||
while(start = direction(start)) {
|
||||
if(start === target) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_find: function(start, direction, condition, exitCondition) {
|
||||
while(start = direction(start)) {
|
||||
if(exitCondition && exitCondition(start)) return null;
|
||||
if(condition(start)) return start;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Walks Forward through DOM tree from start to end, and collects all nodes that matches with a filter.
|
||||
* If no filter provided, it just collects all nodes.
|
||||
*
|
||||
* @param {Element} start Starting element.
|
||||
* @param {Element} end Ending element.
|
||||
* @param {Function} filter A filter function.
|
||||
*/
|
||||
collectNodesBetween: function(start, end, filter) {
|
||||
if(start === end) return [start, end].findAll(filter || function() {return true});
|
||||
|
||||
var nodes = this.collectForward(start, function(node) {return node === end}, filter);
|
||||
if(
|
||||
start !== end &&
|
||||
typeof filter === "function" &&
|
||||
filter(end)
|
||||
) nodes.push(end);
|
||||
|
||||
return nodes;
|
||||
},
|
||||
|
||||
collectForward: function(start, exitCondition, filter) {
|
||||
return this.collect(start, this.walkForward, exitCondition, filter);
|
||||
},
|
||||
|
||||
collectBackward: function(start, exitCondition, filter) {
|
||||
return this.collect(start, this.walkBackward, exitCondition, filter);
|
||||
},
|
||||
|
||||
collectNext: function(start, exitCondition, filter) {
|
||||
return this.collect(start, this.walkNext, exitCondition, filter);
|
||||
},
|
||||
|
||||
collectPrev: function(start, exitCondition, filter) {
|
||||
return this.collect(start, this.walkPrev, exitCondition, filter);
|
||||
},
|
||||
|
||||
collect: function(start, next, exitCondition, filter) {
|
||||
var nodes = [start];
|
||||
|
||||
while(true) {
|
||||
start = next(start);
|
||||
if(
|
||||
(start === null) ||
|
||||
(typeof exitCondition === "function" && exitCondition(start))
|
||||
) break;
|
||||
|
||||
nodes.push(start);
|
||||
}
|
||||
|
||||
return (typeof filter === "function") ? nodes.findAll(filter) : nodes;
|
||||
},
|
||||
|
||||
hasBlocks: function(element) {
|
||||
var nodes = element.childNodes;
|
||||
for(var i = 0; i < nodes.length; i++) {
|
||||
if(this.isBlock(nodes[i])) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
hasMixedContents: function(element) {
|
||||
if(!this.isBlock(element)) return false;
|
||||
if(!this.isBlockContainer(element)) return false;
|
||||
|
||||
var hasTextOrInline = false;
|
||||
var hasBlock = false;
|
||||
for(var i = 0; i < element.childNodes.length; i++) {
|
||||
var node = element.childNodes[i];
|
||||
if(!hasTextOrInline && this.isTextOrInlineNode(node)) hasTextOrInline = true;
|
||||
if(!hasBlock && this.isBlock(node)) hasBlock = true;
|
||||
|
||||
if(hasTextOrInline && hasBlock) break;
|
||||
}
|
||||
if(!hasTextOrInline || !hasBlock) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isBlockOnlyContainer: function(element) {
|
||||
if(!element) return false;
|
||||
return this._blockOnlyContainerTags.indexOf(typeof element === 'string' ? element : element.nodeName) !== -1;
|
||||
},
|
||||
|
||||
isTableCell: function(element) {
|
||||
if(!element) return false;
|
||||
return this._tableCellTags.indexOf(typeof element === 'string' ? element : element.nodeName) !== -1;
|
||||
},
|
||||
|
||||
isBlockContainer: function(element) {
|
||||
if(!element) return false;
|
||||
return this._blockContainerTags.indexOf(typeof element === 'string' ? element : element.nodeName) !== -1;
|
||||
},
|
||||
|
||||
isHeading: function(element) {
|
||||
if(!element) return false;
|
||||
return (typeof element === 'string' ? element : element.nodeName).match(/H\d/);
|
||||
},
|
||||
|
||||
isBlock: function(element) {
|
||||
if(!element) return false;
|
||||
return this._blockTags.indexOf(typeof element === 'string' ? element : element.nodeName) !== -1;
|
||||
},
|
||||
|
||||
isAtomic: function(element) {
|
||||
if(!element) return false;
|
||||
return this._atomicTags.indexOf(typeof element === 'string' ? element : element.nodeName) !== -1;
|
||||
},
|
||||
|
||||
isListContainer: function(element) {
|
||||
if(!element) return false;
|
||||
return this._listContainerTags.indexOf(typeof element === 'string' ? element : element.nodeName) !== -1;
|
||||
},
|
||||
|
||||
isTextOrInlineNode: function(node) {
|
||||
return node && (node.nodeType === 3 || !this.isBlock(node));
|
||||
}
|
||||
});
|
||||
151
modules/editor/skins/xquared/javascripts/EditHistory.js
Normal file
151
modules/editor/skins/xquared/javascripts/EditHistory.js
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/Factory.js
|
||||
*/
|
||||
xq.EditHistory = xq.Class(/** @lends xq.EditHistory.prototype */{
|
||||
/**
|
||||
* Manages editing history and performs UNDO/REDO.
|
||||
*
|
||||
* @constructs
|
||||
* @param {xq.rdom.Base} rdom Base instance
|
||||
* @param {Number} [max=100] maximum UNDO buffer size.
|
||||
*/
|
||||
initialize: function(rdom, max) {
|
||||
xq.addToFinalizeQueue(this);
|
||||
if (!rdom) throw "IllegalArgumentException";
|
||||
|
||||
this.disabled = false;
|
||||
this.max = max || 100;
|
||||
this.rdom = rdom;
|
||||
this.index = -1;
|
||||
this.queue = [];
|
||||
|
||||
this.lastModified = Date.get();
|
||||
},
|
||||
getLastModifiedDate: function() {
|
||||
return this.lastModified;
|
||||
},
|
||||
isUndoable: function() {
|
||||
return this.queue.length > 0 && this.index > 0;
|
||||
},
|
||||
isRedoable: function() {
|
||||
return this.queue.length > 0 && this.index < this.queue.length - 1;
|
||||
},
|
||||
disable: function() {
|
||||
this.disabled = true;
|
||||
},
|
||||
enable: function() {
|
||||
this.disabled = false;
|
||||
},
|
||||
undo: function() {
|
||||
this.pushContent();
|
||||
|
||||
if (this.isUndoable()) {
|
||||
this.index--;
|
||||
this.popContent();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
redo: function() {
|
||||
if (this.isRedoable()) {
|
||||
this.index++;
|
||||
this.popContent();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
onCommand: function() {
|
||||
this.lastModified = Date.get();
|
||||
if(this.disabled) return false;
|
||||
|
||||
return this.pushContent();
|
||||
},
|
||||
onEvent: function(event) {
|
||||
this.lastModified = Date.get();
|
||||
if(this.disabled) return false;
|
||||
|
||||
var arrowKeys = [33,34,35,36,37,39];
|
||||
// @WORKAROUND: Mac에서 화살표 up/down 누를 때 pushContent 하면 캐럿이 튄다
|
||||
if(!xq.Browser.isMac) arrowKeys.push(38,40);
|
||||
|
||||
// ignore some event types
|
||||
if(['blur', 'mouseup'].indexOf(event.type) !== -1) return false;
|
||||
|
||||
// ignore normal keys
|
||||
if('keydown' === event.type && !(event.ctrlKey || event.metaKey)) return false;
|
||||
if(['keydown', 'keyup', 'keypress'].indexOf(event.type) !== -1 && !event.ctrlKey && !event.altKey && !event.metaKey && arrowKeys.indexOf(event.keyCode) === -1) return false;
|
||||
if(['keydown', 'keyup', 'keypress'].indexOf(event.type) !== -1 && (event.ctrlKey || event.metaKey) && [89,90].indexOf(event.keyCode) !== -1) return false;
|
||||
|
||||
// ignore ctrl/shift/alt/meta keys
|
||||
if([16,17,18,224].indexOf(event.keyCode) !== -1) return false;
|
||||
|
||||
return this.pushContent();
|
||||
},
|
||||
popContent: function() {
|
||||
this.lastModified = Date.get();
|
||||
var entry = this.queue[this.index];
|
||||
if (entry.caret > 0) {
|
||||
var html=entry.html.substring(0, entry.caret) + '<span id="caret_marker_eh"></span>' + entry.html.substring(entry.caret);
|
||||
this.rdom.getRoot().innerHTML = html;
|
||||
} else {
|
||||
this.rdom.getRoot().innerHTML = entry.html;
|
||||
}
|
||||
this.restoreCaret();
|
||||
},
|
||||
pushContent: function(ignoreCaret) {
|
||||
if(xq.Browser.isTrident && !ignoreCaret && !this.rdom.hasFocus()) return false;
|
||||
if(!this.rdom.getCurrentElement()) return false;
|
||||
|
||||
var html = this.rdom.getRoot().innerHTML;
|
||||
if(html === (this.queue[this.index] ? this.queue[this.index].html : null)) return false;
|
||||
|
||||
var caret = ignoreCaret ? -1 : this.saveCaret();
|
||||
|
||||
if(this.queue.length >= this.max) {
|
||||
this.queue.shift();
|
||||
} else {
|
||||
this.index++;
|
||||
}
|
||||
|
||||
this.queue.splice(this.index, this.queue.length - this.index, {html:html, caret:caret});
|
||||
return true;
|
||||
},
|
||||
clear: function() {
|
||||
this.index = -1;
|
||||
this.queue = [];
|
||||
this.pushContent(true);
|
||||
},
|
||||
saveCaret: function() {
|
||||
if(this.rdom.hasSelection()) return null;
|
||||
|
||||
var bookmark = this.rdom.saveSelection();
|
||||
var marker = this.rdom.pushMarker();
|
||||
|
||||
var str = xq.Browser.isTrident ? '<SPAN class='+marker.className : '<span class="'+marker.className+'"';
|
||||
var caret = this.rdom.getRoot().innerHTML.indexOf(str);
|
||||
|
||||
this.rdom.popMarker();
|
||||
this.rdom.restoreSelection(bookmark);
|
||||
|
||||
return caret;
|
||||
},
|
||||
restoreCaret: function() {
|
||||
var marker = this.rdom.$('caret_marker_eh');
|
||||
|
||||
if(marker) {
|
||||
this.rdom.selectElement(marker, true);
|
||||
this.rdom.collapseSelection(false);
|
||||
this.rdom.deleteNode(marker);
|
||||
} else {
|
||||
var node = this.rdom.tree.findForward(this.rdom.getRoot(), function(node) {
|
||||
return this.isBlock(node) && !this.hasBlocks(node);
|
||||
}.bind(this.rdom.tree));
|
||||
this.rdom.selectElement(node, false);
|
||||
this.rdom.collapseSelection(false);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
2551
modules/editor/skins/xquared/javascripts/Editor.js
Normal file
2551
modules/editor/skins/xquared/javascripts/Editor.js
Normal file
File diff suppressed because it is too large
Load diff
275
modules/editor/skins/xquared/javascripts/Json2.js
Normal file
275
modules/editor/skins/xquared/javascripts/Json2.js
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
json2.js
|
||||
2008-02-14
|
||||
|
||||
Public Domain
|
||||
|
||||
No warranty expressed or implied. Use at your own risk.
|
||||
|
||||
See http://www.JSON.org/js.html
|
||||
|
||||
This file creates a global JSON object containing two methods:
|
||||
|
||||
JSON.stringify(value, whitelist)
|
||||
value any JavaScript value, usually an object or array.
|
||||
|
||||
whitelist an optional array parameter that determines how object
|
||||
values are stringified.
|
||||
|
||||
This method produces a JSON text from a JavaScript value.
|
||||
There are three possible ways to stringify an object, depending
|
||||
on the optional whitelist parameter.
|
||||
|
||||
If an object has a toJSON method, then the toJSON() method will be
|
||||
called. The value returned from the toJSON method will be
|
||||
stringified.
|
||||
|
||||
Otherwise, if the optional whitelist parameter is an array, then
|
||||
the elements of the array will be used to select members of the
|
||||
object for stringification.
|
||||
|
||||
Otherwise, if there is no whitelist parameter, then all of the
|
||||
members of the object will be stringified.
|
||||
|
||||
Values that do not have JSON representaions, such as undefined or
|
||||
functions, will not be serialized. Such values in objects will be
|
||||
dropped; in arrays will be replaced with null.
|
||||
JSON.stringify(undefined) returns undefined. Dates will be
|
||||
stringified as quoted ISO dates.
|
||||
|
||||
Example:
|
||||
|
||||
var text = JSON.stringify(['e', {pluribus: 'unum'}]);
|
||||
// text is '["e",{"pluribus":"unum"}]'
|
||||
|
||||
JSON.parse(text, filter)
|
||||
This method parses a JSON text to produce an object or
|
||||
array. It can throw a SyntaxError exception.
|
||||
|
||||
The optional filter parameter is a function that can filter and
|
||||
transform the results. It receives each of the keys and values, and
|
||||
its return value is used instead of the original value. If it
|
||||
returns what it received, then structure is not modified. If it
|
||||
returns undefined then the member is deleted.
|
||||
|
||||
Example:
|
||||
|
||||
// Parse the text. If a key contains the string 'date' then
|
||||
// convert the value to a date.
|
||||
|
||||
myData = JSON.parse(text, function (key, value) {
|
||||
return key.indexOf('date') >= 0 ? new Date(value) : value;
|
||||
});
|
||||
|
||||
This is a reference implementation. You are free to copy, modify, or
|
||||
redistribute.
|
||||
|
||||
Use your own copy. It is extremely unwise to load third party
|
||||
code into your pages.
|
||||
*/
|
||||
|
||||
/*jslint evil: true */
|
||||
|
||||
/*global JSON */
|
||||
|
||||
/*members "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
|
||||
charCodeAt, floor, getUTCDate, getUTCFullYear, getUTCHours,
|
||||
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, length,
|
||||
parse, propertyIsEnumerable, prototype, push, replace, stringify, test,
|
||||
toJSON, toString
|
||||
*/
|
||||
|
||||
if (!this.JSON) {
|
||||
|
||||
JSON = function () {
|
||||
|
||||
function f(n) { // Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
Date.prototype.toJSON = function () {
|
||||
|
||||
// Eventually, this method will be based on the date.toISOString method.
|
||||
|
||||
return this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z';
|
||||
};
|
||||
|
||||
|
||||
var m = { // table of character substitutions
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'"' : '\\"',
|
||||
'\\': '\\\\'
|
||||
};
|
||||
|
||||
function stringify(value, whitelist) {
|
||||
var a, // The array holding the partial texts.
|
||||
i, // The loop counter.
|
||||
k, // The member key.
|
||||
l, // Length.
|
||||
r = /["\\\x00-\x1f\x7f-\x9f]/g,
|
||||
v; // The member value.
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
|
||||
// If the string contains no control characters, no quote characters, and no
|
||||
// backslash characters, then we can safely slap some quotes around it.
|
||||
// Otherwise we must also replace the offending characters with safe sequences.
|
||||
|
||||
return r.test(value) ?
|
||||
'"' + value.replace(r, function (a) {
|
||||
var c = m[a];
|
||||
if (c) {
|
||||
return c;
|
||||
}
|
||||
c = a.charCodeAt();
|
||||
return '\\u00' + Math.floor(c / 16).toString(16) +
|
||||
(c % 16).toString(16);
|
||||
}) + '"' :
|
||||
'"' + value + '"';
|
||||
|
||||
case 'number':
|
||||
|
||||
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||
|
||||
return isFinite(value) ? String(value) : 'null';
|
||||
|
||||
case 'boolean':
|
||||
case 'null':
|
||||
return String(value);
|
||||
|
||||
case 'object':
|
||||
|
||||
// Due to a specification blunder in ECMAScript,
|
||||
// typeof null is 'object', so watch out for that case.
|
||||
|
||||
if (!value) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
// If the object has a toJSON method, call it, and stringify the result.
|
||||
|
||||
if (typeof value.toJSON === 'function') {
|
||||
return stringify(value.toJSON());
|
||||
}
|
||||
a = [];
|
||||
if (typeof value.length === 'number' &&
|
||||
!(value.propertyIsEnumerable('length'))) {
|
||||
|
||||
// The object is an array. Stringify every element. Use null as a placeholder
|
||||
// for non-JSON values.
|
||||
|
||||
l = value.length;
|
||||
for (i = 0; i < l; i += 1) {
|
||||
a.push(stringify(value[i], whitelist) || 'null');
|
||||
}
|
||||
|
||||
// Join all of the elements together and wrap them in brackets.
|
||||
|
||||
return '[' + a.join(',') + ']';
|
||||
}
|
||||
if (whitelist) {
|
||||
|
||||
// If a whitelist (array of keys) is provided, use it to select the components
|
||||
// of the object.
|
||||
|
||||
l = whitelist.length;
|
||||
for (i = 0; i < l; i += 1) {
|
||||
k = whitelist[i];
|
||||
if (typeof k === 'string') {
|
||||
v = stringify(value[k], whitelist);
|
||||
if (v) {
|
||||
a.push(stringify(k) + ':' + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Otherwise, iterate through all of the keys in the object.
|
||||
|
||||
for (k in value) {
|
||||
if (typeof k === 'string') {
|
||||
v = stringify(value[k], whitelist);
|
||||
if (v) {
|
||||
a.push(stringify(k) + ':' + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Join all of the member texts together and wrap them in braces.
|
||||
|
||||
return '{' + a.join(',') + '}';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
stringify: stringify,
|
||||
parse: function (text, filter) {
|
||||
var j;
|
||||
|
||||
function walk(k, v) {
|
||||
var i, n;
|
||||
if (v && typeof v === 'object') {
|
||||
for (i in v) {
|
||||
if (Object.prototype.hasOwnProperty.apply(v, [i])) {
|
||||
n = walk(i, v[i]);
|
||||
if (n !== undefined) {
|
||||
v[i] = n;
|
||||
} else {
|
||||
delete v[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return filter(k, v);
|
||||
}
|
||||
|
||||
|
||||
// Parsing happens in three stages. In the first stage, we run the text against
|
||||
// regular expressions that look for non-JSON patterns. We are especially
|
||||
// concerned with '()' and 'new' because they can cause invocation, and '='
|
||||
// because it can cause mutation. But just to be safe, we want to reject all
|
||||
// unexpected forms.
|
||||
|
||||
// We split the first stage into 4 regexp operations in order to work around
|
||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
||||
// replace all backslash pairs with '@' (a non-JSON character). Second, we
|
||||
// replace all simple value tokens with ']' characters. Third, we delete all
|
||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
||||
// we look to see that the remaining characters are only whitespace or ']' or
|
||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
||||
|
||||
if (/^[\],:{}\s]*$/.test(text.replace(/\\./g, '@').
|
||||
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
|
||||
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
||||
|
||||
// In the second stage we use the eval function to compile the text into a
|
||||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
||||
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
||||
// in parens to eliminate the ambiguity.
|
||||
|
||||
j = eval('(' + text + ')');
|
||||
|
||||
// In the optional third stage, we recursively walk the new structure, passing
|
||||
// each name/value pair to a filter function for possible transformation.
|
||||
|
||||
return typeof filter === 'function' ? walk('', j) : j;
|
||||
}
|
||||
|
||||
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
||||
|
||||
throw new SyntaxError('parseJSON');
|
||||
}
|
||||
};
|
||||
}();
|
||||
}
|
||||
79
modules/editor/skins/xquared/javascripts/Layer.js
Normal file
79
modules/editor/skins/xquared/javascripts/Layer.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Editor.js
|
||||
*/
|
||||
xq.Layer = xq.Class(/** @lends xq.Layer.prototype */{
|
||||
/**
|
||||
* @constructs
|
||||
*
|
||||
* @param {xq.Editor} editor editor instance
|
||||
* @param {Element} element designMode document's element. Layer instance will be attached to this element
|
||||
* @param {String} html HTML for body.
|
||||
*/
|
||||
initialize: function(editor, element, html) {
|
||||
xq.addToFinalizeQueue(this);
|
||||
|
||||
this.margin = 4;
|
||||
this.editor = editor;
|
||||
this.element = element;
|
||||
this.frame = this.editor._createIFrame(this.editor.getOuterDoc(), this.element.offsetWidth - (this.margin * 2) + "px", this.element.offsetHeight + (this.margin * 2) + "px");
|
||||
this.editor.getOuterDoc().body.appendChild(this.frame);
|
||||
this.doc = editor._createDoc(
|
||||
this.frame,
|
||||
'<style type="text/css">html, body {margin:0px; padding:0px; background-color: transparent; width: 100%; height: 100%; overflow: hidden;}</style>',
|
||||
[], null, null, html
|
||||
);
|
||||
this.frame.style.position = "absolute";
|
||||
this.updatePosition();
|
||||
},
|
||||
|
||||
getFrame: function() {
|
||||
return this.frame;
|
||||
},
|
||||
|
||||
getDoc: function() {
|
||||
return this.doc;
|
||||
},
|
||||
|
||||
getBody: function() {
|
||||
return this.doc.body;
|
||||
},
|
||||
|
||||
isValid: function() {
|
||||
return this.element && this.element.parentNode && this.element.offsetParent;
|
||||
},
|
||||
|
||||
detach: function() {
|
||||
this.frame.parentNode.removeChild(this.frame);
|
||||
|
||||
this.frame = null;
|
||||
this.element = null;
|
||||
},
|
||||
|
||||
updatePosition: function() {
|
||||
// calculate element position
|
||||
var offset = xq.getCumulativeOffset(this.element, this.editor.rdom.getRoot());
|
||||
|
||||
// and scroll position
|
||||
var doc = this.editor.getDoc();
|
||||
var body = this.editor.getBody();
|
||||
offset.left -= doc.documentElement.scrollLeft + body.scrollLeft - this.margin;
|
||||
offset.top -= doc.documentElement.scrollTop + body.scrollTop - this.margin;
|
||||
|
||||
// apply new position
|
||||
this.frame.style.left = offset.left + "px";
|
||||
this.frame.style.top = offset.top + "px";
|
||||
|
||||
// perform autofit
|
||||
var newWidth = this.doc.body.scrollWidth + (this.margin - 1) * 2;
|
||||
var newHeight = this.doc.body.scrollHeight + (this.margin - 1) * 2;
|
||||
|
||||
// without -1, the element increasing slowly.
|
||||
this.element.width = newWidth;
|
||||
this.element.height = newHeight;
|
||||
|
||||
// resize frame
|
||||
this.frame.style.width = this.element.offsetWidth - (this.margin * 2) + "px";
|
||||
this.frame.style.height = this.element.offsetHeight - (this.margin * 2) + "px";
|
||||
}
|
||||
});
|
||||
215
modules/editor/skins/xquared/javascripts/RichTable.js
Normal file
215
modules/editor/skins/xquared/javascripts/RichTable.js
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/Base.js
|
||||
*/
|
||||
xq.RichTable = xq.Class(/** @lends xq.RichTable.prototype */{
|
||||
/**
|
||||
* TODO: Add description
|
||||
*
|
||||
* @constructs
|
||||
*/
|
||||
initialize: function(rdom, table) {
|
||||
xq.addToFinalizeQueue(this);
|
||||
|
||||
this.rdom = rdom;
|
||||
this.table = table;
|
||||
},
|
||||
insertNewRowAt: function(tr, where) {
|
||||
var row = this.rdom.createElement("TR");
|
||||
var cells = tr.cells;
|
||||
for(var i = 0; i < cells.length; i++) {
|
||||
var cell = this.rdom.createElement(cells[i].nodeName);
|
||||
this.rdom.correctEmptyElement(cell);
|
||||
row.appendChild(cell);
|
||||
}
|
||||
return this.rdom.insertNodeAt(row, tr, where);
|
||||
},
|
||||
insertNewCellAt: function(cell, where) {
|
||||
// collect cells;
|
||||
var cells = [];
|
||||
var x = this.getXIndexOf(cell);
|
||||
var y = 0;
|
||||
while(true) {
|
||||
var cur = this.getCellAt(x, y);
|
||||
if(!cur) break;
|
||||
cells.push(cur);
|
||||
y++;
|
||||
}
|
||||
|
||||
// insert new cells
|
||||
for(var i = 0; i < cells.length; i++) {
|
||||
var cell = this.rdom.createElement(cells[i].nodeName);
|
||||
this.rdom.correctEmptyElement(cell);
|
||||
this.rdom.insertNodeAt(cell, cells[i], where);
|
||||
}
|
||||
},
|
||||
deleteRow: function(tr) {
|
||||
return this.rdom.removeBlock(tr);
|
||||
},
|
||||
deleteCell: function(cell) {
|
||||
if(!cell.previousSibling && !cell.nextSibling) {
|
||||
this.rdom.deleteNode(this.table);
|
||||
return;
|
||||
}
|
||||
|
||||
// collect cells;
|
||||
var cells = [];
|
||||
var x = this.getXIndexOf(cell);
|
||||
var y = 0;
|
||||
while(true) {
|
||||
var cur = this.getCellAt(x, y);
|
||||
if(!cur) break;
|
||||
cells.push(cur);
|
||||
y++;
|
||||
}
|
||||
|
||||
for(var i = 0; i < cells.length; i++) {
|
||||
this.rdom.deleteNode(cells[i]);
|
||||
}
|
||||
},
|
||||
getPreviousCellOf: function(cell) {
|
||||
if(cell.previousSibling) return cell.previousSibling;
|
||||
var adjRow = this.getPreviousRowOf(cell.parentNode);
|
||||
if(adjRow) return adjRow.lastChild;
|
||||
return null;
|
||||
},
|
||||
getNextCellOf: function(cell) {
|
||||
if(cell.nextSibling) return cell.nextSibling;
|
||||
var adjRow = this.getNextRowOf(cell.parentNode);
|
||||
if(adjRow) return adjRow.firstChild;
|
||||
return null;
|
||||
},
|
||||
getPreviousRowOf: function(row) {
|
||||
if(row.previousSibling) return row.previousSibling;
|
||||
var rowContainer = row.parentNode;
|
||||
if(rowContainer.previousSibling && rowContainer.previousSibling.lastChild) return rowContainer.previousSibling.lastChild;
|
||||
return null;
|
||||
},
|
||||
getNextRowOf: function(row) {
|
||||
if(row.nextSibling) return row.nextSibling;
|
||||
var rowContainer = row.parentNode;
|
||||
if(rowContainer.nextSibling && rowContainer.nextSibling.firstChild) return rowContainer.nextSibling.firstChild;
|
||||
return null;
|
||||
},
|
||||
getAboveCellOf: function(cell) {
|
||||
var row = this.getPreviousRowOf(cell.parentNode);
|
||||
if(!row) return null;
|
||||
|
||||
var x = this.getXIndexOf(cell);
|
||||
return row.cells[x];
|
||||
},
|
||||
getBelowCellOf: function(cell) {
|
||||
var row = this.getNextRowOf(cell.parentNode);
|
||||
if(!row) return null;
|
||||
|
||||
var x = this.getXIndexOf(cell);
|
||||
return row.cells[x];
|
||||
},
|
||||
getXIndexOf: function(cell) {
|
||||
var row = cell.parentNode;
|
||||
for(var i = 0; i < row.cells.length; i++) {
|
||||
if(row.cells[i] === cell) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
},
|
||||
getYIndexOf: function(cell) {
|
||||
var y = -1;
|
||||
|
||||
// find y
|
||||
var group = row.parentNode;
|
||||
for(var i = 0; i <group.rows.length; i++) {
|
||||
if(group.rows[i] === row) {
|
||||
y = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(this.hasHeadingAtTop() && group.nodeName === "TBODY") y = y + 1;
|
||||
|
||||
return y;
|
||||
},
|
||||
/**
|
||||
* TODO: Not used. Delete or not?
|
||||
*/
|
||||
getLocationOf: function(cell) {
|
||||
var x = this.getXIndexOf(cell);
|
||||
var y = this.getYIndexOf(cell);
|
||||
return {x:x, y:y};
|
||||
},
|
||||
getCellAt: function(col, row) {
|
||||
var row = this.getRowAt(row);
|
||||
return (row && row.cells.length > col) ? row.cells[col] : null;
|
||||
},
|
||||
getRowAt: function(index) {
|
||||
if(this.hasHeadingAtTop()) {
|
||||
return index === 0 ? this.table.tHead.rows[0] : this.table.tBodies[0].rows[index - 1];
|
||||
} else {
|
||||
var rows = this.table.tBodies[0].rows;
|
||||
return (rows.length > index) ? rows[index] : null;
|
||||
}
|
||||
},
|
||||
getDom: function() {
|
||||
return this.table;
|
||||
},
|
||||
hasHeadingAtTop: function() {
|
||||
return !!(this.table.tHead && this.table.tHead.rows[0]);
|
||||
},
|
||||
hasHeadingAtLeft: function() {
|
||||
return this.table.tBodies[0].rows[0].cells[0].nodeName === "TH";
|
||||
},
|
||||
correctEmptyCells: function() {
|
||||
var cells = xq.$A(this.table.getElementsByTagName("TH"));
|
||||
var tds = xq.$A(this.table.getElementsByTagName("TD"));
|
||||
for(var i = 0; i < tds.length; i++) {
|
||||
cells.push(tds[i]);
|
||||
}
|
||||
|
||||
for(var i = 0; i < cells.length; i++) {
|
||||
if(this.rdom.isEmptyBlock(cells[i])) this.rdom.correctEmptyElement(cells[i])
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
xq.RichTable.create = function(rdom, cols, rows, headerPositions) {
|
||||
if(["t", "tl", "lt"].indexOf(headerPositions) !== -1) var headingAtTop = true
|
||||
if(["l", "tl", "lt"].indexOf(headerPositions) !== -1) var headingAtLeft = true
|
||||
|
||||
var sb = []
|
||||
sb.push('<table class="datatable">')
|
||||
|
||||
// thead
|
||||
if(headingAtTop) {
|
||||
sb.push('<thead><tr>')
|
||||
for(var i = 0; i < cols; i++) sb.push('<th></th>')
|
||||
sb.push('</tr></thead>')
|
||||
rows -= 1
|
||||
}
|
||||
|
||||
// tbody
|
||||
sb.push('<tbody>')
|
||||
for(var i = 0; i < rows; i++) {
|
||||
sb.push('<tr>')
|
||||
|
||||
for(var j = 0; j < cols; j++) {
|
||||
if(headingAtLeft && j === 0) {
|
||||
sb.push('<th></th>')
|
||||
} else {
|
||||
sb.push('<td></td>')
|
||||
}
|
||||
}
|
||||
|
||||
sb.push('</tr>')
|
||||
}
|
||||
sb.push('</tbody>')
|
||||
|
||||
sb.push('</table>')
|
||||
|
||||
// create DOM element
|
||||
var container = rdom.createElement("div");
|
||||
container.innerHTML = sb.join("");
|
||||
|
||||
// correct empty cells and return
|
||||
var rtable = new xq.RichTable(rdom, container.firstChild);
|
||||
rtable.correctEmptyCells();
|
||||
return rtable;
|
||||
}
|
||||
139
modules/editor/skins/xquared/javascripts/Shortcut.js
Normal file
139
modules/editor/skins/xquared/javascripts/Shortcut.js
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
*/
|
||||
xq.Shortcut = xq.Class(/** @lends xq.Shortcut.prototype */{
|
||||
/**
|
||||
* Interpretes keyboard event.
|
||||
*
|
||||
* @constructs
|
||||
*/
|
||||
initialize: function(keymapOrExpression) {
|
||||
xq.addToFinalizeQueue(this);
|
||||
this.keymap = keymapOrExpression;
|
||||
},
|
||||
matches: function(e) {
|
||||
if(typeof this.keymap === "string") this.keymap = xq.Shortcut.interprete(this.keymap).keymap;
|
||||
|
||||
// check for key code
|
||||
var which = xq.Browser.isGecko && xq.Browser.isMac ? (e.keyCode + "_" + e.charCode) : e.keyCode;
|
||||
var keyMatches =
|
||||
(this.keymap.which === which) ||
|
||||
(this.keymap.which === 32 && which === 25); // 25 is SPACE in Type-3 keyboard.
|
||||
if(!keyMatches) return false;
|
||||
|
||||
// check for modifier
|
||||
if(typeof e.metaKey === "undefined") e.metaKey = false;
|
||||
|
||||
var modifierMatches =
|
||||
(this.keymap.shiftKey === e.shiftKey || typeof this.keymap.shiftKey === "undefined") &&
|
||||
(this.keymap.altKey === e.altKey || typeof this.keymap.altKey === "undefined") &&
|
||||
(this.keymap.ctrlKey === e.ctrlKey || typeof this.keymap.ctrlKey === "undefined") &&
|
||||
// Webkit turns on meta key flag when alt key is pressed
|
||||
(xq.Browser.isWin && xq.Browser.isWebkit || this.keymap.metaKey === e.metaKey || typeof this.keymap.metaKey === "undefined")
|
||||
|
||||
return modifierMatches;
|
||||
}
|
||||
});
|
||||
|
||||
xq.Shortcut.interprete = function(expression) {
|
||||
expression = expression.toUpperCase();
|
||||
|
||||
var which = xq.Shortcut._interpreteWhich(expression.split("+").pop());
|
||||
var ctrlKey = xq.Shortcut._interpreteModifier(expression, "CTRL");
|
||||
var altKey = xq.Shortcut._interpreteModifier(expression, "ALT");
|
||||
var shiftKey = xq.Shortcut._interpreteModifier(expression, "SHIFT");
|
||||
var metaKey = xq.Shortcut._interpreteModifier(expression, "META");
|
||||
|
||||
var keymap = {};
|
||||
|
||||
keymap.which = which;
|
||||
if(typeof ctrlKey !== "undefined") keymap.ctrlKey = ctrlKey;
|
||||
if(typeof altKey !== "undefined") keymap.altKey = altKey;
|
||||
if(typeof shiftKey !== "undefined") keymap.shiftKey = shiftKey;
|
||||
if(typeof metaKey !== "undefined") keymap.metaKey = metaKey;
|
||||
|
||||
return new xq.Shortcut(keymap);
|
||||
}
|
||||
|
||||
xq.Shortcut._interpreteModifier = function(expression, modifierName) {
|
||||
return expression.match("\\(" + modifierName + "\\)") ?
|
||||
undefined :
|
||||
expression.match(modifierName) ?
|
||||
true : false;
|
||||
}
|
||||
xq.Shortcut._interpreteWhich = function(keyName) {
|
||||
var which = keyName.length === 1 ?
|
||||
((xq.Browser.isMac && xq.Browser.isGecko) ? "0_" + keyName.toLowerCase().charCodeAt(0) : keyName.charCodeAt(0)) :
|
||||
xq.Shortcut._keyNames[keyName];
|
||||
|
||||
if(typeof which === "undefined") throw "Unknown special key name: [" + keyName + "]"
|
||||
|
||||
return which;
|
||||
}
|
||||
xq.Shortcut._keyNames =
|
||||
xq.Browser.isMac && xq.Browser.isGecko ?
|
||||
{
|
||||
BACKSPACE: "8_0",
|
||||
TAB: "9_0",
|
||||
RETURN: "13_0",
|
||||
ENTER: "13_0",
|
||||
ESC: "27_0",
|
||||
SPACE: "0_32",
|
||||
LEFT: "37_0",
|
||||
UP: "38_0",
|
||||
RIGHT: "39_0",
|
||||
DOWN: "40_0",
|
||||
DELETE: "46_0",
|
||||
HOME: "36_0",
|
||||
END: "35_0",
|
||||
PAGEUP: "33_0",
|
||||
PAGEDOWN: "34_0",
|
||||
COMMA: "0_44",
|
||||
HYPHEN: "0_45",
|
||||
EQUAL: "0_61",
|
||||
PERIOD: "0_46",
|
||||
SLASH: "0_47",
|
||||
F1: "112_0",
|
||||
F2: "113_0",
|
||||
F3: "114_0",
|
||||
F4: "115_0",
|
||||
F5: "116_0",
|
||||
F6: "117_0",
|
||||
F7: "118_0",
|
||||
F8: "119_0"
|
||||
}
|
||||
:
|
||||
{
|
||||
BACKSPACE: 8,
|
||||
TAB: 9,
|
||||
RETURN: 13,
|
||||
ENTER: 13,
|
||||
ESC: 27,
|
||||
SPACE: 32,
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
DELETE: 46,
|
||||
HOME: 36,
|
||||
END: 35,
|
||||
PAGEUP: 33,
|
||||
PAGEDOWN: 34,
|
||||
COMMA: 188,
|
||||
HYPHEN: xq.Browser.isTrident ? 189 : 109,
|
||||
EQUAL: xq.Browser.isTrident ? 187 : 61,
|
||||
PERIOD: 190,
|
||||
SLASH: 191,
|
||||
F1:112,
|
||||
F2:113,
|
||||
F3:114,
|
||||
F4:115,
|
||||
F5:116,
|
||||
F6:117,
|
||||
F7:118,
|
||||
F8:119,
|
||||
F9:120,
|
||||
F10:121,
|
||||
F11:122,
|
||||
F12:123
|
||||
}
|
||||
90
modules/editor/skins/xquared/javascripts/Timer.js
Normal file
90
modules/editor/skins/xquared/javascripts/Timer.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
*/
|
||||
xq.Timer = xq.Class(/** @lends xq.Timer.prototype */{
|
||||
/**
|
||||
* @constructs
|
||||
*
|
||||
* @param {Number} precision precision in milliseconds
|
||||
*/
|
||||
initialize: function(precision) {
|
||||
xq.addToFinalizeQueue(this);
|
||||
|
||||
this.precision = precision;
|
||||
this.jobs = {};
|
||||
this.nextJobId = 0;
|
||||
|
||||
this.checker = null;
|
||||
},
|
||||
|
||||
finalize: function() {
|
||||
this.stop();
|
||||
},
|
||||
|
||||
/**
|
||||
* starts timer
|
||||
*/
|
||||
start: function() {
|
||||
this.stop();
|
||||
|
||||
this.checker = window.setInterval(function() {
|
||||
this.executeJobs();
|
||||
}.bind(this), this.precision);
|
||||
},
|
||||
|
||||
/**
|
||||
* stops timer
|
||||
*/
|
||||
stop: function() {
|
||||
if(this.checker) window.clearInterval(this.checker);
|
||||
},
|
||||
|
||||
/**
|
||||
* registers new job
|
||||
*
|
||||
* @param {Function} job function to execute
|
||||
* @param {Number} interval interval in milliseconds
|
||||
*
|
||||
* @return {Number} job id
|
||||
*/
|
||||
register: function(job, interval) {
|
||||
var jobId = this.nextJobId++;
|
||||
|
||||
this.jobs[jobId] = {
|
||||
func:job,
|
||||
interval: interval,
|
||||
lastExecution: Date.get()
|
||||
};
|
||||
|
||||
return jobId;
|
||||
},
|
||||
|
||||
/**
|
||||
* unregister job by job id
|
||||
*
|
||||
* @param {Number} job id
|
||||
*/
|
||||
unregister: function(jobId) {
|
||||
delete this.jobs[jobId];
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute all expired jobs immedialty. This method will be called automatically by interval timer.
|
||||
*/
|
||||
executeJobs: function() {
|
||||
var curDate = new Date();
|
||||
|
||||
for(var id in this.jobs) {
|
||||
var job = this.jobs[id];
|
||||
if(job.lastExecution.elapsed(job.interval, curDate)) {
|
||||
try {
|
||||
job.lastReturn = job.func();
|
||||
} catch(e) {
|
||||
job.lastException = e;
|
||||
} finally {
|
||||
job.lastExecution = curDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
708
modules/editor/skins/xquared/javascripts/XQuared.js
Normal file
708
modules/editor/skins/xquared/javascripts/XQuared.js
Normal file
|
|
@ -0,0 +1,708 @@
|
|||
/*! Xquared is copyrighted free software by Alan Kang <jania902@gmail.com>.
|
||||
* For more information, see http://xquared.springbook.playmaru.net/
|
||||
*/
|
||||
if(!window.xq) {
|
||||
/**
|
||||
* @namespace Contains all variables.
|
||||
*/
|
||||
var xq = {};
|
||||
}
|
||||
|
||||
xq.majorVersion = '0.7';
|
||||
xq.minorVersion = '20080402';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Compiles regular expression pattern if possible.
|
||||
*
|
||||
* @param {String} p Regular expression.
|
||||
* @param {String} f Flags.
|
||||
*/
|
||||
xq.compilePattern = function(p, f) {
|
||||
if(!RegExp.prototype.compile) return new RegExp(p, f);
|
||||
|
||||
var r = new RegExp();
|
||||
r.compile(p, f);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @class Simple class based OOP framework
|
||||
*/
|
||||
xq.Class = function() {
|
||||
var parent = null, properties = xq.$A(arguments), key;
|
||||
if (typeof properties[0] === "function") {
|
||||
parent = properties.shift();
|
||||
}
|
||||
|
||||
function klass() {
|
||||
this.initialize.apply(this, arguments);
|
||||
}
|
||||
|
||||
if(parent) {
|
||||
for (key in parent.prototype) {
|
||||
klass.prototype[key] = parent.prototype[key];
|
||||
}
|
||||
}
|
||||
|
||||
for (key in properties[0]) if(properties[0].hasOwnProperty(key)){
|
||||
klass.prototype[key] = properties[0][key];
|
||||
}
|
||||
|
||||
if (!klass.prototype.initialize) {
|
||||
klass.prototype.initialize = function() {};
|
||||
}
|
||||
|
||||
klass.prototype.constructor = klass;
|
||||
|
||||
return klass;
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers event handler
|
||||
*
|
||||
* @param {Element} element Target element.
|
||||
* @param {String} eventName Name of event. For example "keydown".
|
||||
* @param {Function} handler Event handler.
|
||||
*/
|
||||
xq.observe = function(element, eventName, handler) {
|
||||
if (element.addEventListener) {
|
||||
element.addEventListener(eventName, handler, false);
|
||||
} else {
|
||||
element.attachEvent('on' + eventName, handler);
|
||||
}
|
||||
element = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregisters event handler
|
||||
*/
|
||||
xq.stopObserving = function(element, eventName, handler) {
|
||||
if (element.removeEventListener) {
|
||||
element.removeEventListener(eventName, handler, false);
|
||||
} else {
|
||||
element.detachEvent("on" + eventName, handler);
|
||||
}
|
||||
element = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Predefined event handler which simply cancels given event
|
||||
*
|
||||
* @param {Event} e Event to cancel.
|
||||
*/
|
||||
xq.cancelHandler = function(e) {xq.stopEvent(e); return false;};
|
||||
|
||||
/**
|
||||
* Stops event propagation.
|
||||
*
|
||||
* @param {Event} e Event to stop.
|
||||
*/
|
||||
xq.stopEvent = function(e) {
|
||||
if(e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if(e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
e.returnValue = false;
|
||||
e.cancelBubble = true;
|
||||
e.stopped = true;
|
||||
};
|
||||
|
||||
xq.isButton = function(event, code) {
|
||||
return event.which ? (event.which === code + 1) : (event.button === code);
|
||||
};
|
||||
xq.isLeftClick = function(event) {return xq.isButton(event, 0);};
|
||||
xq.isMiddleClick = function(event) {return xq.isButton(event, 1);};
|
||||
xq.isRightClick = function(event) {return xq.isButton(event, 2);};
|
||||
|
||||
xq.getEventPoint = function(event) {
|
||||
return {
|
||||
x: event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)),
|
||||
y: event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop))
|
||||
};
|
||||
};
|
||||
|
||||
xq.getCumulativeOffset = function(element, until) {
|
||||
var top = 0, left = 0;
|
||||
|
||||
do {
|
||||
top += element.offsetTop || 0;
|
||||
left += element.offsetLeft || 0;
|
||||
element = element.offsetParent;
|
||||
} while (element && element != until);
|
||||
|
||||
return {top:top, left:left};
|
||||
};
|
||||
|
||||
xq.$ = function(id) {
|
||||
return document.getElementById(id);
|
||||
};
|
||||
|
||||
xq.isEmptyHash = function(h) {
|
||||
for(var key in h) if(h.hasOwnProperty(key)){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
xq.emptyFunction = function() {};
|
||||
|
||||
xq.$A = function(arraylike) {
|
||||
var len = arraylike.length, a = [];
|
||||
while (len--) {
|
||||
a[len] = arraylike[len];
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
xq.addClassName = function(element, className) {
|
||||
if (!xq.hasClassName(element, className)) {
|
||||
element.className += (element.className ? ' ' : '') + className;
|
||||
}
|
||||
return element;
|
||||
};
|
||||
xq.removeClassName = function(element, className) {
|
||||
if (xq.hasClassName(element, className)) {
|
||||
element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
|
||||
}
|
||||
return element;
|
||||
};
|
||||
xq.hasClassName = function(element, className) {
|
||||
var classNames = element.className;
|
||||
return (classNames.length > 0 && (classNames === className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(classNames)));
|
||||
};
|
||||
|
||||
xq.serializeForm = function(f) {
|
||||
var options = {hash: true};
|
||||
var data = {};
|
||||
var elements = f.getElementsByTagName("*");
|
||||
for(var i = 0; i < elements.length; i++) {
|
||||
var element = elements[i];
|
||||
var tagName = element.tagName.toLowerCase();
|
||||
if(element.disabled || !element.name || ['input', 'textarea', 'option', 'select'].indexOf(tagName) === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = element.name;
|
||||
var value = xq.getValueOfElement(element);
|
||||
|
||||
if(value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(key in data) {
|
||||
if(data[key].constructor === Array) {
|
||||
data[key] = [data[key]];
|
||||
}
|
||||
data[key].push(value);
|
||||
} else {
|
||||
data[key] = value;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
xq.getValueOfElement = function(e) {
|
||||
var type = e.type.toLowerCase();
|
||||
if(type === 'checkbox' || type === 'radio') {
|
||||
return e.checked ? e.value : undefined;
|
||||
} else {
|
||||
return e.value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find elements by class name (and tag name)
|
||||
*
|
||||
* @param {Element} element Root element
|
||||
* @param {String} className Target class name
|
||||
* @param {String} tagName Optional tag name
|
||||
*/
|
||||
xq.getElementsByClassName = function(element, className, tagName) {
|
||||
if(!tagName && element.getElementsByClassName) {
|
||||
return element.getElementsByClassName(className);
|
||||
}
|
||||
|
||||
var elements = element.getElementsByTagName(tagName || "*");
|
||||
var len = elements.length;
|
||||
var result = [];
|
||||
var p = xq.compilePattern("(^|\\s)" + className + "($|\\s)", "i");
|
||||
for(var i = 0; i < len; i++) {
|
||||
var cur = elements[i];
|
||||
if(p.test(cur.className)) {
|
||||
result.push(cur);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
if(!window.Prototype) {
|
||||
if(!Function.prototype.bind) {
|
||||
Function.prototype.bind = function() {
|
||||
var m = this, arg = xq.$A(arguments), o = arg.shift();
|
||||
return function() {
|
||||
return m.apply(o, arg.concat(xq.$A(arguments)));
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
if(!Function.prototype.bindAsEventListener) {
|
||||
Function.prototype.bindAsEventListener = function() {
|
||||
var m = this, arg = xq.$A(arguments), o = arg.shift();
|
||||
return function(event) {
|
||||
return m.apply(o, [event || window.event].concat(arg));
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Array.prototype.find = function(f) {
|
||||
for(var i = 0; i < this.length; i++) {
|
||||
if(f(this[i])) {
|
||||
return this[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Array.prototype.findAll = function(f) {
|
||||
var result = [];
|
||||
for(var i = 0; i < this.length; i++) {
|
||||
if(f(this[i])) {
|
||||
result.push(this[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
Array.prototype.first = function() {return this[0];};
|
||||
|
||||
Array.prototype.last = function() {return this[this.length - 1];};
|
||||
|
||||
Array.prototype.flatten = function() {
|
||||
var result = [];
|
||||
var recursive = function(array) {
|
||||
for(var i = 0; i < array.length; i++) {
|
||||
if(array[i].constructor === Array) {
|
||||
recursive(array[i]);
|
||||
} else {
|
||||
result.push(array[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
recursive(this);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
xq.pStripTags = xq.compilePattern("</?[^>]+>", "gi");
|
||||
String.prototype.stripTags = function() {
|
||||
return this.replace(xq.pStripTags, '');
|
||||
};
|
||||
String.prototype.escapeHTML = function() {
|
||||
xq.textNode.data = this;
|
||||
return xq.divNode.innerHTML;
|
||||
};
|
||||
xq.textNode = document.createTextNode('');
|
||||
xq.divNode = document.createElement('div');
|
||||
xq.divNode.appendChild(xq.textNode);
|
||||
|
||||
xq.pStrip1 = xq.compilePattern("^\\s+");
|
||||
xq.pStrip2 = xq.compilePattern("\\s+$");
|
||||
String.prototype.strip = function() {
|
||||
return this.replace(xq.pStrip1, '').replace(xq.pStrip2, '');
|
||||
};
|
||||
|
||||
Array.prototype.indexOf = function(n) {
|
||||
for(var i = 0; i < this.length; i++) {
|
||||
if(this[i] === n) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
|
||||
Array.prototype.includeElement = function(o) {
|
||||
if (this.indexOf(o) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var found = false;
|
||||
for(var i = 0; i < this.length; i++) {
|
||||
if(this[i] === o) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make given object as event source
|
||||
*
|
||||
* @param {Object} object target object
|
||||
* @param {String} prefix prefix for generated functions
|
||||
* @param {Array} events array of string which contains name of events
|
||||
*/
|
||||
xq.asEventSource = function(object, prefix, events) {
|
||||
object.autoRegisteredEventListeners = [];
|
||||
object.registerEventFirer = function(prefix, name) {
|
||||
this["_fireOn" + name] = function() {
|
||||
for(var i = 0; i < this.autoRegisteredEventListeners.length; i++) {
|
||||
var listener = this.autoRegisteredEventListeners[i];
|
||||
var func = listener["on" + prefix + name];
|
||||
if(func) {
|
||||
func.apply(listener, xq.$A(arguments));
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
object.addListener = function(l) {
|
||||
this.autoRegisteredEventListeners.push(l);
|
||||
};
|
||||
|
||||
for(var i = 0; i < events.length; i++) {
|
||||
object.registerEventFirer(prefix, events[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* JSON to Element mapper
|
||||
*/
|
||||
xq.json2element = function(json, doc) {
|
||||
var div = doc.createElement("DIV");
|
||||
div.innerHTML = xq.json2html(json);
|
||||
return div.firstChild || {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Element to JSON mapper
|
||||
*/
|
||||
xq.element2json = function(element) {
|
||||
var o, i, childElements;
|
||||
|
||||
if(element.nodeName === 'DL') {
|
||||
o = {};
|
||||
childElements = xq.findChildElements(element);
|
||||
for(i = 0; i < childElements.length; i++) {
|
||||
var dt = childElements[i];
|
||||
var dd = childElements[++i];
|
||||
o[dt.innerHTML] = xq.element2json(xq.findChildElements(dd)[0]);
|
||||
}
|
||||
return o;
|
||||
} else if (element.nodeName === 'OL') {
|
||||
o = [];
|
||||
childElements = xq.findChildElements(element);
|
||||
for(i = 0; i < childElements.length; i++) {
|
||||
var li = childElements[i];
|
||||
o[i] = xq.element2json(xq.findChildElements(li)[0]);
|
||||
}
|
||||
} else if(element.nodeName === 'SPAN' && element.className === 'number') {
|
||||
return parseFloat(element.innerHTML);
|
||||
} else if(element.nodeName === 'SPAN' && element.className === 'string') {
|
||||
return element.innerHTML;
|
||||
} else { // ignore textnode or unknown tag
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON to HTML string mapper
|
||||
*/
|
||||
xq.json2html = function(json) {
|
||||
var sb = [];
|
||||
xq._json2html(json, sb);
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
xq._json2html = function(o, sb) {
|
||||
if(typeof o === 'number') {
|
||||
sb.push('<span class="number">' + o + '</span>');
|
||||
} else if(typeof o === 'string') {
|
||||
sb.push('<span class="string">' + o.escapeHTML() + '</span>');
|
||||
} else if(o.constructor === Array) {
|
||||
sb.push('<ol>');
|
||||
for(var i = 0; i < o.length; i++) {
|
||||
sb.push('<li>');
|
||||
xq._json2html(o[i], sb);
|
||||
sb.push('</li>');
|
||||
}
|
||||
sb.push('</ol>');
|
||||
} else { // Object
|
||||
sb.push('<dl>');
|
||||
for (var key in o) if (o.hasOwnProperty(key)) {
|
||||
sb.push('<dt>' + key + '</dt>');
|
||||
sb.push('<dd>');
|
||||
xq._json2html(o[key], sb);
|
||||
sb.push('</dd>');
|
||||
}
|
||||
sb.push('</dl>');
|
||||
}
|
||||
};
|
||||
|
||||
xq.findChildElements = function(parent) {
|
||||
var childNodes = parent.childNodes;
|
||||
var elements = [];
|
||||
for(var i = 0; i < childNodes.length; i++) {
|
||||
if(childNodes[i].nodeType === 1) {
|
||||
elements.push(childNodes[i]);
|
||||
}
|
||||
}
|
||||
return elements;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Date.preset = null;
|
||||
Date.pass = function(msec) {
|
||||
if(Date.preset !== null) {
|
||||
Date.preset = new Date(Date.preset.getTime() + msec);
|
||||
}
|
||||
};
|
||||
Date.get = function() {
|
||||
return Date.preset === null ? new Date() : Date.preset;
|
||||
};
|
||||
Date.prototype.elapsed = function(msec, curDate) {
|
||||
return (curDate || Date.get()).getTime() - this.getTime() >= msec;
|
||||
};
|
||||
|
||||
String.prototype.merge = function(data) {
|
||||
var newString = this;
|
||||
for(var k in data) if(data.hasOwnProperty(k)) {
|
||||
newString = newString.replace("{" + k + "}", data[k]);
|
||||
}
|
||||
return newString;
|
||||
};
|
||||
xq.pBlank = xq.compilePattern("^\\s*$");
|
||||
String.prototype.isBlank = function() {
|
||||
return xq.pBlank.test(this);
|
||||
};
|
||||
xq.pURL = xq.compilePattern("((((\\w+)://(((([^@:]+)(:([^@]+))?)@)?([^:/\\?#]+)?(:(\\d+))?))?([^\\?#]+)?)(\\?([^#]+))?)(#(.+))?");
|
||||
String.prototype.parseURL = function() {
|
||||
var m = this.match(xq.pURL);
|
||||
|
||||
var includeAnchor = m[0];
|
||||
var includeQuery = m[1] || undefined;
|
||||
var includePath = m[2] || undefined;
|
||||
var includeHost = m[3] || undefined;
|
||||
var includeBase = null;
|
||||
var protocol = m[4] || undefined;
|
||||
var user = m[8] || undefined;
|
||||
var password = m[10] || undefined;
|
||||
var domain = m[11] || undefined;
|
||||
var port = m[13] || undefined;
|
||||
var path = m[14] || undefined;
|
||||
var query = m[16] || undefined;
|
||||
var anchor = m[18] || undefined;
|
||||
|
||||
if(!path || path === '/') {
|
||||
includeBase = includeHost + '/';
|
||||
} else {
|
||||
var index = path.lastIndexOf('/');
|
||||
includeBase = includeHost + path.substring(0, index + 1);
|
||||
}
|
||||
|
||||
return {
|
||||
includeAnchor: includeAnchor,
|
||||
includeQuery: includeQuery,
|
||||
includePath: includePath,
|
||||
includeBase: includeBase,
|
||||
includeHost: includeHost,
|
||||
protocol: protocol,
|
||||
user: user,
|
||||
password: password,
|
||||
domain: domain,
|
||||
port: port,
|
||||
path: path,
|
||||
query: query,
|
||||
anchor: anchor
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
xq.commonAttrs = ['title', 'class', 'id', 'style'];;
|
||||
|
||||
/**
|
||||
* Pre-defined whitelist
|
||||
*/
|
||||
xq.predefinedWhitelist = {
|
||||
'a': xq.commonAttrs.concat('href', 'charset', 'rev', 'rel', 'type', 'hreflang', 'tabindex'),
|
||||
'abbr': xq.commonAttrs.concat(),
|
||||
'acronym': xq.commonAttrs.concat(),
|
||||
'address': xq.commonAttrs.concat(),
|
||||
'blockquote': xq.commonAttrs.concat('cite'),
|
||||
'br': xq.commonAttrs.concat(),
|
||||
'button': xq.commonAttrs.concat('disabled', 'type', 'name', 'value'),
|
||||
'caption': xq.commonAttrs.concat(),
|
||||
'cite': xq.commonAttrs.concat(),
|
||||
'code': xq.commonAttrs.concat(),
|
||||
'dd': xq.commonAttrs.concat(),
|
||||
'dfn': xq.commonAttrs.concat(),
|
||||
'div': xq.commonAttrs.concat(),
|
||||
'dl': xq.commonAttrs.concat(),
|
||||
'dt': xq.commonAttrs.concat(),
|
||||
'em': xq.commonAttrs.concat(),
|
||||
'embed': xq.commonAttrs.concat('src', 'width', 'height', 'allowscriptaccess', 'type', 'allowfullscreen', 'bgcolor'),
|
||||
'h1': xq.commonAttrs.concat(),
|
||||
'h2': xq.commonAttrs.concat(),
|
||||
'h3': xq.commonAttrs.concat(),
|
||||
'h4': xq.commonAttrs.concat(),
|
||||
'h5': xq.commonAttrs.concat(),
|
||||
'h6': xq.commonAttrs.concat(),
|
||||
'hr': xq.commonAttrs.concat(),
|
||||
'iframe': xq.commonAttrs.concat('name', 'src', 'frameborder', 'scrolling', 'width', 'height', 'longdesc'),
|
||||
'input': xq.commonAttrs.concat('type', 'name', 'value', 'size', 'checked', 'readonly', 'src', 'maxlength'),
|
||||
'img': xq.commonAttrs.concat('alt', 'width', 'height', 'src', 'longdesc'),
|
||||
'label': xq.commonAttrs.concat('for'),
|
||||
'kbd': xq.commonAttrs.concat(),
|
||||
'li': xq.commonAttrs.concat(),
|
||||
'object': xq.commonAttrs.concat('align', 'classid', 'codetype', 'archive', 'width', 'type', 'codebase', 'height', 'data', 'name', 'standby', 'declare'),
|
||||
'ol': xq.commonAttrs.concat(),
|
||||
'option': xq.commonAttrs.concat('disabled', 'selected', 'laabel', 'value'),
|
||||
'p': xq.commonAttrs.concat(),
|
||||
'param': xq.commonAttrs.concat('name', 'value', 'valuetype', 'type'),
|
||||
'pre': xq.commonAttrs.concat(),
|
||||
'q': xq.commonAttrs.concat('cite'),
|
||||
'samp': xq.commonAttrs.concat(),
|
||||
'script': xq.commonAttrs.concat('src', 'type'),
|
||||
'select': xq.commonAttrs.concat('disabled', 'size', 'multiple', 'name'),
|
||||
'span': xq.commonAttrs.concat(),
|
||||
'sup': xq.commonAttrs.concat(),
|
||||
'sub': xq.commonAttrs.concat(),
|
||||
'strong': xq.commonAttrs.concat(),
|
||||
'table': xq.commonAttrs.concat('summary', 'width'),
|
||||
'thead': xq.commonAttrs.concat(),
|
||||
'textarea': xq.commonAttrs.concat('cols', 'disabled', 'rows', 'readonly', 'name'),
|
||||
'tbody': xq.commonAttrs.concat(),
|
||||
'th': xq.commonAttrs.concat('colspan', 'rowspan'),
|
||||
'td': xq.commonAttrs.concat('colspan', 'rowspan'),
|
||||
'tr': xq.commonAttrs.concat(),
|
||||
'tt': xq.commonAttrs.concat(),
|
||||
'ul': xq.commonAttrs.concat(),
|
||||
'var': xq.commonAttrs.concat()
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Automatic finalization queue
|
||||
*/
|
||||
xq.autoFinalizeQueue = [];
|
||||
|
||||
/**
|
||||
* Automatic finalizer
|
||||
*/
|
||||
xq.addToFinalizeQueue = function(obj) {
|
||||
xq.autoFinalizeQueue.push(obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finalizes given object
|
||||
*/
|
||||
xq.finalize = function(obj) {
|
||||
if(typeof obj.finalize === "function") {
|
||||
try {obj.finalize();} catch(ignored) {}
|
||||
}
|
||||
|
||||
for(var key in obj) if(obj.hasOwnProperty(key)) {
|
||||
obj[key] = null;
|
||||
}
|
||||
};
|
||||
|
||||
xq.observe(window, "unload", function() {
|
||||
// "xq" and "xq.autoFinalizeQueue" could be removed by another libraries' clean-up mechanism.
|
||||
if(xq && xq.autoFinalizeQueue) {
|
||||
for(var i = 0; i < xq.autoFinalizeQueue.length; i++) {
|
||||
xq.finalize(xq.autoFinalizeQueue[i]);
|
||||
}
|
||||
xq = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Finds Xquared's <script> element
|
||||
*/
|
||||
xq.findXquaredScript = function() {
|
||||
return xq.$A(document.getElementsByTagName("script")).find(function(script) {
|
||||
return script.src && script.src.match(/xquared\.js/i);
|
||||
});
|
||||
};
|
||||
xq.shouldLoadOthers = function() {
|
||||
var script = xq.findXquaredScript();
|
||||
return script && !!script.src.match(/xquared\.js\?load_others=1/i);
|
||||
};
|
||||
/**
|
||||
* Loads javascript from given URL
|
||||
*/
|
||||
xq.loadScript = function(url) {
|
||||
document.write('<script type="text/javascript" src="' + url + '"></script>');
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all Xquared script file names
|
||||
*/
|
||||
xq.getXquaredScriptFileNames = function() {
|
||||
return [
|
||||
'Xquared.js',
|
||||
'Browser.js',
|
||||
'DomTree.js',
|
||||
'rdom/Base.js',
|
||||
'rdom/W3.js',
|
||||
'rdom/Gecko.js',
|
||||
'rdom/Webkit.js',
|
||||
'rdom/Trident.js',
|
||||
'rdom/Factory.js',
|
||||
'validator/Base.js',
|
||||
'validator/W3.js',
|
||||
'validator/Gecko.js',
|
||||
'validator/Webkit.js',
|
||||
'validator/Trident.js',
|
||||
'validator/Factory.js',
|
||||
'macro/Base.js',
|
||||
'macro/Factory.js',
|
||||
'macro/FlashMovieMacro.js',
|
||||
'macro/IFrameMacro.js',
|
||||
'macro/JavascriptMacro.js',
|
||||
'EditHistory.js',
|
||||
'plugin/Base.js',
|
||||
'RichTable.js',
|
||||
'Timer.js',
|
||||
'Layer.js',
|
||||
'ui/Base.js',
|
||||
'ui/Control.js',
|
||||
'ui/Toolbar.js',
|
||||
'ui/_templates.js',
|
||||
'Json2.js',
|
||||
'Shortcut.js',
|
||||
'Editor.js'
|
||||
];
|
||||
};
|
||||
xq.getXquaredScriptBasePath = function() {
|
||||
var script = xq.findXquaredScript();
|
||||
return script.src.match(/(.*\/)xquared\.js.*/i)[1];
|
||||
};
|
||||
|
||||
xq.loadOthers = function() {
|
||||
var basePath = xq.getXquaredScriptBasePath();
|
||||
var others = xq.getXquaredScriptFileNames();
|
||||
|
||||
// Xquared.js(this file) should not be loaded again. So the value of "i" starts with 1 instead of 0
|
||||
for(var i = 1; i < others.length; i++) {
|
||||
xq.loadScript(basePath + others[i]);
|
||||
}
|
||||
};
|
||||
|
||||
if(xq.shouldLoadOthers()) {
|
||||
xq.loadOthers();
|
||||
}
|
||||
55
modules/editor/skins/xquared/javascripts/macro/Base.js
Normal file
55
modules/editor/skins/xquared/javascripts/macro/Base.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @namespace
|
||||
*/
|
||||
xq.macro = {};
|
||||
|
||||
/**
|
||||
* @requires Xquared.js
|
||||
*/
|
||||
xq.macro.Base = xq.Class(/** @lends xq.macro.Base.prototype */{
|
||||
/**
|
||||
* @constructs
|
||||
*
|
||||
* @param {Object} Parameters or HTML fragment.
|
||||
* @param {String} URL to place holder image.
|
||||
*/
|
||||
initialize: function(id, paramsOrHtml, placeHolderImgSrc) {
|
||||
this.id = id;
|
||||
this.placeHolderImgSrc = placeHolderImgSrc;
|
||||
|
||||
if(typeof paramsOrHtml === "string") {
|
||||
this.html = paramsOrHtml;
|
||||
this.params = {};
|
||||
|
||||
this.initFromHtml();
|
||||
} else {
|
||||
this.html = null;
|
||||
this.params = paramsOrHtml;
|
||||
|
||||
this.initFromParams();
|
||||
}
|
||||
},
|
||||
|
||||
initFromHtml: function() {},
|
||||
initFromParams: function() {},
|
||||
createHtml: function() {throw "Not implemented";},
|
||||
onLayerInitialzied: function(layer) {},
|
||||
|
||||
createPlaceHolderHtml: function() {
|
||||
var size = {width: 5, height: 5};
|
||||
var def = {};
|
||||
def.id = this.id;
|
||||
def.params = this.params;
|
||||
|
||||
sb = [];
|
||||
sb.push('<img ');
|
||||
sb.push( 'class="xqlayer" ');
|
||||
sb.push( 'src="' + this.placeHolderImgSrc + '" ');
|
||||
sb.push( 'width="' + (size.width + 4) + '" height="' + (size.height + 4) + '" ');
|
||||
sb.push( 'longdesc="' + escape(JSON.stringify(def)) + '" ');
|
||||
sb.push( 'style="border: 1px solid #ccc" ');
|
||||
sb.push('/>');
|
||||
|
||||
return sb.join('');
|
||||
}
|
||||
})
|
||||
52
modules/editor/skins/xquared/javascripts/macro/Factory.js
Normal file
52
modules/editor/skins/xquared/javascripts/macro/Factory.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires macro/Base.js
|
||||
*/
|
||||
xq.macro.Factory = xq.Class(/** @lends xq.macro.Factory.prototype */{
|
||||
/**
|
||||
* @constructs
|
||||
*
|
||||
* @param {String} URL to place holder image.
|
||||
*/
|
||||
initialize: function(placeHolderImgSrc) {
|
||||
this.placeHolderImgSrc = placeHolderImgSrc;
|
||||
this.macroClazzes = {};
|
||||
},
|
||||
/**
|
||||
* Registers new macro by ID.
|
||||
*
|
||||
* @param {String} id Macro id.
|
||||
*/
|
||||
register: function(id) {
|
||||
var clazz = xq.macro[id + "Macro"];
|
||||
if(!clazz) throw "Unknown macro id: [" + id + "]";
|
||||
|
||||
this.macroClazzes[id] = clazz;
|
||||
},
|
||||
/**
|
||||
* Creates macro instance by given HTML fragment.
|
||||
*
|
||||
* @param {String} html HTML fragment.
|
||||
* @returns {xq.macro.Base} Macro instance or null if recognization of the HTML fragment fails.
|
||||
*/
|
||||
createMacroFromHtml: function(html) {
|
||||
for(var id in this.macroClazzes) {
|
||||
var clazz = this.macroClazzes[id];
|
||||
if(clazz.recognize(html)) return new clazz(id, html, this.placeHolderImgSrc);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
/**
|
||||
* Creates macro instance by given macro definition.
|
||||
*
|
||||
* @param {Object} def Macro definition.
|
||||
* @returns {xq.macro.Base} Macro instance
|
||||
* @throws If macro not found by def[id].
|
||||
*/
|
||||
createMacroFromDefinition: function(def) {
|
||||
var clazz = this.macroClazzes[def.id];
|
||||
if(!clazz) return null;
|
||||
|
||||
return new clazz(def.id, def.params, this.placeHolderImgSrc);
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* @requires macro/Base.js
|
||||
*/
|
||||
xq.macro.FlashMovieMacro = xq.Class(xq.macro.Base,
|
||||
/**
|
||||
* Flash movie macro
|
||||
*
|
||||
* @name xq.macro.FlashMovieMacro
|
||||
* @lends xq.macro.FlashMovieMacro.prototype
|
||||
* @extends xq.macro.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
initFromHtml: function() {
|
||||
this.params.html = this.html;
|
||||
},
|
||||
initFromParams: function() {
|
||||
if(!xq.macro.FlashMovieMacro.recognize(this.params.html)) throw "Unknown src";
|
||||
},
|
||||
createHtml: function() {
|
||||
return this.params.html;
|
||||
}
|
||||
});
|
||||
xq.macro.FlashMovieMacro.recognize = function(html) {
|
||||
var providers = {
|
||||
tvpot: /http:\/\/flvs\.daum\.net\/flvPlayer\.swf\?/,
|
||||
youtube: /http:\/\/(?:www\.)?youtube\.com\/v\//,
|
||||
pandoratv: /http:\/\/flvr\.pandora\.tv\/flv2pan\/flvmovie\.dll\?/,
|
||||
pandoratv2: /http:\/\/imgcdn\.pandora\.tv\/gplayer\/pandora\_EGplayer\.swf\?/,
|
||||
mncast: /http:\/\/dory\.mncast\.com\/mncHMovie\.swf\?/,
|
||||
yahoo: /http:\/\/d\.yimg\.com\//
|
||||
};
|
||||
|
||||
for(var id in providers) {
|
||||
if(html.match(providers[id])) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @requires macro/Base.js
|
||||
*/
|
||||
xq.macro.IFrameMacro = xq.Class(xq.macro.Base,
|
||||
/**
|
||||
* IFrame macro
|
||||
*
|
||||
* @name xq.macro.IFrameMacro
|
||||
* @lends xq.macro.IFrameMacro.prototype
|
||||
* @extends xq.macro.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
initFromHtml: function() {
|
||||
this.params.html = this.html;
|
||||
},
|
||||
initFromParams: function() {
|
||||
if(this.params.html) return;
|
||||
|
||||
var sb = [];
|
||||
sb.push('<iframe');
|
||||
for(var attrName in this.params) {
|
||||
var attrValue = this.params[attrName];
|
||||
if(attrValue) sb.push(' ' + attrName.substring("p_".length) + '="' + attrValue + '"');
|
||||
}
|
||||
sb.push('></iframe>');
|
||||
this.params = {html:sb.join("")};
|
||||
},
|
||||
createHtml: function() {
|
||||
return this.params.html;
|
||||
}
|
||||
});
|
||||
xq.macro.IFrameMacro.recognize = function(html) {
|
||||
var p = xq.compilePattern("<IFRAME\\s+[^>]+(?:/>|>.*?</(?:IFRAME)>)", "img");
|
||||
return !!html.match(p);
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* @requires macro/Base.js
|
||||
*/
|
||||
xq.macro.JavascriptMacro = xq.Class(xq.macro.Base,
|
||||
/**
|
||||
* Javascript macro
|
||||
*
|
||||
* @name xq.macro.JavascriptMacro
|
||||
* @lends xq.macro.JavascriptMacro.prototype
|
||||
* @extends xq.macro.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
initFromHtml: function() {
|
||||
var p = xq.compilePattern("src=[\"'](.+?)[\"']", "img");
|
||||
this.params.url = p.exec(this.html)[1];
|
||||
},
|
||||
initFromParams: function() {
|
||||
if(!xq.macro.JavascriptMacro.isSafeScript(this.params.url)) throw "Unknown src";
|
||||
},
|
||||
createHtml: function() {return '<script type="text/javascript" src="' + this.params.url + '"></script>'},
|
||||
|
||||
onLayerInitialzied: function(layer) {
|
||||
layer.getDoc().write(this.createHtml());
|
||||
}
|
||||
});
|
||||
|
||||
xq.macro.JavascriptMacro.recognize = function(html) {
|
||||
var p = xq.compilePattern("<SCRIPT\\s+[^>]*src=[\"']([^\"']+)[\"'][^>]*(?:/>|>.*?</(?:SCRIPT)>)", "img");
|
||||
var m = p.exec(html);
|
||||
if(!m || !m[1]) return false;
|
||||
return this.isSafeScript(m[1]);
|
||||
}
|
||||
xq.macro.JavascriptMacro.isSafeScript = function(url) {
|
||||
var safeSrcs = {
|
||||
googleGadget: /http:\/\/gmodules\.com\/ig\/ifr\?/img
|
||||
};
|
||||
for(var id in safeSrcs) {
|
||||
if(url.match(safeSrcs[id])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
9
modules/editor/skins/xquared/javascripts/module/Full.js
Normal file
9
modules/editor/skins/xquared/javascripts/module/Full.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Editor.js
|
||||
* @requires plugin/MacroPlugin.js
|
||||
* @requires plugin/FlashMovieMacroPlugin.js
|
||||
* @requires plugin/IFrameMacroPlugin.js
|
||||
* @requires plugin/JavascriptMacroPlugin.js
|
||||
*/
|
||||
xq.moduleName = "Full";
|
||||
37
modules/editor/skins/xquared/javascripts/module/Full_list
Normal file
37
modules/editor/skins/xquared/javascripts/module/Full_list
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
Xquared.js
|
||||
Browser.js
|
||||
Timer.js
|
||||
DomTree.js
|
||||
rdom/Base.js
|
||||
rdom/Trident.js
|
||||
rdom/W3.js
|
||||
rdom/Gecko.js
|
||||
rdom/Webkit.js
|
||||
rdom/Factory.js
|
||||
validator/Base.js
|
||||
validator/Trident.js
|
||||
validator/W3.js
|
||||
validator/Gecko.js
|
||||
validator/Webkit.js
|
||||
validator/Factory.js
|
||||
EditHistory.js
|
||||
plugin/Base.js
|
||||
RichTable.js
|
||||
ui/Base.js
|
||||
ui/Control.js
|
||||
ui/Toolbar.js
|
||||
ui/_templates.js
|
||||
Shortcut.js
|
||||
Editor.js
|
||||
macro/Base.js
|
||||
macro/Factory.js
|
||||
Layer.js
|
||||
Json2.js
|
||||
plugin/MacroPlugin.js
|
||||
macro/FlashMovieMacro.js
|
||||
plugin/FlashMovieMacroPlugin.js
|
||||
macro/IFrameMacro.js
|
||||
plugin/IFrameMacroPlugin.js
|
||||
macro/JavascriptMacro.js
|
||||
plugin/JavascriptMacroPlugin.js
|
||||
module/Full.js
|
||||
9999
modules/editor/skins/xquared/javascripts/module/Full_merged.js
Normal file
9999
modules/editor/skins/xquared/javascripts/module/Full_merged.js
Normal file
File diff suppressed because it is too large
Load diff
3531
modules/editor/skins/xquared/javascripts/module/Full_merged_min.js
Normal file
3531
modules/editor/skins/xquared/javascripts/module/Full_merged_min.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Editor.js
|
||||
*/
|
||||
xq.moduleName = "Minimal"
|
||||
26
modules/editor/skins/xquared/javascripts/module/Minimal_list
Normal file
26
modules/editor/skins/xquared/javascripts/module/Minimal_list
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
Xquared.js
|
||||
Browser.js
|
||||
Timer.js
|
||||
DomTree.js
|
||||
rdom/Base.js
|
||||
rdom/Trident.js
|
||||
rdom/W3.js
|
||||
rdom/Gecko.js
|
||||
rdom/Webkit.js
|
||||
rdom/Factory.js
|
||||
validator/Base.js
|
||||
validator/Trident.js
|
||||
validator/W3.js
|
||||
validator/Gecko.js
|
||||
validator/Webkit.js
|
||||
validator/Factory.js
|
||||
EditHistory.js
|
||||
plugin/Base.js
|
||||
RichTable.js
|
||||
ui/Base.js
|
||||
ui/Control.js
|
||||
ui/Toolbar.js
|
||||
ui/_templates.js
|
||||
Shortcut.js
|
||||
Editor.js
|
||||
module/Minimal.js
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
102
modules/editor/skins/xquared/javascripts/plugin/Base.js
Normal file
102
modules/editor/skins/xquared/javascripts/plugin/Base.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @namespace
|
||||
*/
|
||||
xq.plugin = {};
|
||||
|
||||
/**
|
||||
* @requires Xquared.js
|
||||
*/
|
||||
xq.plugin.Base = xq.Class(/** @lends xq.plugin.Base.prototype */{
|
||||
/**
|
||||
* Abstract base class for Xquared plugins.
|
||||
*
|
||||
* @constructs
|
||||
*/
|
||||
initialize: function() {},
|
||||
|
||||
/**
|
||||
* Loads plugin. Automatically called by xq.Editor.
|
||||
*
|
||||
* @param {xq.Editor} editor Editor instance.
|
||||
*/
|
||||
load: function(editor) {
|
||||
this.editor = editor;
|
||||
if(this.isEventListener()) this.editor.addListener(this);
|
||||
|
||||
this.onBeforeLoad(this.editor);
|
||||
this.editor.addShortcuts(this.getShortcuts() || []);
|
||||
this.editor.addAutocorrections(this.getAutocorrections() || []);
|
||||
this.editor.addAutocompletions(this.getAutocompletions() || []);
|
||||
this.editor.addTemplateProcessors(this.getTemplateProcessors() || []);
|
||||
this.editor.addContextMenuHandlers(this.getContextMenuHandlers() || []);
|
||||
this.onAfterLoad(this.editor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unloads plugin. Automatically called by xq.Editor
|
||||
*/
|
||||
unload: function() {
|
||||
this.onBeforeUnload(this.editor);
|
||||
for(var key in this.getShortcuts()) this.editor.removeShortcut(key);
|
||||
for(var key in this.getAutocorrections()) this.editor.removeAutocorrection(key);
|
||||
for(var key in this.getAutocompletions()) this.editor.removeAutocompletion(key);
|
||||
for(var key in this.getTemplateProcessors()) this.editor.removeTemplateProcessor(key);
|
||||
for(var key in this.getContextMenuHandlers()) this.editor.removeContextMenuHandler(key);
|
||||
this.onAfterUnload(this.editor);
|
||||
},
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Always returns false.<br />
|
||||
* <br />
|
||||
* Derived class may override this to make a plugin as a event listener.<br />
|
||||
* Whenever you override this function, you should also implement at least one event handler for xq.Editor.
|
||||
*/
|
||||
isEventListener: function() {return false},
|
||||
|
||||
/**
|
||||
* Callback function. Derived class may override this.
|
||||
*/
|
||||
onBeforeLoad: function(editor) {},
|
||||
|
||||
/**
|
||||
* Callback function. Derived class may override this.
|
||||
*/
|
||||
onAfterLoad: function(editor) {},
|
||||
|
||||
/**
|
||||
* Callback function. Derived class may override this.
|
||||
*/
|
||||
onBeforeUnload: function(editor) {},
|
||||
|
||||
/**
|
||||
* Callback function. Derived class may override this.
|
||||
*/
|
||||
onAfterUnload: function(editor) {},
|
||||
|
||||
/**
|
||||
* Callback function. Derived class may override this.
|
||||
*/
|
||||
getShortcuts: function() {return [];},
|
||||
|
||||
/**
|
||||
* Callback function. Derived class may override this.
|
||||
*/
|
||||
getAutocorrections: function() {return [];},
|
||||
|
||||
/**
|
||||
* Callback function. Derived class may override this.
|
||||
*/
|
||||
getAutocompletions: function() {return [];},
|
||||
|
||||
/**
|
||||
* Callback function. Derived class may override this.
|
||||
*/
|
||||
getTemplateProcessors: function() {return [];},
|
||||
|
||||
/**
|
||||
* Callback function. Derived class may override this.
|
||||
*/
|
||||
getContextMenuHandlers: function() {return [];}
|
||||
});
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Browser.js
|
||||
* @requires Editor.js
|
||||
* @requires plugin/Base.js
|
||||
*/
|
||||
xq.plugin.EditorResizePlugin = xq.Class(xq.plugin.Base,
|
||||
/**
|
||||
* @name xq.plugin.EditorResizePlugin
|
||||
* @lends xq.plugin.EditorResizePlugin.prototype
|
||||
* @extends xq.plugin.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
isEventListener: function() {return true;},
|
||||
|
||||
onAfterLoad: function(xed) {
|
||||
this.xed = xed;
|
||||
this.bar = null;
|
||||
this.screen = null;
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
onEditorInitialized: function(xed) {
|
||||
xed.registerEventFirer("Editor", "Resized");
|
||||
|
||||
var wrapper = this.xed.getOutmostWrapper();
|
||||
var doc = wrapper.ownerDocument;
|
||||
|
||||
// create resize bar
|
||||
this.bar = doc.createElement("DIV");
|
||||
if(xq.Browser.isIE6) this.bar.innerHTML = "<span></span>";
|
||||
this.bar.style.height = "6px";
|
||||
this.bar.style.backgroundColor = "#ddd";
|
||||
this.bar.style.cursor = "n-resize";
|
||||
wrapper.appendChild(this.bar);
|
||||
|
||||
// register event
|
||||
xq.observe(this.bar, 'mousedown', this.onMousedown.bindAsEventListener(this));
|
||||
xq.observe(this.bar, 'mouseup', this.onMouseup.bindAsEventListener(this));
|
||||
xq.observe(this.bar, 'click', this.onMouseup.bindAsEventListener(this));
|
||||
this.mousemoveHandler = this.onMousemove.bindAsEventListener(this);
|
||||
},
|
||||
|
||||
onMousedown: function(e) {
|
||||
if(this.active) return;
|
||||
|
||||
xq.observe(document, 'mousemove', this.mousemoveHandler);
|
||||
this.last = e.screenY;
|
||||
|
||||
var wrapper = this.xed.getOutmostWrapper();
|
||||
var doc = wrapper.ownerDocument;
|
||||
var wysiwygDiv = this.xed.getWysiwygEditorDiv();
|
||||
var sourceDiv = this.xed.getSourceEditorDiv();
|
||||
var visibleDiv = this.xed.getCurrentEditMode() == "wysiwyg" ? wysiwygDiv : sourceDiv;
|
||||
var location = xq.getCumulativeOffset(visibleDiv);
|
||||
|
||||
// create screen
|
||||
this.screen = doc.createElement("DIV");
|
||||
if(xq.Browser.isIE6) this.screen.innerHTML = "<span></span>";
|
||||
|
||||
if(xq.Browser.isIE6) {
|
||||
this.screen.style.backgroundColor = "#EEE";
|
||||
wysiwygDiv.style.display = "none";
|
||||
} else {
|
||||
this.screen.style.position = "absolute";
|
||||
this.screen.style.left = location.left + "px";
|
||||
this.screen.style.top = location.top + "px";
|
||||
}
|
||||
|
||||
this.screen.style.width = visibleDiv.clientWidth + "px";
|
||||
this.screen.style.height = visibleDiv.clientHeight + "px";
|
||||
wrapper.insertBefore(this.screen, visibleDiv);
|
||||
|
||||
this.resize(e.screenY);
|
||||
this.active = true;
|
||||
|
||||
xq.stopEvent(e);
|
||||
return true;
|
||||
},
|
||||
onMouseup: function(e) {
|
||||
if(!this.active) return;
|
||||
|
||||
this.active = false;
|
||||
|
||||
xq.stopObserving(document, 'mousemove', this.mousemoveHandler);
|
||||
this.resize(e.screenY);
|
||||
|
||||
if(xq.Browser.isIE6) {
|
||||
var wysiwygDiv = this.xed.getWysiwygEditorDiv();
|
||||
var sourceDiv = this.xed.getSourceEditorDiv();
|
||||
var visibleDiv = this.xed.getCurrentEditMode() == "wysiwyg" ? wysiwygDiv : sourceDiv;
|
||||
visibleDiv.style.display = "block";
|
||||
}
|
||||
|
||||
this.screen.parentNode.removeChild(this.screen);
|
||||
this.screen = null;
|
||||
|
||||
this.xed._fireOnResized(this.xed);
|
||||
|
||||
xq.stopEvent(e);
|
||||
return true;
|
||||
},
|
||||
onMousemove: function(e) {
|
||||
this.resize(e.screenY);
|
||||
|
||||
xq.stopEvent(e);
|
||||
return true;
|
||||
},
|
||||
resize: function(y) {
|
||||
var delta = y - this.last;
|
||||
|
||||
var wysiwygDiv = this.xed.getWysiwygEditorDiv();
|
||||
var sourceDiv = this.xed.getSourceEditorDiv();
|
||||
var newHeight = Math.max(0, this.screen.clientHeight + delta);
|
||||
|
||||
sourceDiv.style.height = wysiwygDiv.style.height = this.screen.style.height = newHeight + "px";
|
||||
|
||||
this.last = y;
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Browser.js
|
||||
* @requires Editor.js
|
||||
* @requires plugin/Base.js
|
||||
*/
|
||||
xq.plugin.EventLogPlugin = xq.Class(xq.plugin.Base,
|
||||
/**
|
||||
* @name xq.plugin.EventLogPlugin
|
||||
* @lends xq.plugin.EventLogPlugin.prototype
|
||||
* @extends xq.plugin.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
isEventListener: function() {return true;},
|
||||
|
||||
onAfterLoad: function(xed) {
|
||||
this.createLogWindow();
|
||||
},
|
||||
|
||||
onEditorStartInitialization: function(xed) {
|
||||
this.log("Start initialization.");
|
||||
},
|
||||
onEditorInitialized: function(xed) {
|
||||
this.log("Initialized.");
|
||||
},
|
||||
onEditorElementChanged: function(xed, from, to) {
|
||||
this.log("Element changed from <" + (from ? from.nodeName : null) + "> to <" + (to ? to.nodeName : null) + ">.");
|
||||
},
|
||||
onEditorBeforeEvent: function(xed, e) {
|
||||
this.log("Before event [" + e.type + "]");
|
||||
},
|
||||
onEditorAfterEvent: function(xed, e) {
|
||||
this.log("After event [" + e.type + "]");
|
||||
},
|
||||
onEditorCurrentContentChanged: function(xed) {
|
||||
this.log("Current content changed.");
|
||||
},
|
||||
onEditorStaticContentChanged: function(xed, content) {
|
||||
this.log("Static content changed.");
|
||||
},
|
||||
onEditorCurrentEditModeChanged: function(xed, from, to) {
|
||||
this.log("Edit mode changed from <" + from + "> to <" + to + ">.");
|
||||
},
|
||||
|
||||
|
||||
|
||||
createLogWindow: function() {
|
||||
var wrapper = document.createElement("DIV");
|
||||
wrapper.innerHTML = "<h2>Log</h2>";
|
||||
wrapper.style.width = "500px";
|
||||
document.body.appendChild(wrapper);
|
||||
|
||||
this.logWindow = document.createElement("PRE");
|
||||
this.logWindow.style.fontSize = "0.75em";
|
||||
this.logWindow.style.height = "200px";
|
||||
this.logWindow.style.overflow = "scroll";
|
||||
this.logWindow.style.border = "1px solid black";
|
||||
this.logWindow.style.padding = "2px";
|
||||
wrapper.appendChild(this.logWindow);
|
||||
},
|
||||
|
||||
log: function(message) {
|
||||
var line = document.createTextNode(this.getFormattedTime() + ": " + message);
|
||||
this.logWindow.insertBefore(document.createElement("BR"), this.logWindow.firstChild);
|
||||
this.logWindow.insertBefore(line, this.logWindow.firstChild);
|
||||
},
|
||||
|
||||
getFormattedTime: function() {
|
||||
var date = new Date();
|
||||
var time = date.toTimeString().split(" ")[0];
|
||||
var msec = "000" + date.getMilliseconds();
|
||||
msec = msec.substring(msec.length - 4);
|
||||
|
||||
return time + "." + msec;
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Browser.js
|
||||
* @requires Editor.js
|
||||
* @requires plugin/Base.js
|
||||
* @requires ui/Control.js
|
||||
* @requires macro/Factory.js
|
||||
* @requires macro/FlashMovieMacro.js
|
||||
*/
|
||||
xq.plugin.FlashMovieMacroPlugin = xq.Class(xq.plugin.Base,
|
||||
/**
|
||||
* @name xq.plugin.FlashMovieMacroPlugin
|
||||
* @lends xq.plugin.FlashMovieMacroPlugin.prototype
|
||||
* @extends xq.plugin.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
onAfterLoad: function(xed) {
|
||||
xed.config.macroIds.push("FlashMovie");
|
||||
xed.config.defaultToolbarButtonGroups.insert.push(
|
||||
{className:"movie", title:"Movie", handler:"xed.handleMovie()"}
|
||||
)
|
||||
|
||||
xed.handleInsertMovie = function(html) {
|
||||
var macro = this.macroFactory.createMacroFromDefinition({id:"FlashMovie", params:{html:html}});
|
||||
if(macro) {
|
||||
var placeHolder = macro.createPlaceHolderHtml();
|
||||
this.rdom.insertHtml(placeHolder);
|
||||
|
||||
var historyAdded = this.editHistory.onCommand();
|
||||
this._fireOnCurrentContentChanged(this);
|
||||
} else {
|
||||
alert("Unknown URL pattern");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
xed.handleMovie = function() {
|
||||
var dialog = new xq.ui.FormDialog(
|
||||
this,
|
||||
xq.ui_templates.basicMovieDialog,
|
||||
function(dialog) {},
|
||||
function(data) {
|
||||
this.focus();
|
||||
|
||||
if(xq.Browser.isTrident) {
|
||||
var rng = this.rdom.rng();
|
||||
rng.moveToBookmark(bm);
|
||||
rng.select();
|
||||
}
|
||||
|
||||
// cancel?
|
||||
if(!data) return;
|
||||
|
||||
this.handleInsertMovie(data.html);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
if(xq.Browser.isTrident) var bm = this.rdom.rng().getBookmark();
|
||||
dialog.show({position: 'centerOfEditor'});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Browser.js
|
||||
* @requires Editor.js
|
||||
* @requires plugin/Base.js
|
||||
* @requires ui/Control.js
|
||||
* @requires macro/Factory.js
|
||||
* @requires macro/IFrameMacro.js
|
||||
*/
|
||||
xq.plugin.IFrameMacroPlugin = xq.Class(xq.plugin.Base,
|
||||
/**
|
||||
* @name xq.plugin.IFrameMacroPlugin
|
||||
* @lends xq.plugin.IFrameMacroPlugin.prototype
|
||||
* @extends xq.plugin.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
onAfterLoad: function(xed) {
|
||||
xed.config.macroIds.push("IFrame");
|
||||
xed.config.defaultToolbarButtonGroups.insert.push(
|
||||
{className:"iframe", title:"IFrame", handler:"xed.handleIFrame()"}
|
||||
)
|
||||
|
||||
xed.handleIFrame = function() {
|
||||
var dialog = new xq.ui.FormDialog(
|
||||
this,
|
||||
xq.ui_templates.basicIFrameDialog,
|
||||
function(dialog) {},
|
||||
function(data) {
|
||||
this.focus();
|
||||
|
||||
// cancel?
|
||||
if(!data) return;
|
||||
|
||||
var macro = this.macroFactory.createMacroFromDefinition({id:"IFrame", params:data});
|
||||
if(macro) {
|
||||
var placeHolder = macro.createPlaceHolderHtml();
|
||||
this.rdom.insertHtml(placeHolder);
|
||||
} else {
|
||||
alert("Unknown error");
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
dialog.show({position: 'centerOfEditor'});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Browser.js
|
||||
* @requires Editor.js
|
||||
* @requires plugin/Base.js
|
||||
* @requires ui/Control.js
|
||||
* @requires macro/Factory.js
|
||||
* @requires macro/JavascriptMacro.js
|
||||
*/
|
||||
xq.plugin.JavascriptMacroPlugin = xq.Class(xq.plugin.Base,
|
||||
/**
|
||||
* @name xq.plugin.JavascriptMacroPlugin
|
||||
* @lends xq.plugin.JavascriptMacroPlugin.prototype
|
||||
* @extends xq.plugin.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
onAfterLoad: function(xed) {
|
||||
xed.config.macroIds.push("Javascript");
|
||||
xed.config.defaultToolbarButtonGroups.insert.push(
|
||||
{className:"script", title:"Script", handler:"xed.handleScript()"}
|
||||
)
|
||||
|
||||
xed.handleInsertScript = function(url) {
|
||||
var params = {url: url};
|
||||
var macro = this.macroFactory.createMacroFromDefinition({id:"Javascript", params:params});
|
||||
if(macro) {
|
||||
var placeHolder = macro.createPlaceHolderHtml();
|
||||
this.rdom.insertHtml(placeHolder);
|
||||
|
||||
var historyAdded = this.editHistory.onCommand();
|
||||
this._fireOnCurrentContentChanged(this);
|
||||
} else {
|
||||
alert("Unknown URL pattern");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
xed.handleScript = function() {
|
||||
var dialog = new xq.ui.FormDialog(
|
||||
this,
|
||||
xq.ui_templates.basicScriptDialog,
|
||||
function(dialog) {},
|
||||
function(data) {
|
||||
this.focus();
|
||||
|
||||
if(xq.Browser.isTrident) {
|
||||
var rng = this.rdom.rng();
|
||||
rng.moveToBookmark(bm);
|
||||
rng.select();
|
||||
}
|
||||
|
||||
// cancel?
|
||||
if(!data) return;
|
||||
|
||||
this.handleInsertScript(data.url);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
if(xq.Browser.isTrident) var bm = this.rdom.rng().getBookmark();
|
||||
dialog.show({position: 'centerOfEditor'});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
110
modules/editor/skins/xquared/javascripts/plugin/MacroPlugin.js
Normal file
110
modules/editor/skins/xquared/javascripts/plugin/MacroPlugin.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Browser.js
|
||||
* @requires Editor.js
|
||||
*
|
||||
* @requires macro/Factory.js
|
||||
* @requires Layer.js
|
||||
* @requires Json2.js
|
||||
*
|
||||
* @requires plugin/Base.js
|
||||
*/
|
||||
xq.plugin.MacroPlugin = xq.Class(xq.plugin.Base,
|
||||
/**
|
||||
* @name xq.plugin.MacroPlugin
|
||||
* @lends xq.plugin.MacroPlugin.prototype
|
||||
* @extends xq.plugin.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
isEventListener: function() {return true;},
|
||||
|
||||
onAfterLoad: function(xed) {
|
||||
this.xed = xed;
|
||||
this.xed.config.macroIds = [];
|
||||
this.layers = [];
|
||||
},
|
||||
|
||||
onEditorStartInitialization: function(xed) {
|
||||
this.xed.validator.addListener(this);
|
||||
|
||||
this.xed.macroFactory = new xq.macro.Factory(this.xed.config.imagePathForContent + 'placeholder.gif');
|
||||
for(var i = 0; i < this.xed.config.macroIds.length; i++) {
|
||||
this.xed.macroFactory.register(this.xed.config.macroIds[i]);
|
||||
}
|
||||
|
||||
xed.timer.register(this.updateLayers.bind(this), 100);
|
||||
xed.timer.register(this.updateLayerList.bind(this), 2000);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Element} [placeHolder] place holder element
|
||||
*/
|
||||
attachMacro: function(element) {
|
||||
var longdesc = element.getAttribute("longdesc") || element.longdesc;
|
||||
var def = JSON.parse(unescape(longdesc));
|
||||
var macro = this.xed.macroFactory.createMacroFromDefinition(def);
|
||||
var layer = new xq.Layer(this.xed, element, macro.createHtml());
|
||||
macro.onLayerInitialzied(layer);
|
||||
this.layers.push(layer);
|
||||
},
|
||||
|
||||
isAttachedPlaceHolder: function(element) {
|
||||
for(var i = 0; i < this.layers.length; i++) {
|
||||
if(this.layers[i].element === element) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
updateLayerList: function() {
|
||||
if(this.xed.getCurrentEditMode() !== 'wysiwyg') {
|
||||
for(var i = 0; i < this.layers.length; i++) {
|
||||
this.layers[i].detach();
|
||||
}
|
||||
this.layers = [];
|
||||
} else {
|
||||
var placeHolders = xq.getElementsByClassName(this.xed.rdom.getRoot(), "xqlayer", xq.Browser.isTrident ? "img" : null);
|
||||
for(var i = 0; i < placeHolders.length; i++) {
|
||||
if(!this.isAttachedPlaceHolder(placeHolders[i])) {
|
||||
this.attachMacro(placeHolders[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates all layers immediately. If there're invalid layers, detachs and removes them.
|
||||
*/
|
||||
updateLayers: function() {
|
||||
if(this.xed.getCurrentEditMode() !== 'wysiwyg') return;
|
||||
|
||||
for(var i = 0; i < this.layers.length; i++) {
|
||||
var layer = this.layers[i];
|
||||
if(layer.isValid()) {
|
||||
layer.updatePosition();
|
||||
} else {
|
||||
layer.detach();
|
||||
this.layers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onValidatorPreprocessing: function(html) {
|
||||
var p = xq.compilePattern("<(IFRAME|SCRIPT|OBJECT|EMBED)\\s+[^>]+(?:/>|>.*?</(?:IFRAME|SCRIPT|OBJECT|EMBED)>)", "img");
|
||||
html.value = html.value.replace(p, function(str, tag) {
|
||||
var macro = this.xed.macroFactory.createMacroFromHtml(str);
|
||||
return macro ? macro.createPlaceHolderHtml() : "";
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
onValidatorAfterStringValidation: function(html) {
|
||||
var p1 = /<img\s+[^>]*class="xqlayer"\s+[^>]*\/>/mg;
|
||||
var p2 = /<img\s+[^>]*longdesc="(.+?)"\s+[^>]*\/>/m;
|
||||
|
||||
html.value = html.value.replace(p1, function(img) {
|
||||
var def = JSON.parse(unescape(img.match(p2)[1]));
|
||||
var macro = this.xed.macroFactory.createMacroFromDefinition(def);
|
||||
return macro.createHtml();
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Browser.js
|
||||
* @requires Editor.js
|
||||
* @requires plugin/Base.js
|
||||
*/
|
||||
xq.plugin.SpringnotePlugin = xq.Class(xq.plugin.Base,
|
||||
/**
|
||||
* @name xq.plugin.SpringnotePlugin
|
||||
* @lends xq.plugin.SpringnotePlugin.prototype
|
||||
* @extends xq.plugin.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
getShortcuts: function() {
|
||||
if(xq.Browser.isMac) {
|
||||
// Mac FF & Safari
|
||||
return [
|
||||
{event:"Ctrl+SPACE", handler:"xed.handleAutocompletion(); stop = true;"},
|
||||
{event:"Ctrl+Meta+0", handler:"xed.handleApplyBlock('P')"},
|
||||
{event:"Ctrl+Meta+1", handler:"xed.handleApplyBlock('H1')"},
|
||||
{event:"Ctrl+Meta+2", handler:"xed.handleApplyBlock('H2')"},
|
||||
{event:"Ctrl+Meta+3", handler:"xed.handleApplyBlock('H3')"},
|
||||
{event:"Ctrl+Meta+4", handler:"xed.handleApplyBlock('H4')"},
|
||||
{event:"Ctrl+Meta+5", handler:"xed.handleApplyBlock('H5')"},
|
||||
{event:"Ctrl+Meta+6", handler:"xed.handleApplyBlock('H6')"},
|
||||
|
||||
{event:"Ctrl+Meta+B", handler:"xed.handleApplyBlock('BLOCKQUOTE')"},
|
||||
{event:"Ctrl+Meta+D", handler:"xed.handleApplyBlock('DIV')"},
|
||||
{event:"Ctrl+Meta+EQUAL", handler:"xed.handleSeparator()"},
|
||||
|
||||
{event:"Ctrl+Meta+O", handler:"xed.handleList('OL')"},
|
||||
{event:"Ctrl+Meta+U", handler:"xed.handleList('UL')"},
|
||||
|
||||
{event:"Ctrl+Meta+E", handler:"xed.handleRemoveBlock()"},
|
||||
|
||||
{event:"Ctrl+(Meta)+COMMA", handler:"xed.handleJustify('left')"},
|
||||
{event:"Ctrl+(Meta)+PERIOD", handler:"xed.handleJustify('center')"},
|
||||
{event:"Ctrl+(Meta)+SLASH", handler:"xed.handleJustify('right')"},
|
||||
|
||||
{event:"Meta+UP", handler:"xed.handleMoveBlock(true)"},
|
||||
{event:"Meta+DOWN", handler:"xed.handleMoveBlock(false)"}
|
||||
];
|
||||
} else if(xq.Browser.isUbuntu) {
|
||||
// Ubunto FF
|
||||
return [
|
||||
{event:"Ctrl+SPACE", handler:"xed.handleAutocompletion(); stop = true;"},
|
||||
{event:"Ctrl+0", handler:"xed.handleApplyBlock('P')"},
|
||||
{event:"Ctrl+1", handler:"xed.handleApplyBlock('H1')"},
|
||||
{event:"Ctrl+2", handler:"xed.handleApplyBlock('H2')"},
|
||||
{event:"Ctrl+3", handler:"xed.handleApplyBlock('H3')"},
|
||||
{event:"Ctrl+4", handler:"xed.handleApplyBlock('H4')"},
|
||||
{event:"Ctrl+5", handler:"xed.handleApplyBlock('H5')"},
|
||||
{event:"Ctrl+6", handler:"xed.handleApplyBlock('H6')"},
|
||||
|
||||
{event:"Ctrl+Alt+B", handler:"xed.handleApplyBlock('BLOCKQUOTE')"},
|
||||
{event:"Ctrl+Alt+D", handler:"xed.handleApplyBlock('DIV')"},
|
||||
{event:"Alt+HYPHEN", handler:"xed.handleSeparator()"},
|
||||
|
||||
{event:"Ctrl+Alt+O", handler:"xed.handleList('OL')"},
|
||||
{event:"Ctrl+Alt+U", handler:"xed.handleList('UL')"},
|
||||
|
||||
{event:"Ctrl+Alt+E", handler:"xed.handleRemoveBlock()"},
|
||||
|
||||
{event:"Alt+COMMA", handler:"xed.handleJustify('left')"},
|
||||
{event:"Alt+PERIOD", handler:"xed.handleJustify('center')"},
|
||||
{event:"Alt+SLASH", handler:"xed.handleJustify('right')"},
|
||||
|
||||
{event:"Alt+UP", handler:"xed.handleMoveBlock(true)"},
|
||||
{event:"Alt+DOWN", handler:"xed.handleMoveBlock(false)"}
|
||||
];
|
||||
} else {
|
||||
// Win IE & FF && Safari
|
||||
return [
|
||||
{event:"Ctrl+SPACE", handler:"xed.handleAutocompletion(); stop = true;"},
|
||||
{event:"Alt+0", handler:"xed.handleApplyBlock('P')"},
|
||||
{event:"Alt+1", handler:"xed.handleApplyBlock('H1')"},
|
||||
{event:"Alt+2", handler:"xed.handleApplyBlock('H2')"},
|
||||
{event:"Alt+3", handler:"xed.handleApplyBlock('H3')"},
|
||||
{event:"Alt+4", handler:"xed.handleApplyBlock('H4')"},
|
||||
{event:"Alt+5", handler:"xed.handleApplyBlock('H5')"},
|
||||
{event:"Alt+6", handler:"xed.handleApplyBlock('H6')"},
|
||||
{event:"Alt+7", handler:"xed.handleInsertMacro('TableOfContents')"},
|
||||
{event:"Alt+8", handler:"xed.attachLayer()"},
|
||||
|
||||
{event:"Ctrl+Alt+B", handler:"xed.handleApplyBlock('BLOCKQUOTE')"},
|
||||
{event:"Ctrl+Alt+D", handler:"xed.handleApplyBlock('DIV')"},
|
||||
{event:"Alt+HYPHEN", handler:"xed.handleSeparator()"},
|
||||
|
||||
{event:"Ctrl+Alt+O", handler:"xed.handleList('OL')"},
|
||||
{event:"Ctrl+Alt+U", handler:"xed.handleList('UL')"},
|
||||
|
||||
{event:"Ctrl+Alt+E", handler:"xed.handleRemoveBlock()"},
|
||||
|
||||
{event:"Alt+COMMA", handler:"xed.handleJustify('left')"},
|
||||
{event:"Alt+PERIOD", handler:"xed.handleJustify('center')"},
|
||||
{event:"Alt+SLASH", handler:"xed.handleJustify('right')"},
|
||||
|
||||
{event:"Alt+UP", handler:"xed.handleMoveBlock(true)"},
|
||||
{event:"Alt+DOWN", handler:"xed.handleMoveBlock(false)"}
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
getAutocorrections: function() {
|
||||
return [
|
||||
{id:'bullet', criteria: /^(\s|\ \;)*(\*|-)(\s|\ \;).+$/, handler: function(xed, rdom, block, text) {
|
||||
rdom.pushMarker();
|
||||
rdom.removePlaceHoldersAndEmptyNodes(block);
|
||||
block.innerHTML = block.innerHTML.replace(/((\s| )*(\*|\-)\s*)/, "");
|
||||
if(block.nodeName === "LI") xed.handleIndent();
|
||||
if(block.parentNode.nodeName !== "UL") xed.handleList('UL');
|
||||
rdom.popMarker(true);
|
||||
}},
|
||||
{id:'numbering', criteria: /^(\s|\ \;)*(\d\.|#)(\s|\ \;).+$/, handler: function(xed, rdom, block, text) {
|
||||
rdom.pushMarker();
|
||||
rdom.removePlaceHoldersAndEmptyNodes(block);
|
||||
block.innerHTML = block.innerHTML.replace(/(\s| )*(\d\.|\#)\s*/, "")
|
||||
if(block.nodeName === "LI") xed.handleIndent();
|
||||
if(block.parentNode.nodeName !== "OL") xed.handleList('OL');
|
||||
rdom.popMarker(true);
|
||||
}},
|
||||
{id:'imageUrl', criteria: /https?:\/\/.*?\/(.*?\.(jpg|jpeg|gif|bmp|png))$/i, handler: function(xed, rdom, block, text) {
|
||||
var fileName = text.match(/https?:\/\/.*?\/(.*?\.(jpg|jpeg|gif|bmp|png))$/i)[1];
|
||||
block.innerHTML = "";
|
||||
var img = rdom.createElement("img");
|
||||
img.src = text;
|
||||
img.alt = fileName;
|
||||
img.title = fileName;
|
||||
block.appendChild(img);
|
||||
rdom.selectElement(block);
|
||||
rdom.collapseSelection(false);
|
||||
}},
|
||||
{id:'separator', criteria: /^---+(\ |\s)*$/, handler: function(xed, rdom, block, text) {
|
||||
if(rdom.tree.isBlockContainer(block)) block = rdom.wrapAllInlineOrTextNodesAs("P", block, true)[0];
|
||||
rdom.insertNodeAt(rdom.createElement("HR"), block, "before");
|
||||
block.innerHTML = "";
|
||||
rdom.placeCaretAtStartOf(block);
|
||||
return true;
|
||||
}},
|
||||
{id:'heading', criteria: /^\=+[^=]*\=+(\ |\s)*$/, handler: function(xed, rdom, block, text) {
|
||||
var textWithoutEqualMarks = text.strip().replace(/=/g, "");
|
||||
var level = Math.min(6, parseInt((text.length - textWithoutEqualMarks.length) / 2))
|
||||
xed.handleApplyBlock('H' + level);
|
||||
block = rdom.getCurrentBlockElement();
|
||||
block.innerHTML = textWithoutEqualMarks;
|
||||
rdom.selectElement(block);
|
||||
rdom.collapseSelection();
|
||||
}}
|
||||
];
|
||||
},
|
||||
|
||||
getAutocompletions: function() {
|
||||
return [
|
||||
{
|
||||
id:'isbn',
|
||||
criteria: /@ISBN:\d+$/i,
|
||||
handler: function(xed, rdom, block, wrapper, text) {
|
||||
var isbn = text.split(":")[1]
|
||||
var korean = isbn.indexOf("97889") === 0 || isbn.indexOf("89") === 0
|
||||
var href = korean ?
|
||||
"http://www.aladdin.co.kr/shop/wproduct.aspx?ISBN=" :
|
||||
"http://www.amazon.com/exec/obidos/ISBN="
|
||||
var node = rdom.createElement('A');
|
||||
node.innerHTML = 'ISBN:' + isbn;
|
||||
node.href = href + isbn;
|
||||
node.className = 'external';
|
||||
node.title = 'ISBN:' + isbn;
|
||||
|
||||
wrapper.innerHTML = "";
|
||||
wrapper.appendChild(node);
|
||||
}
|
||||
},
|
||||
{
|
||||
id:'anchor',
|
||||
criteria: /@A(:(.+))?$/i,
|
||||
handler: function(xed, rdom, block, wrapper, text) {
|
||||
var m = text.match(/@A(:(.+))?$/i);
|
||||
var anchorId = m[2] ? m[2] : function() {
|
||||
var id = 0;
|
||||
while(true) {
|
||||
var element = rdom.$("a" + (id));
|
||||
if(!element) return "a" + id;
|
||||
id++;
|
||||
}
|
||||
}();
|
||||
|
||||
var node = rdom.createElement('A');
|
||||
node.id = anchorId;
|
||||
node.href = '#' + anchorId;
|
||||
node.className = 'anchor';
|
||||
node.title = 'Anchor ' + anchorId;
|
||||
node.innerHTML = '(' + anchorId + ')';
|
||||
|
||||
wrapper.innerHTML = "";
|
||||
wrapper.appendChild(node);
|
||||
}
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
getTemplateProcessors: function() {
|
||||
return [
|
||||
{
|
||||
id:"datetime",
|
||||
handler:function(html) {
|
||||
var today = Date.get();
|
||||
var keywords = {
|
||||
year: today.getFullYear(),
|
||||
month: today.getMonth() + 1,
|
||||
date: today.getDate(),
|
||||
hour: today.getHours(),
|
||||
min: today.getMinutes(),
|
||||
sec: today.getSeconds()
|
||||
};
|
||||
|
||||
return html.replace(/\{xq:(year|month|date|hour|min|sec)\}/img, function(text, keyword) {
|
||||
return keywords[keyword] || keyword;
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
2347
modules/editor/skins/xquared/javascripts/rdom/Base.js
Normal file
2347
modules/editor/skins/xquared/javascripts/rdom/Base.js
Normal file
File diff suppressed because it is too large
Load diff
18
modules/editor/skins/xquared/javascripts/rdom/Factory.js
Normal file
18
modules/editor/skins/xquared/javascripts/rdom/Factory.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Creates and returns instance of browser specific implementation.
|
||||
*
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/Base.js
|
||||
* @requires rdom/Trident.js
|
||||
* @requires rdom/Gecko.js
|
||||
* @requires rdom/Webkit.js
|
||||
*/
|
||||
xq.rdom.Base.createInstance = function() {
|
||||
if(xq.Browser.isTrident) {
|
||||
return new xq.rdom.Trident();
|
||||
} else if(xq.Browser.isWebkit) {
|
||||
return new xq.rdom.Webkit();
|
||||
} else {
|
||||
return new xq.rdom.Gecko();
|
||||
}
|
||||
}
|
||||
48
modules/editor/skins/xquared/javascripts/rdom/Gecko.js
Normal file
48
modules/editor/skins/xquared/javascripts/rdom/Gecko.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/W3.js
|
||||
*/
|
||||
xq.rdom.Gecko = xq.Class(xq.rdom.W3,
|
||||
/**
|
||||
* @name xq.rdom.Gecko
|
||||
* @lends xq.rdom.Gecko.prototype
|
||||
* @extends xq.rdom.W3
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
makePlaceHolder: function() {
|
||||
var holder = this.createElement("BR");
|
||||
holder.setAttribute("type", "_moz");
|
||||
return holder;
|
||||
},
|
||||
|
||||
makePlaceHolderString: function() {
|
||||
return '<br type="_moz" />';
|
||||
},
|
||||
|
||||
makeEmptyParagraph: function() {
|
||||
return this.createElementFromHtml('<p><br type="_moz" /></p>');
|
||||
},
|
||||
|
||||
isPlaceHolder: function(node) {
|
||||
return node.nodeName === "BR" && (node.getAttribute("type") === "_moz" || !this.getNextSibling(node));
|
||||
},
|
||||
|
||||
selectElement: function(element, entireElement) {
|
||||
if(!element) throw "[element] is null";
|
||||
if(element.nodeType !== 1) throw "[element] is not an element";
|
||||
|
||||
// @WORKAROUND: required to avoid Windows FF selection bug.
|
||||
try {
|
||||
if(!xq.Browser.isMac) this.getDoc().execCommand("SelectAll", false, null);
|
||||
} catch(ignored) {}
|
||||
|
||||
var rng = this.rng() || this.getDoc().createRange();
|
||||
|
||||
if(entireElement) {
|
||||
rng.selectNode(element);
|
||||
} else {
|
||||
rng.selectNodeContents(element);
|
||||
}
|
||||
}
|
||||
});
|
||||
397
modules/editor/skins/xquared/javascripts/rdom/Trident.js
Normal file
397
modules/editor/skins/xquared/javascripts/rdom/Trident.js
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/Base.js
|
||||
*/
|
||||
xq.rdom.Trident = xq.Class(xq.rdom.Base,
|
||||
/**
|
||||
* @name xq.rdom.Trident
|
||||
* @lends xq.rdom.Trident.prototype
|
||||
* @extends xq.rdom.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
makePlaceHolder: function() {
|
||||
return this.createTextNode(" ");
|
||||
},
|
||||
|
||||
makePlaceHolderString: function() {
|
||||
return ' ';
|
||||
},
|
||||
|
||||
makeEmptyParagraph: function() {
|
||||
return this.createElementFromHtml("<p> </p>");
|
||||
},
|
||||
|
||||
isPlaceHolder: function(node) {
|
||||
return false;
|
||||
},
|
||||
|
||||
getOuterHTML: function(element) {
|
||||
return element.outerHTML;
|
||||
},
|
||||
|
||||
getCurrentBlockElement: function() {
|
||||
var cur = this.getCurrentElement();
|
||||
if(!cur) return null;
|
||||
|
||||
var block = this.getParentBlockElementOf(cur);
|
||||
if(!block) return null;
|
||||
|
||||
if(block.nodeName === "BODY") {
|
||||
// Atomic block such as HR
|
||||
var newParagraph = this.insertNode(this.makeEmptyParagraph());
|
||||
var next = newParagraph.nextSibling;
|
||||
if(this.tree.isAtomic(next)) {
|
||||
this.deleteNode(newParagraph);
|
||||
return next;
|
||||
}
|
||||
} else {
|
||||
return block;
|
||||
}
|
||||
},
|
||||
|
||||
insertNode: function(node) {
|
||||
if(this.hasSelection()) this.collapseSelection(true);
|
||||
|
||||
this.rng().pasteHTML('<span id="xquared_temp"></span>');
|
||||
var marker = this.$('xquared_temp');
|
||||
if(node.id === 'xquared_temp') return marker;
|
||||
|
||||
if(marker) marker.replaceNode(node);
|
||||
return node;
|
||||
},
|
||||
|
||||
removeTrailingWhitespace: function(block) {
|
||||
if(!block) return;
|
||||
|
||||
// @TODO: reimplement to handle atomic tags and so on. (use DomTree)
|
||||
if(this.tree.isBlockOnlyContainer(block)) return;
|
||||
if(this.isEmptyBlock(block)) return;
|
||||
|
||||
var text = block.innerText;
|
||||
var html = block.innerHTML;
|
||||
var lastCharCode = text.charCodeAt(text.length - 1);
|
||||
if(text.length <= 1 || [32,160].indexOf(lastCharCode) === -1) return;
|
||||
|
||||
// shortcut for most common case
|
||||
if(text == html.replace(/ /g, " ")) {
|
||||
block.innerHTML = html.replace(/ $/, "");
|
||||
return;
|
||||
}
|
||||
|
||||
var node = block;
|
||||
while(node && node.nodeType !== 3) node = node.lastChild;
|
||||
if(!node) return;
|
||||
|
||||
// DO NOT REMOVE OR MODIFY FOLLOWING CODE. Modifying following code will crash IE7
|
||||
var nodeValue = node.nodeValue;
|
||||
if(nodeValue.length <= 1) {
|
||||
this.deleteNode(node, true);
|
||||
} else {
|
||||
node.nodeValue = nodeValue.substring(0, nodeValue.length - 1);
|
||||
}
|
||||
},
|
||||
|
||||
correctEmptyElement: function(element) {
|
||||
if(!element || element.nodeType !== 1 || this.tree.isAtomic(element)) return;
|
||||
|
||||
if(element.firstChild) {
|
||||
this.correctEmptyElement(element.firstChild);
|
||||
} else {
|
||||
element.innerHTML = " ";
|
||||
}
|
||||
},
|
||||
|
||||
copyAttributes: function(from, to, copyId) {
|
||||
to.mergeAttributes(from, !copyId);
|
||||
},
|
||||
|
||||
correctParagraph: function() {
|
||||
if(!this.hasFocus()) return false;
|
||||
if(this.hasSelection()) return false;
|
||||
|
||||
var block = this.getCurrentElement();
|
||||
|
||||
// if caret is at
|
||||
// * atomic block level elements(HR) or
|
||||
// * ...
|
||||
// then following is true
|
||||
if(this.tree.isBlockOnlyContainer(block)) {
|
||||
// check for atomic block element such as HR
|
||||
block = this.insertNode(this.makeEmptyParagraph());
|
||||
if(this.tree.isAtomic(block.nextSibling)) {
|
||||
// @WORKAROUND:
|
||||
// At this point, HR has a caret but getCurrentElement() doesn't return the HR and
|
||||
// I couldn't find a way to get this HR. So I have to keep this reference.
|
||||
// I will be used in Editor._handleEnter.
|
||||
this.recentHR = block.nextSibling;
|
||||
this.deleteNode(block);
|
||||
return false;
|
||||
} else {
|
||||
// I can't remember exactly when following is executed and what it does :-(
|
||||
// * Case 1: Performing Ctrl+A and Ctrl+X repeatedly
|
||||
// * ...
|
||||
var nextBlock = this.tree.findForward(
|
||||
block,
|
||||
function(node) {return this.tree.isBlock(node) && !this.tree.isBlockOnlyContainer(node)}.bind(this)
|
||||
);
|
||||
|
||||
if(nextBlock) {
|
||||
this.deleteNode(block);
|
||||
this.placeCaretAtStartOf(nextBlock);
|
||||
} else {
|
||||
this.placeCaretAtStartOf(block);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
block = this.getCurrentBlockElement();
|
||||
if(block.nodeType === 3) block = block.parentNode;
|
||||
|
||||
if(this.tree.hasMixedContents(block)) {
|
||||
var marker = this.pushMarker();
|
||||
this.wrapAllInlineOrTextNodesAs("P", block, true);
|
||||
this.popMarker(true);
|
||||
return true;
|
||||
} else if((this.tree.isTextOrInlineNode(block.previousSibling) || this.tree.isTextOrInlineNode(block.nextSibling)) && this.tree.hasMixedContents(block.parentNode)) {
|
||||
// @WORKAROUND:
|
||||
// IE에서는 Block과 Inline/Text가 인접한 경우 getCurrentElement 등이 오작동한다.
|
||||
// 따라서 현재 Block 주변까지 한번에 잡아주어야 한다.
|
||||
this.wrapAllInlineOrTextNodesAs("P", block.parentNode, true);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
//////
|
||||
// Commands
|
||||
execCommand: function(commandId, param) {
|
||||
return this.getDoc().execCommand(commandId, false, param);
|
||||
},
|
||||
|
||||
applyBackgroundColor: function(color) {
|
||||
this.execCommand("BackColor", color);
|
||||
},
|
||||
|
||||
applyEmphasis: function() {
|
||||
// Generate <i> tag. It will be replaced with <emphasis> tag during cleanup phase.
|
||||
this.execCommand("Italic");
|
||||
},
|
||||
applyStrongEmphasis: function() {
|
||||
// Generate <b> tag. It will be replaced with <strong> tag during cleanup phase.
|
||||
this.execCommand("Bold");
|
||||
},
|
||||
applyStrike: function() {
|
||||
// Generate <strike> tag. It will be replaced with <style class="strike"> tag during cleanup phase.
|
||||
this.execCommand("strikethrough");
|
||||
},
|
||||
applyUnderline: function() {
|
||||
// Generate <u> tag. It will be replaced with <em class="underline"> tag during cleanup phase.
|
||||
this.execCommand("underline");
|
||||
},
|
||||
applyRemoveFormat: function() {
|
||||
this.execCommand("RemoveFormat");
|
||||
},
|
||||
applyRemoveLink: function() {
|
||||
this.execCommand("Unlink");
|
||||
},
|
||||
|
||||
|
||||
|
||||
//////
|
||||
// Focus/Caret/Selection
|
||||
|
||||
focus: function() {
|
||||
this.getWin().focus();
|
||||
},
|
||||
|
||||
sel: function() {
|
||||
return this.getDoc().selection;
|
||||
},
|
||||
|
||||
crng: function() {
|
||||
return this.getDoc().body.createControlRange();
|
||||
},
|
||||
|
||||
rng: function() {
|
||||
try {
|
||||
var sel = this.sel();
|
||||
return (sel === null) ? null : sel.createRange();
|
||||
} catch(ignored) {
|
||||
// IE often fails
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
hasSelection: function() {
|
||||
var selectionType = this.sel().type.toLowerCase();
|
||||
if("none" === selectionType) return false;
|
||||
if("text" === selectionType && this.getSelectionAsHtml().length === 0) return false;
|
||||
return true;
|
||||
},
|
||||
|
||||
deleteSelection: function() {
|
||||
if(this.getSelectionAsText() !== "") this.sel().clear();
|
||||
},
|
||||
|
||||
placeCaretAtStartOf: function(element) {
|
||||
// If there's no empty span, caret sometimes moves into a previous node.
|
||||
var ph = this.insertNodeAt(this.createElement("SPAN"), element, "start");
|
||||
this.selectElement(ph);
|
||||
this.collapseSelection(false);
|
||||
this.deleteNode(ph);
|
||||
},
|
||||
|
||||
selectElement: function(element, entireElement, forceTextSelection) {
|
||||
if(!element) throw "[element] is null";
|
||||
if(element.nodeType !== 1) throw "[element] is not an element";
|
||||
|
||||
var rng = null;
|
||||
if(!forceTextSelection && this.tree.isAtomic(element)) {
|
||||
rng = this.crng();
|
||||
rng.addElement(element);
|
||||
} else {
|
||||
var rng = this.rng();
|
||||
rng.moveToElementText(element);
|
||||
}
|
||||
rng.select();
|
||||
},
|
||||
|
||||
selectBlocksBetween: function(start, end) {
|
||||
var rng = this.rng();
|
||||
var rngTemp = this.rng();
|
||||
|
||||
rngTemp.moveToElementText(start);
|
||||
rng.setEndPoint("StartToStart", rngTemp);
|
||||
|
||||
rngTemp.moveToElementText(end);
|
||||
rng.setEndPoint("EndToEnd", rngTemp);
|
||||
|
||||
rng.select();
|
||||
},
|
||||
|
||||
collapseSelection: function(toStart) {
|
||||
if(this.sel().type.toLowerCase() === "control") {
|
||||
var curElement = this.getCurrentElement();
|
||||
this.sel().empty();
|
||||
this.selectElement(curElement, false, true);
|
||||
}
|
||||
var rng = this.rng();
|
||||
rng.collapse(toStart);
|
||||
rng.select();
|
||||
},
|
||||
|
||||
getSelectionAsHtml: function() {
|
||||
var rng = this.rng()
|
||||
return rng && rng.htmlText ? rng.htmlText : ""
|
||||
},
|
||||
|
||||
getSelectionAsText: function() {
|
||||
var rng = this.rng();
|
||||
return rng && rng.text ? rng.text : "";
|
||||
},
|
||||
|
||||
hasImportantAttributes: function(element) {
|
||||
return !!(element.id || element.className || element.style.cssText);
|
||||
},
|
||||
|
||||
isEmptyBlock: function(element) {
|
||||
if(!element.hasChildNodes()) return true;
|
||||
if(element.nodeType === 3 && !element.nodeValue) return true;
|
||||
if([" ", " ", ""].indexOf(element.innerHTML) !== -1) return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getLastChild: function(element) {
|
||||
if(!element || !element.hasChildNodes()) return null;
|
||||
|
||||
var nodes = xq.$A(element.childNodes).reverse();
|
||||
|
||||
for(var i = 0; i < nodes.length; i++) {
|
||||
if(nodes[i].nodeType !== 3 || nodes[i].nodeValue.length !== 0) return nodes[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getCurrentElement: function() {
|
||||
if(this.sel().type.toLowerCase() === "control") return this.rng().item(0);
|
||||
|
||||
var rng = this.rng();
|
||||
if(!rng) return false;
|
||||
|
||||
var element = rng.parentElement();
|
||||
if(element.nodeName == "BODY" && this.hasSelection()) return null;
|
||||
return element;
|
||||
},
|
||||
|
||||
getBlockElementAtSelectionStart: function() {
|
||||
var rng = this.rng();
|
||||
var dup = rng.duplicate();
|
||||
dup.collapse(true);
|
||||
|
||||
var result = this.getParentBlockElementOf(dup.parentElement());
|
||||
if(result.nodeName === "BODY") result = result.firstChild;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getBlockElementAtSelectionEnd: function() {
|
||||
var rng = this.rng();
|
||||
var dup = rng.duplicate();
|
||||
dup.collapse(false);
|
||||
|
||||
var result = this.getParentBlockElementOf(dup.parentElement());
|
||||
if(result.nodeName === "BODY") result = result.lastChild;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getBlockElementsAtSelectionEdge: function(naturalOrder, ignoreEmptyEdges) {
|
||||
return [
|
||||
this.getBlockElementAtSelectionStart(),
|
||||
this.getBlockElementAtSelectionEnd()
|
||||
];
|
||||
},
|
||||
|
||||
isCaretAtBlockEnd: function() {
|
||||
if(this.isCaretAtEmptyBlock()) return true;
|
||||
if(this.hasSelection()) return false;
|
||||
|
||||
var node = this.getCurrentBlockElement();
|
||||
var marker = this.pushMarker();
|
||||
var isTrue = false;
|
||||
while (node = this.getLastChild(node)) {
|
||||
var nodeValue = node.nodeValue;
|
||||
|
||||
if (node === marker) {
|
||||
isTrue = true;
|
||||
break;
|
||||
} else if(
|
||||
node.nodeType === 3 &&
|
||||
node.previousSibling === marker &&
|
||||
(nodeValue === " " || (nodeValue.length === 1 && nodeValue.charCodeAt(0) === 160))
|
||||
) {
|
||||
isTrue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.popMarker();
|
||||
return isTrue;
|
||||
},
|
||||
|
||||
saveSelection: function() {
|
||||
return this.rng();
|
||||
},
|
||||
|
||||
restoreSelection: function(bookmark) {
|
||||
bookmark.select();
|
||||
}
|
||||
});
|
||||
375
modules/editor/skins/xquared/javascripts/rdom/W3.js
Normal file
375
modules/editor/skins/xquared/javascripts/rdom/W3.js
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
/**
|
||||
* Base for W3C Standard Engine
|
||||
*
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/Base.js
|
||||
*/
|
||||
xq.rdom.W3 = xq.Class(xq.rdom.Base,
|
||||
/**
|
||||
* @name xq.rdom.W3
|
||||
* @lends xq.rdom.W3.prototype
|
||||
* @extends xq.rdom.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
insertNode: function(node) {
|
||||
var rng = this.rng();
|
||||
|
||||
if(!rng) {
|
||||
this.getRoot().appendChild(node);
|
||||
} else {
|
||||
rng.insertNode(node);
|
||||
rng.selectNode(node);
|
||||
rng.collapse(false);
|
||||
}
|
||||
return node;
|
||||
},
|
||||
|
||||
removeTrailingWhitespace: function(block) {
|
||||
// TODO: do nothing
|
||||
},
|
||||
|
||||
getOuterHTML: function(element) {
|
||||
var div = element.ownerDocument.createElement("div");
|
||||
div.appendChild(element.cloneNode(true));
|
||||
return div.innerHTML;
|
||||
},
|
||||
|
||||
correctEmptyElement: function(element) {
|
||||
if(!element || element.nodeType !== 1 || this.tree.isAtomic(element)) return;
|
||||
|
||||
if(element.firstChild)
|
||||
this.correctEmptyElement(element.firstChild);
|
||||
else
|
||||
element.appendChild(this.makePlaceHolder());
|
||||
},
|
||||
|
||||
correctParagraph: function() {
|
||||
if(this.hasSelection()) return false;
|
||||
|
||||
var block = this.getCurrentBlockElement();
|
||||
var modified = false;
|
||||
|
||||
if(!block) {
|
||||
try {
|
||||
this.execCommand("InsertParagraph");
|
||||
modified = true;
|
||||
} catch(ignored) {}
|
||||
} else if(this.tree.isBlockOnlyContainer(block)) {
|
||||
this.execCommand("InsertParagraph");
|
||||
|
||||
// check for HR
|
||||
var newBlock = this.getCurrentElement();
|
||||
|
||||
if(this.tree.isAtomic(newBlock.previousSibling) && newBlock.previousSibling.nodeName === "HR") {
|
||||
var nextBlock = this.tree.findForward(
|
||||
newBlock,
|
||||
function(node) {return this.tree.isBlock(node) && !this.tree.isBlockOnlyContainer(node)}.bind(this)
|
||||
);
|
||||
if(nextBlock) {
|
||||
this.deleteNode(newBlock);
|
||||
this.placeCaretAtStartOf(nextBlock);
|
||||
}
|
||||
}
|
||||
modified = true;
|
||||
} else if(this.tree.hasMixedContents(block)) {
|
||||
this.wrapAllInlineOrTextNodesAs("P", block, true);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// insert placeholder - part 1
|
||||
block = this.getCurrentBlockElement();
|
||||
if(this.tree.isBlock(block) && !this._hasPlaceHolderAtEnd(block)) {
|
||||
block.appendChild(this.makePlaceHolder());
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// insert placeholder - part 2
|
||||
if(this.tree.isBlock(block)) {
|
||||
var parentsLastChild = block.parentNode.lastChild;
|
||||
if(this.isPlaceHolder(parentsLastChild)) {
|
||||
this.deleteNode(parentsLastChild);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// remove empty elements
|
||||
if(this.tree.isBlock(block)) {
|
||||
var nodes = block.childNodes;
|
||||
for(var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if(node.nodeType === 1 && !this.tree.isAtomic(node) && !node.hasChildNodes() && !this.isPlaceHolder(node)) {
|
||||
this.deleteNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modified;
|
||||
},
|
||||
|
||||
_hasPlaceHolderAtEnd: function(block) {
|
||||
if(!block.hasChildNodes()) return false;
|
||||
return this.isPlaceHolder(block.lastChild) || this._hasPlaceHolderAtEnd(block.lastChild);
|
||||
},
|
||||
|
||||
applyBackgroundColor: function(color) {
|
||||
this.execCommand("styleWithCSS", "true");
|
||||
this.execCommand("hilitecolor", color);
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
|
||||
// 0. Save current selection
|
||||
var bookmark = this.saveSelection();
|
||||
|
||||
// 1. Get selected blocks
|
||||
var blocks = this.getSelectedBlockElements();
|
||||
if(blocks.length === 0) return;
|
||||
|
||||
// 2. Apply background-color to all adjust inline elements
|
||||
// 3. Remove background-color from blocks
|
||||
for(var i = 0; i < blocks.length; i++) {
|
||||
if((i === 0 || i === blocks.length-1) && !blocks[i].style.backgroundColor) continue;
|
||||
|
||||
var spans = this.wrapAllInlineOrTextNodesAs("SPAN", blocks[i], true);
|
||||
for(var j = 0; j < spans.length; j++) {
|
||||
spans[j].style.backgroundColor = color;
|
||||
}
|
||||
blocks[i].style.backgroundColor = "";
|
||||
}
|
||||
|
||||
// 4. Restore selection
|
||||
this.restoreSelection(bookmark);
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
//////
|
||||
// Commands
|
||||
execCommand: function(commandId, param) {
|
||||
return this.getDoc().execCommand(commandId, false, param || null);
|
||||
},
|
||||
|
||||
applyRemoveFormat: function() {
|
||||
this.execCommand("RemoveFormat");
|
||||
},
|
||||
applyRemoveLink: function() {
|
||||
this.execCommand("Unlink");
|
||||
},
|
||||
applyEmphasis: function() {
|
||||
// Generate <i> tag. It will be replaced with <emphasis> tag during cleanup phase.
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
this.execCommand("italic");
|
||||
},
|
||||
applyStrongEmphasis: function() {
|
||||
// Generate <b> tag. It will be replaced with <strong> tag during cleanup phase.
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
this.execCommand("bold");
|
||||
},
|
||||
applyStrike: function() {
|
||||
// Generate <strike> tag. It will be replaced with <style class="strike"> tag during cleanup phase.
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
this.execCommand("strikethrough");
|
||||
},
|
||||
applyUnderline: function() {
|
||||
// Generate <u> tag. It will be replaced with <em class="underline"> tag during cleanup phase.
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
this.execCommand("underline");
|
||||
},
|
||||
|
||||
|
||||
|
||||
//////
|
||||
// Focus/Caret/Selection
|
||||
|
||||
focus: function() {
|
||||
this.getWin().focus();
|
||||
},
|
||||
|
||||
sel: function() {
|
||||
return this.getWin().getSelection();
|
||||
},
|
||||
|
||||
rng: function() {
|
||||
var sel = this.sel();
|
||||
return (sel === null || sel.rangeCount === 0) ? null : sel.getRangeAt(0);
|
||||
},
|
||||
|
||||
saveSelection: function() {
|
||||
var rng = this.rng();
|
||||
return [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];
|
||||
},
|
||||
|
||||
restoreSelection: function(bookmark) {
|
||||
var rng = this.rng();
|
||||
rng.setStart(bookmark[0], bookmark[1]);
|
||||
rng.setEnd(bookmark[2], bookmark[3]);
|
||||
},
|
||||
|
||||
hasSelection: function() {
|
||||
var sel = this.sel();
|
||||
return sel && !sel.isCollapsed;
|
||||
},
|
||||
|
||||
deleteSelection: function() {
|
||||
this.rng().deleteContents();
|
||||
this.sel().collapseToStart();
|
||||
},
|
||||
|
||||
selectElement: function(element, entireElement) {throw "Not implemented yet"},
|
||||
|
||||
selectBlocksBetween: function(start, end) {
|
||||
// @WORKAROUND: required to avoid FF selection bug.
|
||||
try {
|
||||
if(!xq.Browser.isMac) this.getDoc().execCommand("SelectAll", false, null);
|
||||
} catch(ignored) {}
|
||||
|
||||
var rng = this.rng();
|
||||
rng.setStart(start.firstChild, 0);
|
||||
rng.setEnd(end, end.childNodes.length);
|
||||
},
|
||||
|
||||
collapseSelection: function(toStart) {
|
||||
var rng = this.rng();
|
||||
if(rng) rng.collapse(toStart);
|
||||
},
|
||||
|
||||
placeCaretAtStartOf: function(element) {
|
||||
while(this.tree.isBlock(element.firstChild)) {
|
||||
element = element.firstChild;
|
||||
}
|
||||
this.selectElement(element, false);
|
||||
this.collapseSelection(true);
|
||||
},
|
||||
|
||||
placeCaretAtEndOf: function(element) {
|
||||
while(this.tree.isBlock(element.lastChild)) {
|
||||
element = element.lastChild;
|
||||
}
|
||||
this.selectElement(element, false);
|
||||
this.collapseSelection(false);
|
||||
},
|
||||
|
||||
getSelectionAsHtml: function() {
|
||||
var container = document.createElement("div");
|
||||
container.appendChild(this.rng().cloneContents());
|
||||
return container.innerHTML;
|
||||
},
|
||||
|
||||
getSelectionAsText: function() {
|
||||
return this.rng().toString()
|
||||
},
|
||||
|
||||
hasImportantAttributes: function(element) {
|
||||
return !!(element.id || element.className || element.style.cssText);
|
||||
},
|
||||
|
||||
isEmptyBlock: function(element) {
|
||||
if(!element.hasChildNodes()) return true;
|
||||
var children = element.childNodes;
|
||||
for(var i = 0; i < children.length; i++) {
|
||||
if(!this.isPlaceHolder(children[i]) && !this.isEmptyTextNode(children[i])) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
getLastChild: function(element) {
|
||||
if(!element || !element.hasChildNodes()) return null;
|
||||
|
||||
var nodes = xq.$A(element.childNodes).reverse();
|
||||
|
||||
for(var i = 0; i < nodes.length; i++) {
|
||||
if(!this.isPlaceHolder(nodes[i]) && !this.isEmptyTextNode(nodes[i])) return nodes[i];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getCurrentElement: function() {
|
||||
var rng = this.rng();
|
||||
if(!rng) return null;
|
||||
|
||||
var container = rng.startContainer;
|
||||
|
||||
if(container.nodeType === 3) {
|
||||
return container.parentNode;
|
||||
} else if(this.tree.isBlockOnlyContainer(container)) {
|
||||
return container.childNodes[rng.startOffset];
|
||||
} else {
|
||||
return container;
|
||||
}
|
||||
},
|
||||
|
||||
getBlockElementsAtSelectionEdge: function(naturalOrder, ignoreEmptyEdges) {
|
||||
var start = this.getBlockElementAtSelectionStart();
|
||||
var end = this.getBlockElementAtSelectionEnd();
|
||||
|
||||
var reversed = false;
|
||||
|
||||
if(naturalOrder && start !== end && this.tree.checkTargetBackward(start, end)) {
|
||||
var temp = start;
|
||||
start = end;
|
||||
end = temp;
|
||||
|
||||
reversed = true;
|
||||
}
|
||||
|
||||
if(ignoreEmptyEdges && start !== end) {
|
||||
// @TODO: Firefox sometimes selects one more block.
|
||||
/*
|
||||
|
||||
var sel = this.sel();
|
||||
if(reversed) {
|
||||
if(sel.focusNode.nodeType === 1) start = start.nextSibling;
|
||||
if(sel.anchorNode.nodeType === 3 && sel.focusOffset === 0) end = end.previousSibling;
|
||||
} else {
|
||||
if(sel.anchorNode.nodeType === 1) start = start.nextSibling;
|
||||
if(sel.focusNode.nodeType === 3 && sel.focusOffset === 0) end = end.previousSibling;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return [start, end];
|
||||
},
|
||||
|
||||
isCaretAtBlockEnd: function() {
|
||||
if(this.isCaretAtEmptyBlock()) return true;
|
||||
if(this.hasSelection()) return false;
|
||||
|
||||
var node = this.getCurrentBlockElement();
|
||||
var marker = this.pushMarker();
|
||||
|
||||
var isTrue = false;
|
||||
while (node = this.getLastChild(node)) {
|
||||
var nodeValue = node.nodeValue;
|
||||
|
||||
if (node === marker) {
|
||||
isTrue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.popMarker();
|
||||
return isTrue;
|
||||
},
|
||||
|
||||
getBlockElementAtSelectionStart: function() {
|
||||
var block = this.getParentBlockElementOf(this.sel().anchorNode);
|
||||
|
||||
// find bottom-most first block child
|
||||
while(this.tree.isBlockContainer(block) && block.firstChild && this.tree.isBlock(block.firstChild)) {
|
||||
block = block.firstChild;
|
||||
}
|
||||
|
||||
return block;
|
||||
},
|
||||
|
||||
getBlockElementAtSelectionEnd: function() {
|
||||
var block = this.getParentBlockElementOf(this.sel().focusNode);
|
||||
|
||||
// find bottom-most last block child
|
||||
while(this.tree.isBlockContainer(block) && block.lastChild && this.tree.isBlock(block.lastChild)) {
|
||||
block = block.lastChild;
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
});
|
||||
63
modules/editor/skins/xquared/javascripts/rdom/Webkit.js
Normal file
63
modules/editor/skins/xquared/javascripts/rdom/Webkit.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/W3.js
|
||||
*/
|
||||
xq.rdom.Webkit = xq.Class(xq.rdom.W3,
|
||||
/**
|
||||
* @name xq.rdom.Webkit
|
||||
* @lends xq.rdom.Webkit.prototype
|
||||
* @extends xq.rdom.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
makePlaceHolder: function() {
|
||||
var holder = this.createElement("BR");
|
||||
holder.className = "webkit-block-placeholder";
|
||||
return holder;
|
||||
},
|
||||
|
||||
makePlaceHolderString: function() {
|
||||
return '<br class="webkit-block-placeholder" />';
|
||||
},
|
||||
|
||||
makeEmptyParagraph: function() {
|
||||
return this.createElementFromHtml('<p><br class="webkit-block-placeholder" /></p>');
|
||||
},
|
||||
|
||||
isPlaceHolder: function(node) {
|
||||
return node.className === "webkit-block-placeholder";
|
||||
},
|
||||
|
||||
selectElement: function(element, entireElement) {
|
||||
if(!element) throw "[element] is null";
|
||||
if(element.nodeType !== 1) throw "[element] is not an element";
|
||||
|
||||
var rng = this.rng() || this.getDoc().createRange();
|
||||
if(entireElement) {
|
||||
rng.selectNode(element);
|
||||
} else {
|
||||
rng.selectNodeContents(element);
|
||||
}
|
||||
|
||||
this._setSelectionByRange(rng);
|
||||
},
|
||||
|
||||
getSelectionAsHtml: function() {
|
||||
var container = this.createElement("div");
|
||||
var rng = this.rng();
|
||||
var contents = this.rng().cloneContents();
|
||||
if(contents) container.appendChild(contents);
|
||||
return container.innerHTML;
|
||||
},
|
||||
|
||||
collapseSelection: function(toStart) {
|
||||
var rng = this.rng();
|
||||
rng.collapse(toStart);
|
||||
this._setSelectionByRange(rng);
|
||||
},
|
||||
|
||||
_setSelectionByRange: function(rng) {
|
||||
var sel = this.sel();
|
||||
sel.setBaseAndExtent(rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset);
|
||||
}
|
||||
});
|
||||
6
modules/editor/skins/xquared/javascripts/ui/Base.js
Normal file
6
modules/editor/skins/xquared/javascripts/ui/Base.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* @namespace UI Controls
|
||||
*
|
||||
* @requires Xquared.js
|
||||
*/
|
||||
xq.ui = {};
|
||||
404
modules/editor/skins/xquared/javascripts/ui/Control.js
Normal file
404
modules/editor/skins/xquared/javascripts/ui/Control.js
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
/**
|
||||
* @namespace UI Controls
|
||||
*
|
||||
* @requires Xquared.js
|
||||
* @requires ui/Base.js
|
||||
*/
|
||||
xq.ui.FormDialog = xq.Class(/** @lends xq.ui.FormDialog.prototype */ {
|
||||
/**
|
||||
* Displays given HTML form as a dialog.
|
||||
*
|
||||
* @constructs
|
||||
* @param {xq.Editor} xed Dialog owner.
|
||||
* @param {String} html HTML string which contains FORM.
|
||||
* @param {Function} [onLoadHandler] callback function to be called when the form is loaded.
|
||||
* @param {Function} [onCloseHandler] callback function to be called when the form is closed.
|
||||
*/
|
||||
initialize: function(xed, html, onLoadHandler, onCloseHandler) {
|
||||
xq.addToFinalizeQueue(this);
|
||||
|
||||
this.xed = xed;
|
||||
this.html = html;
|
||||
this.onLoadHandler = onLoadHandler || function() {};
|
||||
this.onCloseHandler = onCloseHandler || function() {};
|
||||
this.form = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows dialog
|
||||
*
|
||||
* @param {Object} [options] collection of options
|
||||
*/
|
||||
show: function(options) {
|
||||
options = options || {};
|
||||
options.position = options.position || 'centerOfWindow';
|
||||
options.mode = options.mode || 'modal';
|
||||
options.cancelOnEsc = options.cancelOnEsc || true;
|
||||
|
||||
var self = this;
|
||||
|
||||
// create and append container
|
||||
var container = document.createElement('DIV');
|
||||
container.style.display = 'none';
|
||||
document.body.appendChild(container);
|
||||
|
||||
// initialize form
|
||||
container.innerHTML = this.html;
|
||||
this.form = container.getElementsByTagName('FORM')[0];
|
||||
|
||||
this.form.onsubmit = function() {
|
||||
self.onCloseHandler(xq.serializeForm(this));
|
||||
self.close();
|
||||
return false;
|
||||
};
|
||||
|
||||
var cancelButton = xq.getElementsByClassName(this.form, 'cancel')[0];
|
||||
cancelButton.onclick = function() {
|
||||
self.onCloseHandler();
|
||||
self.close();
|
||||
};
|
||||
|
||||
if(options.mode === 'modal') {
|
||||
this.dimmed = document.createElement('DIV');
|
||||
this.dimmed.style.position = 'absolute';
|
||||
this.dimmed.style.backgroundColor = 'black';
|
||||
this.dimmed.style.opacity = 0.5;
|
||||
this.dimmed.style.filter = 'alpha(opacity=50)';
|
||||
this.dimmed.style.zIndex=902;
|
||||
this.dimmed.style.top='0px';
|
||||
this.dimmed.style.left='0px';
|
||||
document.body.appendChild(this.dimmed);
|
||||
|
||||
this.resizeDimmedDiv = function(e) {
|
||||
this.dimmed.style.display='none';
|
||||
this.dimmed.style.width=document.documentElement.scrollWidth+'px';
|
||||
this.dimmed.style.height=document.documentElement.scrollHeight+'px';
|
||||
this.dimmed.style.display='block';
|
||||
}.bind(this);
|
||||
|
||||
xq.observe(window, 'resize', this.resizeDimmedDiv);
|
||||
|
||||
this.resizeDimmedDiv();
|
||||
}
|
||||
|
||||
// append dialog
|
||||
document.body.appendChild(this.form);
|
||||
container.parentNode.removeChild(container);
|
||||
|
||||
// place dialog to center of window
|
||||
this.setPosition(options.position);
|
||||
|
||||
// give focus
|
||||
var elementToFocus = xq.getElementsByClassName(this.form, 'initialFocus');
|
||||
if(elementToFocus.length > 0) elementToFocus[0].focus();
|
||||
|
||||
// handle cancelOnEsc option
|
||||
if(options.cancelOnEsc) {
|
||||
xq.observe(this.form, 'keydown', function(e) {
|
||||
if(e.keyCode === 27) {
|
||||
this.onCloseHandler();
|
||||
this.close();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this.onLoadHandler(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes dialog
|
||||
*/
|
||||
close: function() {
|
||||
this.form.parentNode.removeChild(this.form);
|
||||
|
||||
if(this.dimmed) {
|
||||
this.dimmed.parentNode.removeChild(this.dimmed);
|
||||
this.dimmed = null;
|
||||
xq.stopObserving(window, 'resize', this.resizeDimmedDiv);
|
||||
this.resizeDimmedDiv = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets position of dialog
|
||||
*
|
||||
* @param {String} target "centerOfWindow" or "centerOfEditor"
|
||||
*/
|
||||
setPosition: function(target) {
|
||||
var targetElement = null;
|
||||
var left = 0;
|
||||
var top = 0;
|
||||
|
||||
if(target === 'centerOfWindow') {
|
||||
targetElement = document.documentElement;
|
||||
left += targetElement.scrollLeft;
|
||||
top += targetElement.scrollTop;
|
||||
} else if(target === 'centerOfEditor') {
|
||||
targetElement = this.xed.getCurrentEditMode() == 'wysiwyg' ? this.xed.wysiwygEditorDiv : this.xed.sourceEditorDiv;
|
||||
var o = targetElement;
|
||||
do {
|
||||
left += o.offsetLeft;
|
||||
top += o.offsetTop;
|
||||
} while(o = o.offsetParent)
|
||||
} else if(target === 'nearbyCaret') {
|
||||
throw "Not implemented yet";
|
||||
} else {
|
||||
throw "Invalid argument: " + target;
|
||||
}
|
||||
|
||||
var targetWidth = targetElement.clientWidth;
|
||||
var targetHeight = targetElement.clientHeight;
|
||||
var dialogWidth = this.form.clientWidth;
|
||||
var dialogHeight = this.form.clientHeight;
|
||||
|
||||
left += parseInt((targetWidth - dialogWidth) / 2);
|
||||
top += parseInt((targetHeight - dialogHeight) / 2);
|
||||
|
||||
this.form.style.left = left + "px";
|
||||
this.form.style.top = top + "px";
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
xq.ui.QuickSearchDialog = xq.Class(/** @lends xq.ui.QuickSearchDialog.prototype */ {
|
||||
/**
|
||||
* Displays quick search dialog
|
||||
*
|
||||
* @constructs
|
||||
* @param {xq.Editor} xed Dialog owner.
|
||||
* @param {Object} param Parameters.
|
||||
*/
|
||||
initialize: function(xed, param) {
|
||||
xq.addToFinalizeQueue(this);
|
||||
this.xed = xed;
|
||||
|
||||
this.rdom = xq.rdom.Base.createInstance();
|
||||
|
||||
this.param = param;
|
||||
if(!this.param.renderItem) this.param.renderItem = function(item) {
|
||||
return this.rdom.getInnerText(item);
|
||||
}.bind(this);
|
||||
|
||||
this.container = null;
|
||||
},
|
||||
|
||||
getQuery: function() {
|
||||
if(!this.container) return "";
|
||||
return this._getInputField().value;
|
||||
},
|
||||
|
||||
onSubmit: function(e) {
|
||||
if(this.matchCount() > 0) {
|
||||
this.param.onSelect(this.xed, this.list[this._getSelectedIndex()]);
|
||||
}
|
||||
|
||||
this.close();
|
||||
xq.stopEvent(e);
|
||||
return false;
|
||||
},
|
||||
|
||||
onCancel: function(e) {
|
||||
if(this.param.onCancel) this.param.onCancel(this.xed);
|
||||
this.close();
|
||||
},
|
||||
|
||||
onBlur: function(e) {
|
||||
// @WORKAROUND: Ugly
|
||||
setTimeout(function() {this.onCancel(e)}.bind(this), 400);
|
||||
},
|
||||
|
||||
onKey: function(e) {
|
||||
var esc = new xq.Shortcut("ESC");
|
||||
var enter = new xq.Shortcut("ENTER");
|
||||
var up = new xq.Shortcut("UP");
|
||||
var down = new xq.Shortcut("DOWN");
|
||||
|
||||
if(esc.matches(e)) {
|
||||
this.onCancel(e);
|
||||
} else if(enter.matches(e)) {
|
||||
this.onSubmit(e);
|
||||
} else if(up.matches(e)) {
|
||||
this._moveSelectionUp();
|
||||
} else if(down.matches(e)) {
|
||||
this._moveSelectionDown();
|
||||
} else {
|
||||
this.updateList();
|
||||
}
|
||||
},
|
||||
|
||||
onClick: function(e) {
|
||||
var target = e.srcElement || e.target;
|
||||
if(target.nodeName === "LI") {
|
||||
|
||||
var index = this._getIndexOfLI(target);
|
||||
this.param.onSelect(this.xed, this.list[index]);
|
||||
}
|
||||
},
|
||||
|
||||
onList: function(list) {
|
||||
this.list = list;
|
||||
this.renderList(list);
|
||||
},
|
||||
|
||||
updateList: function() {
|
||||
window.setTimeout(function() {
|
||||
this.param.listProvider(this.getQuery(), this.xed, this.onList.bind(this));
|
||||
}.bind(this), 0);
|
||||
},
|
||||
|
||||
renderList: function(list)
|
||||
{
|
||||
var ol = this._getListContainer();
|
||||
ol.innerHTML = "";
|
||||
|
||||
for(var i = 0; i < list.length; i++) {
|
||||
var li = this.rdom.createElement('LI');
|
||||
li.innerHTML = this.param.renderItem(list[i]);
|
||||
ol.appendChild(li);
|
||||
}
|
||||
|
||||
if(ol.hasChildNodes()) {
|
||||
ol.firstChild.className = "selected";
|
||||
}
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if(!this.container) this.container = this._create();
|
||||
|
||||
var dialog = this.rdom.insertNodeAt(this.container, this.rdom.getRoot(), "end");
|
||||
this.setPosition('centerOfEditor');
|
||||
this.updateList();
|
||||
this.focus();
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.rdom.deleteNode(this.container);
|
||||
},
|
||||
|
||||
focus: function() {
|
||||
this._getInputField().focus();
|
||||
},
|
||||
|
||||
setPosition: function(target) {
|
||||
var targetElement = null;
|
||||
var left = 0;
|
||||
var top = 0;
|
||||
|
||||
if(target === 'centerOfWindow') {
|
||||
left += targetElement.scrollLeft;
|
||||
top += targetElement.scrollTop;
|
||||
targetElement = document.documentElement;
|
||||
} else if(target === 'centerOfEditor') {
|
||||
targetElement = this.xed.getCurrentEditMode() == 'wysiwyg' ? this.xed.wysiwygEditorDiv : this.xed.sourceEditorDiv;
|
||||
var o = targetElement;
|
||||
do {
|
||||
left += o.offsetLeft;
|
||||
top += o.offsetTop;
|
||||
} while(o = o.offsetParent)
|
||||
} else if(target === 'nearbyCaret') {
|
||||
throw "Not implemented yet";
|
||||
} else {
|
||||
throw "Invalid argument: " + target;
|
||||
}
|
||||
|
||||
var targetWidth = targetElement.clientWidth;
|
||||
var targetHeight = targetElement.clientHeight;
|
||||
var dialogWidth = this.container.clientWidth;
|
||||
var dialogHeight = this.container.clientHeight;
|
||||
|
||||
left += parseInt((targetWidth - dialogWidth) / 2);
|
||||
top += parseInt((targetHeight - dialogHeight) / 2);
|
||||
|
||||
this.container.style.left = left + "px";
|
||||
this.container.style.top = top + "px";
|
||||
},
|
||||
|
||||
matchCount: function() {
|
||||
return this.list ? this.list.length : 0;
|
||||
},
|
||||
|
||||
_create: function() {
|
||||
// make container
|
||||
var container = this.rdom.createElement("DIV");
|
||||
container.className = "xqQuickSearch";
|
||||
|
||||
// make title
|
||||
if(this.param.title) {
|
||||
var title = this.rdom.createElement("H1");
|
||||
title.innerHTML = this.param.title;
|
||||
container.appendChild(title);
|
||||
}
|
||||
|
||||
// make input field
|
||||
var inputWrapper = this.rdom.createElement("DIV");
|
||||
inputWrapper.className = "input";
|
||||
var form = this.rdom.createElement("FORM");
|
||||
var input = this.rdom.createElement("INPUT");
|
||||
input.type = "text";
|
||||
input.value = "";
|
||||
form.appendChild(input);
|
||||
inputWrapper.appendChild(form);
|
||||
container.appendChild(inputWrapper);
|
||||
|
||||
// make list
|
||||
var list = this.rdom.createElement("OL");
|
||||
|
||||
xq.observe(input, 'blur', this.onBlur.bindAsEventListener(this));
|
||||
xq.observe(input, 'keypress', this.onKey.bindAsEventListener(this));
|
||||
xq.observe(list, 'click', this.onClick.bindAsEventListener(this), true);
|
||||
xq.observe(form, 'submit', this.onSubmit.bindAsEventListener(this));
|
||||
xq.observe(form, 'reset', this.onCancel.bindAsEventListener(this));
|
||||
|
||||
container.appendChild(list);
|
||||
return container;
|
||||
},
|
||||
|
||||
_getInputField: function() {
|
||||
return this.container.getElementsByTagName('INPUT')[0];
|
||||
},
|
||||
|
||||
_getListContainer: function() {
|
||||
return this.container.getElementsByTagName('OL')[0];
|
||||
},
|
||||
|
||||
_getSelectedIndex: function() {
|
||||
var ol = this._getListContainer();
|
||||
for(var i = 0; i < ol.childNodes.length; i++) {
|
||||
if(ol.childNodes[i].className === 'selected') return i;
|
||||
}
|
||||
},
|
||||
|
||||
_getIndexOfLI: function(li) {
|
||||
var ol = this._getListContainer();
|
||||
for(var i = 0; i < ol.childNodes.length; i++) {
|
||||
if(ol.childNodes[i] === li) return i;
|
||||
}
|
||||
},
|
||||
|
||||
_moveSelectionUp: function() {
|
||||
var count = this.matchCount();
|
||||
if(count === 0) return;
|
||||
var index = this._getSelectedIndex();
|
||||
var ol = this._getListContainer();
|
||||
ol.childNodes[index].className = "";
|
||||
|
||||
index--;
|
||||
if(index < 0) index = count - 1;
|
||||
|
||||
ol.childNodes[index].className = "selected";
|
||||
},
|
||||
|
||||
_moveSelectionDown: function() {
|
||||
var count = this.matchCount();
|
||||
if(count === 0) return;
|
||||
var index = this._getSelectedIndex();
|
||||
var ol = this._getListContainer();
|
||||
ol.childNodes[index].className = "";
|
||||
|
||||
index++;
|
||||
if(index >= count) index = 0;
|
||||
|
||||
ol.childNodes[index].className = "selected";
|
||||
}
|
||||
});
|
||||
312
modules/editor/skins/xquared/javascripts/ui/Toolbar.js
Normal file
312
modules/editor/skins/xquared/javascripts/ui/Toolbar.js
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Browser.js
|
||||
* @requires ui/Base.js
|
||||
*/
|
||||
xq.ui.Toolbar = xq.Class(/** @lends xq.ui.Toolbar.prototype */{
|
||||
/**
|
||||
* TODO: Add description
|
||||
*
|
||||
* @constructs
|
||||
*/
|
||||
initialize: function(xed, container, wrapper, buttonMap, imagePath, structureAndStyleCollector) {
|
||||
xq.addToFinalizeQueue(this);
|
||||
|
||||
this.xed = xed;
|
||||
|
||||
if(typeof container === 'string') {
|
||||
container = xq.$(container);
|
||||
}
|
||||
if(container && container.nodeType !== 1) {
|
||||
throw "[container] is not an element";
|
||||
}
|
||||
|
||||
this.wrapper = wrapper;
|
||||
this.doc = this.wrapper.ownerDocument;
|
||||
this.buttonMap = buttonMap;
|
||||
this.imagePath = imagePath;
|
||||
this.structureAndStyleCollector = structureAndStyleCollector;
|
||||
|
||||
this.buttons = null;
|
||||
this.anchorsCache = [];
|
||||
this._scheduledUpdate = null;
|
||||
|
||||
if(!container) {
|
||||
this.create();
|
||||
this._addStyleRules([
|
||||
{selector:".xquared div.toolbar", rule:"background-image: url(" + imagePath + "toolbarBg.gif)"},
|
||||
{selector:".xquared ul.buttons li", rule:"background-image: url(" + imagePath + "toolbarButtonBg.gif)"},
|
||||
{selector:".xquared ul.buttons li.xq_separator", rule:"background-image: url(" + imagePath + "toolbarSeparator.gif)"}
|
||||
]);
|
||||
} else {
|
||||
this.container = container;
|
||||
}
|
||||
},
|
||||
|
||||
finalize: function() {
|
||||
for(var i = 0; i < this.anchorsCache.length; i++) {
|
||||
// TODO remove dependency to Editor
|
||||
this.anchorsCache[i].xed = null;
|
||||
this.anchorsCache[i].handler = null;
|
||||
this.anchorsCache[i] = null;
|
||||
}
|
||||
|
||||
this.toolbarAnchorsCache = null;
|
||||
},
|
||||
|
||||
triggerUpdate: function() {
|
||||
if(this._scheduledUpdate) return;
|
||||
|
||||
this._scheduledUpdate = window.setTimeout(
|
||||
function() {
|
||||
this._scheduledUpdate = null;
|
||||
var ss = this.structureAndStyleCollector();
|
||||
if(ss) this.update(ss);
|
||||
}.bind(this), 200
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates all buttons' status. Override this to customize status L&F. Don't call this function directly. Use triggerUpdate() to call it indirectly.
|
||||
*
|
||||
* @param {Object} structure and style information. see xq.rdom.Base.collectStructureAndStyle()
|
||||
*/
|
||||
update: function(info) {
|
||||
if(!this.container) return;
|
||||
if(!this.buttons) {
|
||||
var classNames = [
|
||||
"emphasis", "strongEmphasis", "underline", "strike", "superscription", "subscription",
|
||||
"justifyLeft", "justifyCenter", "justifyRight", "justifyBoth",
|
||||
"unorderedList", "orderedList", "code",
|
||||
"paragraph", "heading1", "heading2", "heading3", "heading4", "heading5", "heading6"
|
||||
];
|
||||
|
||||
this.buttons = {};
|
||||
|
||||
for(var i = 0; i < classNames.length; i++) {
|
||||
var found = xq.getElementsByClassName(this.container, classNames[i]);
|
||||
var button = found && found.length > 0 ? found[0] : null;
|
||||
if(button) this.buttons[classNames[i]] = button;
|
||||
}
|
||||
}
|
||||
|
||||
var buttons = this.buttons;
|
||||
this._updateButtonStatus('emphasis', info.em);
|
||||
this._updateButtonStatus('strongEmphasis', info.strong);
|
||||
this._updateButtonStatus('underline', info.underline);
|
||||
this._updateButtonStatus('strike', info.strike);
|
||||
this._updateButtonStatus('superscription', info.superscription);
|
||||
this._updateButtonStatus('subscription', info.subscription);
|
||||
|
||||
this._updateButtonStatus('justifyLeft', info.justification === 'left');
|
||||
this._updateButtonStatus('justifyCenter', info.justification === 'center');
|
||||
this._updateButtonStatus('justifyRight', info.justification === 'right');
|
||||
this._updateButtonStatus('justifyBoth', info.justification === 'justify');
|
||||
|
||||
this._updateButtonStatus('orderedList', info.list === 'OL');
|
||||
this._updateButtonStatus('unorderedList', info.list === 'UL');
|
||||
this._updateButtonStatus('code', info.list === 'CODE');
|
||||
|
||||
this._updateButtonStatus('paragraph', info.block === 'P');
|
||||
this._updateButtonStatus('heading1', info.block === 'H1');
|
||||
this._updateButtonStatus('heading2', info.block === 'H2');
|
||||
this._updateButtonStatus('heading3', info.block === 'H3');
|
||||
this._updateButtonStatus('heading4', info.block === 'H4');
|
||||
this._updateButtonStatus('heading5', info.block === 'H5');
|
||||
this._updateButtonStatus('heading6', info.block === 'H6');
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables all buttons
|
||||
*
|
||||
* @param {Array} [exceptions] array of string containing classnames to exclude
|
||||
*/
|
||||
enableButtons: function(exceptions) {
|
||||
if(!this.container) return;
|
||||
|
||||
this._execForAllButtons(exceptions, function(li, exception) {
|
||||
li.firstChild.className = !exception ? '' : 'disabled';
|
||||
});
|
||||
|
||||
// @WORKAROUND: Image icon disappears without following code:
|
||||
if(xq.Browser.isIE6) {
|
||||
this.container.style.display = 'none';
|
||||
setTimeout(function() {this.container.style.display = 'block';}.bind(this), 0);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables all buttons
|
||||
*
|
||||
* @param {Array} [exceptions] array of string containing classnames to exclude
|
||||
*/
|
||||
disableButtons: function(exceptions) {
|
||||
this._execForAllButtons(exceptions, function(li, exception) {
|
||||
li.firstChild.className = exception ? '' : 'disabled';
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates toolbar element
|
||||
*/
|
||||
create: function() {
|
||||
// outmost container
|
||||
this.container = this.doc.createElement('div');
|
||||
this.container.className = 'toolbar';
|
||||
|
||||
// button container
|
||||
var buttons = this.doc.createElement('ul');
|
||||
buttons.className = 'buttons';
|
||||
this.container.appendChild(buttons);
|
||||
|
||||
// Generate buttons from map and append it to button container
|
||||
for(var i = 0; i < this.buttonMap.length; i++) {
|
||||
for(var j = 0; j < this.buttonMap[i].length; j++) {
|
||||
var buttonConfig = this.buttonMap[i][j];
|
||||
|
||||
var li = this.doc.createElement('li');
|
||||
buttons.appendChild(li);
|
||||
li.className = buttonConfig.className;
|
||||
|
||||
var span = this.doc.createElement('span');
|
||||
li.appendChild(span);
|
||||
|
||||
if(buttonConfig.handler) {
|
||||
this._createButton(buttonConfig, span);
|
||||
} else {
|
||||
this._createDropdown(buttonConfig, span);
|
||||
}
|
||||
|
||||
if(j === 0 && i !== 0) li.className += ' xq_separator';
|
||||
}
|
||||
}
|
||||
|
||||
this.wrapper.appendChild(this.container);
|
||||
},
|
||||
|
||||
_createButton: function(buttonConfig, span) {
|
||||
var a = this.doc.createElement('a');
|
||||
span.appendChild(a);
|
||||
a.href = '#';
|
||||
a.title = buttonConfig.title;
|
||||
a.handler = buttonConfig.handler;
|
||||
|
||||
this.anchorsCache.push(a);
|
||||
|
||||
xq.observe(a, 'mousedown', xq.cancelHandler);
|
||||
xq.observe(a, 'click', this._clickHandler.bindAsEventListener(this));
|
||||
|
||||
var img = this.doc.createElement('img');
|
||||
a.appendChild(img);
|
||||
img.src = this.imagePath + buttonConfig.className + '.gif';
|
||||
},
|
||||
|
||||
_createDropdown: function(buttonConfig, span) {
|
||||
var select = this.doc.createElement('select');
|
||||
select.handlers = buttonConfig.list;
|
||||
var xed = this.xed;
|
||||
|
||||
xq.observe(select, 'change', function(e) {
|
||||
var src = e.target || e.srcElement;
|
||||
if(src.value === "-1") {
|
||||
src.selectedIndex = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
var handler = src.handlers[src.value].handler;
|
||||
xed.focus();
|
||||
var stop = (typeof handler === "function") ? handler(this) : eval(handler);
|
||||
src.selectedIndex = 0;
|
||||
|
||||
if(stop) {
|
||||
xq.stopEvent(e);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var option = this.doc.createElement('option');
|
||||
option.innerHTML = buttonConfig.title;
|
||||
option.value = -1;
|
||||
select.appendChild(option);
|
||||
|
||||
option = this.doc.createElement('option');
|
||||
option.innerHTML = '----';
|
||||
option.value = -1;
|
||||
select.appendChild(option);
|
||||
|
||||
for(var i = 0; i < buttonConfig.list.length; i++) {
|
||||
option = this.doc.createElement('option');
|
||||
option.innerHTML = buttonConfig.list[i].title;
|
||||
option.value = i;
|
||||
|
||||
select.appendChild(option);
|
||||
}
|
||||
span.appendChild(select);
|
||||
},
|
||||
|
||||
_clickHandler: function(e) {
|
||||
var src = e.target || e.srcElement;
|
||||
while(src.nodeName !== "A") src = src.parentNode;
|
||||
|
||||
if(xq.hasClassName(src.parentNode, 'disabled') || xq.hasClassName(this.container, 'disabled')) {
|
||||
xq.stopEvent(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
var handler = src.handler;
|
||||
var xed = this.xed;
|
||||
xed.focus();
|
||||
if(typeof handler === "function") {
|
||||
handler(this);
|
||||
} else {
|
||||
eval(handler);
|
||||
}
|
||||
|
||||
xq.stopEvent(e);
|
||||
return false;
|
||||
},
|
||||
|
||||
_updateButtonStatus: function(className, selected) {
|
||||
var button = this.buttons[className];
|
||||
if(button) {
|
||||
var newClassName = selected ? 'selected' : '';
|
||||
var target = button.firstChild.firstChild;
|
||||
if(target.className !== newClassName) target.className = newClassName;
|
||||
}
|
||||
},
|
||||
|
||||
_execForAllButtons: function(exceptions, exec) {
|
||||
if(!this.container) return;
|
||||
exceptions = exceptions || [];
|
||||
|
||||
var lis = this.container.getElementsByTagName('LI');
|
||||
for(var i = 0; i < lis.length; i++) {
|
||||
var className = lis[i].className.split(" ").find(function(name) {return name !== 'xq_separator'});
|
||||
var exception = exceptions.indexOf(className) !== -1;
|
||||
exec(lis[i], exception);
|
||||
}
|
||||
},
|
||||
|
||||
_addStyleRules: function(rules) {
|
||||
if(!this.dynamicStyle) {
|
||||
if(xq.Browser.isTrident) {
|
||||
this.dynamicStyle = this.doc.createStyleSheet();
|
||||
} else {
|
||||
var style = this.doc.createElement('style');
|
||||
this.doc.body.appendChild(style);
|
||||
this.dynamicStyle = xq.$A(this.doc.styleSheets).last();
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < rules.length; i++) {
|
||||
var rule = rules[i];
|
||||
if(xq.Browser.isTrident) {
|
||||
this.dynamicStyle.addRule(rules[i].selector, rules[i].rule);
|
||||
} else {
|
||||
this.dynamicStyle.insertRule(rules[i].selector + " {" + rules[i].rule + "}", this.dynamicStyle.cssRules.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
20
modules/editor/skins/xquared/javascripts/ui/_templates.js
Normal file
20
modules/editor/skins/xquared/javascripts/ui/_templates.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
if(!xq) xq = {};
|
||||
if(!xq.ui_templates) xq.ui_templates = {};
|
||||
|
||||
xq.ui_templates.basicColorPickerDialog='<form action="#" class="xqFormDialog xqBasicColorPickerDialog">\n <div>\n <label>\n <input type="radio" class="initialFocus" name="color" value="black" checked="checked" />\n <span style="color: black;">Black</span>\n </label>\n <label>\n <input type="radio" name="color" value="red" />\n <span style="color: red;">Red</span>\n </label>\n <input type="radio" name="color" value="yellow" />\n <span style="color: yellow;">Yellow</span>\n </label>\n </label>\n <input type="radio" name="color" value="pink" />\n <span style="color: pink;">Pink</span>\n </label>\n <label>\n <input type="radio" name="color" value="blue" />\n <span style="color: blue;">Blue</span>\n </label>\n <label>\n <input type="radio" name="color" value="green" />\n <span style="color: green;">Green</span>\n </label>\n \n <input type="submit" value="Ok" />\n <input type="button" class="cancel" value="Cancel" />\n </div>\n </form>';
|
||||
if(!xq) xq = {};
|
||||
if(!xq.ui_templates) xq.ui_templates = {};
|
||||
|
||||
xq.ui_templates.basicIFrameDialog='<form action="#" class="xqFormDialog xqBasicIFrameDialog">\n <table>\n <tr>\n <td>IFrame src:</td>\n <td><input type="text" class="initialFocus" name="p_src" size="36" value="http://" /></td>\n </tr>\n <tr>\n <td>Width:</td>\n <td><input type="text" name="p_width" size="6" value="320" /></td>\n </tr>\n <tr>\n <td>Height:</td>\n <td><input type="text" name="p_height" size="6" value="200" /></td>\n </tr>\n <tr>\n <td>Frame border:</td>\n <td><select name="p_frameborder">\n <option value="0" selected="selected">No</option>\n <option value="1">Yes</option>\n </select></td>\n </tr>\n <tr>\n <td>Scrolling:</td>\n <td><select name="p_scrolling">\n <option value="0">No</option>\n <option value="1" selected="selected">Yes</option>\n </select></td>\n </tr>\n <tr>\n <td>ID(optional):</td>\n <td><input type="text" name="p_id" size="24" value="" /></td>\n </tr>\n <tr>\n <td>Class(optional):</td>\n <td><input type="text" name="p_class" size="24" value="" /></td>\n </tr>\n </table>\n <p>\n <input type="submit" value="Ok" />\n <input type="button" class="cancel" value="Cancel" />\n </p>\n </form>';
|
||||
if(!xq) xq = {};
|
||||
if(!xq.ui_templates) xq.ui_templates = {};
|
||||
|
||||
xq.ui_templates.basicLinkDialog='<form action="#" class="xqFormDialog xqBasicLinkDialog">\n <h3>Link</h3>\n <div>\n <input type="text" class="initialFocus" name="text" value="" />\n <input type="text" name="url" value="http://" />\n \n <input type="submit" value="Ok" />\n <input type="button" class="cancel" value="Cancel" />\n </div>\n </form>';
|
||||
if(!xq) xq = {};
|
||||
if(!xq.ui_templates) xq.ui_templates = {};
|
||||
|
||||
xq.ui_templates.basicMovieDialog='<form action="#" class="xqFormDialog xqBasicMovieDialog">\n <table>\n <tr>\n <td>Movie OBJECT tag:</td>\n <td><input type="text" class="initialFocus" name="html" size="36" value="" /></td>\n </tr>\n </table>\n <p>\n <input type="submit" value="Ok" />\n <input type="button" class="cancel" value="Cancel" />\n </p>\n </form>';
|
||||
if(!xq) xq = {};
|
||||
if(!xq.ui_templates) xq.ui_templates = {};
|
||||
|
||||
xq.ui_templates.basicScriptDialog='<form action="#" class="xqFormDialog xqBasicScriptDialog">\n <table>\n <tr>\n <td>Script URL:</td>\n <td><input type="text" class="initialFocus" name="url" size="36" value="http://" /></td>\n </tr>\n </table>\n <p>\n <input type="submit" value="Ok" />\n <input type="button" class="cancel" value="Cancel" />\n </p>\n </form>';
|
||||
391
modules/editor/skins/xquared/javascripts/validator/Base.js
Normal file
391
modules/editor/skins/xquared/javascripts/validator/Base.js
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
/**
|
||||
* @namespace
|
||||
*/
|
||||
xq.validator = {}
|
||||
|
||||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires Browser.js
|
||||
* @requires rdom/Factory.js
|
||||
*/
|
||||
xq.validator.Base = xq.Class(/** @lends xq.validator.Base.prototype */{
|
||||
/**
|
||||
* @constructs
|
||||
*/
|
||||
initialize: function(curUrl, urlValidationMode, whitelist) {
|
||||
xq.addToFinalizeQueue(this);
|
||||
xq.asEventSource(this, "Validator", ["Preprocessing", "BeforeDomValidation", "AfterDomValidation", "BeforeStringValidation", "AfterStringValidation", "BeforeDomInvalidation", "AfterDomInvalidation", "BeforeStringInvalidation", "AfterStringInvalidation"]);
|
||||
|
||||
this.whitelist = whitelist || xq.predefinedWhitelist;
|
||||
this.pRGB = xq.compilePattern("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
|
||||
|
||||
this.curUrl = curUrl;
|
||||
this.curUrlParts = curUrl ? curUrl.parseURL() : null;
|
||||
this.urlValidationMode = urlValidationMode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform validation on given element
|
||||
*
|
||||
* @param {Element} element Target element. It is not affected by validation.
|
||||
*
|
||||
* @returns {String} Validated HTML string
|
||||
*/
|
||||
validate: function(element, dontClone) {
|
||||
// DOM validation
|
||||
element = dontClone ? element : element.cloneNode(true);
|
||||
this._fireOnBeforeDomValidation(element);
|
||||
this.validateDom(element);
|
||||
this._fireOnAfterDomValidation(element);
|
||||
|
||||
// String validation
|
||||
var html = {value: element.innerHTML};
|
||||
this._fireOnBeforeStringValidation(html);
|
||||
html.value = this.validateString(html.value);
|
||||
this._fireOnAfterStringValidation(html);
|
||||
|
||||
return html.value;
|
||||
},
|
||||
|
||||
validateDom: function(element) {throw "Not implemented";},
|
||||
validateString: function(html) {throw "Not implemented";},
|
||||
|
||||
/**
|
||||
* Perform invalidation on given element to make the designmode works well.
|
||||
*
|
||||
* @param {String} html HTML string.
|
||||
* @returns {String} Invalidated HTML string
|
||||
*/
|
||||
invalidate: function(html) {
|
||||
// Preprocessing
|
||||
var html = {value: html};
|
||||
this._fireOnPreprocessing(html);
|
||||
|
||||
// DOM invalidation
|
||||
var element = document.createElement("DIV");
|
||||
element.innerHTML = html.value;
|
||||
this._fireOnBeforeDomInvalidation(element);
|
||||
this.invalidateDom(element);
|
||||
this._fireOnAfterDomInvalidation(element);
|
||||
|
||||
// String invalidation
|
||||
html.value = element.innerHTML;
|
||||
this._fireOnBeforeStringInvalidation(html);
|
||||
html.value = this.invalidateString(html.value);
|
||||
this._fireOnAfterStringInvalidation(html);
|
||||
|
||||
return html.value;
|
||||
},
|
||||
|
||||
invalidateDom: function(element) {throw "Not implemented"},
|
||||
invalidateString: function(html) {throw "Not implemented"},
|
||||
|
||||
/**
|
||||
* em.class="underline" -> u
|
||||
* span.class="strike" -> strike
|
||||
*/
|
||||
invalidateStrikesAndUnderlines: function(element) {
|
||||
var rdom = xq.rdom.Base.createInstance();
|
||||
rdom.setRoot(element);
|
||||
|
||||
var nameOfClassName = xq.Browser.isTrident ? "className" : "class";
|
||||
|
||||
var underlines = xq.getElementsByClassName(rdom.getRoot(), "underline", "em");
|
||||
var pUnderline = xq.compilePattern("(^|\\s)underline($|\\s)");
|
||||
var lenOfUnderlines = underlines.length;
|
||||
for(var i = 0; i < lenOfUnderlines; i++) {
|
||||
rdom.replaceTag("u", underlines[i]).removeAttribute(nameOfClassName);
|
||||
}
|
||||
|
||||
var strikes = xq.getElementsByClassName(rdom.getRoot(), "strike", "span")
|
||||
var pStrike = xq.compilePattern("(^|\\s)strike($|\\s)");
|
||||
var lenOfStrikes = strikes.length;
|
||||
for(var i = 0; i < lenOfStrikes; i++) {
|
||||
rdom.replaceTag("strike", strikes[i]).removeAttribute(nameOfClassName);
|
||||
}
|
||||
},
|
||||
|
||||
validateStrike: function(content) {
|
||||
content = content.replace(/<strike(>|\s+[^>]*>)/ig, "<span class=\"strike\"$1");
|
||||
content = content.replace(/<\/strike>/ig, "</span>");
|
||||
return content;
|
||||
},
|
||||
|
||||
validateUnderline: function(content) {
|
||||
content = content.replace(/<u(>|\s+[^>]*>)/ig, "<em class=\"underline\"$1");
|
||||
content = content.replace(/<\/u>/ig, "</em>");
|
||||
return content;
|
||||
},
|
||||
|
||||
replaceTag: function(content, from, to) {
|
||||
return content.replace(new RegExp("(</?)" + from + "(>|\\s+[^>]*>)", "ig"), "$1" + to + "$2");
|
||||
},
|
||||
|
||||
validateSelfClosingTags: function(content) {
|
||||
return content.replace(/<(br|hr|img|value)([^>]*?)>/img, function(str, tag, attrs) {
|
||||
return "<" + tag + attrs + " />"
|
||||
});
|
||||
},
|
||||
|
||||
validateFont: function(element) {
|
||||
var rdom = xq.rdom.Base.createInstance();
|
||||
rdom.setRoot(element);
|
||||
|
||||
// It should be reversed to deal with nested elements
|
||||
var fonts = element.getElementsByTagName('FONT');
|
||||
var fontSizes = ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"];
|
||||
var len = fonts.length - 1;
|
||||
for(var i = len; i >= 0; i--) {
|
||||
var font = fonts[i];
|
||||
var color = font.getAttribute('color');
|
||||
var backgroundColor = font.style.backgroundColor;
|
||||
var face = font.getAttribute('face');
|
||||
var size = fontSizes[parseInt(font.getAttribute('size')) % 8 - 1];
|
||||
|
||||
if(color || backgroundColor || face || size) {
|
||||
var span = rdom.replaceTag("span", font);
|
||||
span.removeAttribute('color');
|
||||
span.removeAttribute('face');
|
||||
span.removeAttribute('size');
|
||||
|
||||
if(color) span.style.color = color;
|
||||
if(backgroundColor) span.style.backgroundColor = backgroundColor;
|
||||
if(face) span.style.fontFamily = face;
|
||||
if(size) span.style.fontSize = size;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
invalidateFont: function(element) {
|
||||
var rdom = xq.rdom.Base.createInstance();
|
||||
rdom.setRoot(element);
|
||||
|
||||
// It should be reversed to deal with nested elements
|
||||
var spans = element.getElementsByTagName('SPAN');
|
||||
var fontSizes = {"xx-small":1, "x-small":2, "small":3, "medium":4, "large":5, "x-large":6, "xx-large":7};
|
||||
var len = spans.length - 1;
|
||||
for(var i = len; i >= 0; i--) {
|
||||
var span = spans[i];
|
||||
if(span.className === "strike") continue;
|
||||
|
||||
var color = span.style.color;
|
||||
var backgroundColor = span.style.backgroundColor;
|
||||
var face = span.style.fontFamily;
|
||||
var size = fontSizes[span.style.fontSize];
|
||||
|
||||
if(color || backgroundColor || face || size) {
|
||||
var font = rdom.replaceTag("font", span);
|
||||
font.style.cssText = "";
|
||||
|
||||
if(color) font.setAttribute('color', this.asRGB(color));
|
||||
if(backgroundColor) font.style.backgroundColor = backgroundColor;
|
||||
if(face) font.setAttribute('face', face);
|
||||
if(size) font.setAttribute('size', size);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
asRGB: function(color) {
|
||||
if(color.indexOf("#") === 0) return color;
|
||||
|
||||
var m = this.pRGB.exec(color);
|
||||
if(!m) return color;
|
||||
|
||||
var r = Number(m[1]).toString(16);
|
||||
var g = Number(m[2]).toString(16);
|
||||
var b = Number(m[3]).toString(16);
|
||||
|
||||
if(r.length === 1) r = "0" + r;
|
||||
if(g.length === 1) g = "0" + g;
|
||||
if(b.length === 1) b = "0" + b;
|
||||
|
||||
return "#" + r + g + b;
|
||||
},
|
||||
|
||||
removeComments: function(content) {
|
||||
return content.replace(/<!--.*?-->/img, '');
|
||||
},
|
||||
|
||||
removeDangerousElements: function(element) {
|
||||
var scripts = element.getElementsByTagName('SCRIPT');
|
||||
for(var i = scripts.length - 1; i >= 0; i--) {
|
||||
scripts[i].parentNode.removeChild(scripts[i]);
|
||||
}
|
||||
},
|
||||
|
||||
applyWhitelist: function(content) {
|
||||
var whitelist = this.whitelist;
|
||||
var allowedAttrs = null;
|
||||
|
||||
var p1 = xq.compilePattern("(^|\\s\")([^\"=]+)(\\s|$)", "g");
|
||||
var p2 = xq.compilePattern("(\\S+?)=\"[^\"]*\"", "g");
|
||||
return content.replace(new RegExp("(</?)([^>]+?)(>|\\s+([^>]*?)(\\s?/?)>)", "g"), function(str, head, tag, tail, attrs, selfClosing) {
|
||||
if(!(allowedAttrs = whitelist[tag])) return '';
|
||||
|
||||
if(attrs) {
|
||||
if(xq.Browser.isTrident) attrs = attrs.replace(p1, '$1$2="$2"$3');
|
||||
|
||||
var sb = [];
|
||||
var m = attrs.match(p2);
|
||||
for(var i = 0; i < m.length; i++) {
|
||||
var name = m[i].split('=')[0];
|
||||
if(allowedAttrs.indexOf(name) !== -1) sb.push(m[i]);
|
||||
}
|
||||
|
||||
if(sb.length) {
|
||||
attrs = sb.join(' ');
|
||||
return head + tag + ' ' + attrs + selfClosing + '>';
|
||||
} else {
|
||||
return head + tag + selfClosing + '>';
|
||||
}
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// TODO: very expansive
|
||||
makeUrlsRelative: function(content) {
|
||||
var curUrl = this.curUrl;
|
||||
var urlParts = this.curUrlParts;
|
||||
|
||||
var p1 = xq.compilePattern("(href|src)=\"([^\"]+)\"", "g");
|
||||
var p2 = xq.compilePattern("^\\w+://");
|
||||
|
||||
// 1. find attributes and...
|
||||
return content.replace(/(<\w+\s+)(\/|([^>]+?)(\/?))>/g, function(str, head, ignored, attrs, tail) {
|
||||
if(attrs) {
|
||||
// 2. validate URL part
|
||||
attrs = attrs.replace(p1, function(str, name, url) {
|
||||
// 3. first, make it absolute
|
||||
var abs = null;
|
||||
if(url.charAt(0) === '#') {
|
||||
abs = urlParts.includeQuery + url;
|
||||
} else if(url.charAt(0) === '?') {
|
||||
abs = urlParts.includePath + url;
|
||||
} else if(url.charAt(0) === '/') {
|
||||
abs = urlParts.includeHost + url;
|
||||
} else if(url.match(p2)) {
|
||||
abs = url;
|
||||
} else {
|
||||
abs = urlParts.includeBase + url;
|
||||
}
|
||||
|
||||
// 4. make it relative by removing same part
|
||||
var rel = abs;
|
||||
|
||||
if(abs === urlParts.includeHost) {
|
||||
rel = "/";
|
||||
} else if(abs.indexOf(urlParts.includeQuery) === 0) {
|
||||
rel = abs.substring(urlParts.includeQuery.length);
|
||||
} else if(abs.indexOf(urlParts.includePath) === 0) {
|
||||
rel = abs.substring(urlParts.includePath.length);
|
||||
} else if(abs.indexOf(urlParts.includeBase) === 0) {
|
||||
rel = abs.substring(urlParts.includeBase.length);
|
||||
} else if(abs.indexOf(urlParts.includeHost) === 0) {
|
||||
rel = abs.substring(urlParts.includeHost.length);
|
||||
}
|
||||
|
||||
if(rel === '') rel = '#';
|
||||
|
||||
return name + '="' + rel + '"';
|
||||
});
|
||||
|
||||
return head + attrs + tail + '>';
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
});
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
// TODO: very expansive
|
||||
makeUrlsHostRelative: function(content) {
|
||||
var curUrl = this.curUrl;
|
||||
var urlParts = this.curUrlParts;
|
||||
|
||||
var p1 = xq.compilePattern("(href|src)=\"([^\"]+)\"", "g");
|
||||
var p2 = xq.compilePattern("^\\w+://");
|
||||
|
||||
// 1. find attributes and...
|
||||
return content.replace(/(<\w+\s+)(\/|([^>]+?)(\/?))>/g, function(str, head, ignored, attrs, tail) {
|
||||
if(attrs) {
|
||||
// 2. validate URL part
|
||||
attrs = attrs.replace(p1, function(str, name, url) {
|
||||
// 3. first, make it absolute
|
||||
var abs = null;
|
||||
if(url.charAt(0) === '#') {
|
||||
abs = urlParts.includeQuery + url;
|
||||
} else if(url.charAt(0) === '?') {
|
||||
abs = urlParts.includePath + url;
|
||||
} else if(url.charAt(0) === '/') {
|
||||
abs = urlParts.includeHost + url;
|
||||
} else if(url.match(p2)) {
|
||||
abs = url;
|
||||
} else {
|
||||
abs = urlParts.includeBase + url;
|
||||
}
|
||||
|
||||
// 4. make it relative by removing same part
|
||||
var rel = abs;
|
||||
if(abs === urlParts.includeHost) {
|
||||
rel = "/";
|
||||
} else if(abs.indexOf(urlParts.includeQuery) === 0 && abs.indexOf("#") !== -1) {
|
||||
// same except for fragment-part?
|
||||
rel = abs.substring(abs.indexOf("#"));
|
||||
} else if(abs.indexOf(urlParts.includeHost) === 0) {
|
||||
// same host?
|
||||
rel = abs.substring(urlParts.includeHost.length);
|
||||
}
|
||||
|
||||
if(rel === '') rel = '#';
|
||||
|
||||
return name + '="' + rel + '"';
|
||||
});
|
||||
|
||||
return head + attrs + tail + '>';
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
});
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
// TODO: very expansive
|
||||
makeUrlsAbsolute: function(content) {
|
||||
var curUrl = this.curUrl;
|
||||
var urlParts = this.curUrlParts;
|
||||
|
||||
var p1 = xq.compilePattern("(href|src)=\"([^\"]+)\"", "g");
|
||||
var p2 = xq.compilePattern("^\\w+://");
|
||||
|
||||
// 1. find attributes and...
|
||||
return content.replace(/(<\w+\s+)(\/|([^>]+?)(\/?))>/g, function(str, head, ignored, attrs, tail) {
|
||||
if(attrs) {
|
||||
// 2. validate URL part
|
||||
attrs = attrs.replace(p1, function(str, name, url) {
|
||||
var abs = null;
|
||||
if(url.charAt(0) === '#') {
|
||||
abs = urlParts.includeQuery + url;
|
||||
} else if(url.charAt(0) === '?') {
|
||||
abs = urlParts.includePath + url;
|
||||
} else if(url.charAt(0) === '/') {
|
||||
abs = urlParts.includeHost + url;
|
||||
} else if(url.match(p2)) {
|
||||
abs = url;
|
||||
} else {
|
||||
abs = urlParts.includeBase + url;
|
||||
}
|
||||
|
||||
return name + '="' + abs + '"';
|
||||
});
|
||||
|
||||
return head + attrs + tail + '>';
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Creates and returns instance of browser specific implementation.
|
||||
*
|
||||
* @requires Xquared.js
|
||||
* @requires validator/Base.js
|
||||
* @requires validator/Trident.js
|
||||
* @requires validator/Gecko.js
|
||||
* @requires validator/Webkit.js
|
||||
*/
|
||||
xq.validator.Base.createInstance = function(curUrl, urlValidationMode, whitelist) {
|
||||
if(xq.Browser.isTrident) {
|
||||
return new xq.validator.Trident(curUrl, urlValidationMode, whitelist);
|
||||
} else if(xq.Browser.isWebkit) {
|
||||
return new xq.validator.Webkit(curUrl, urlValidationMode, whitelist);
|
||||
} else {
|
||||
return new xq.validator.Gecko(curUrl, urlValidationMode, whitelist);
|
||||
}
|
||||
}
|
||||
13
modules/editor/skins/xquared/javascripts/validator/Gecko.js
Normal file
13
modules/editor/skins/xquared/javascripts/validator/Gecko.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires validator/W3.js
|
||||
*/
|
||||
xq.validator.Gecko = xq.Class(xq.validator.W3,
|
||||
/**
|
||||
* @name xq.validator.Gecko
|
||||
* @lends xq.validator.Gecko.prototype
|
||||
* @extends xq.validator.W3
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
});
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires validator/Base.js
|
||||
*/
|
||||
xq.validator.Trident = xq.Class(xq.validator.Base,
|
||||
/**
|
||||
* @name xq.validator.Trident
|
||||
* @lends xq.validator.Trident.prototype
|
||||
* @extends xq.validator.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
validateDom: function(element) {
|
||||
this.removeDangerousElements(element);
|
||||
this.validateFont(element);
|
||||
},
|
||||
|
||||
validateString: function(html) {
|
||||
try {
|
||||
html = this.validateStrike(html);
|
||||
html = this.validateUnderline(html);
|
||||
html = this.performFullValidation(html);
|
||||
} catch(ignored) {}
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
invalidateDom: function(element) {
|
||||
this.invalidateFont(element);
|
||||
this.invalidateStrikesAndUnderlines(element);
|
||||
},
|
||||
|
||||
invalidateString: function(html) {
|
||||
html = this.removeComments(html);
|
||||
return html;
|
||||
},
|
||||
|
||||
performFullValidation: function(html) {
|
||||
html = this.lowerTagNamesAndUniformizeQuotation(html);
|
||||
html = this.validateSelfClosingTags(html);
|
||||
html = this.applyWhitelist(html);
|
||||
|
||||
if(this.urlValidationMode === 'relative') {
|
||||
html = this.makeUrlsRelative(html);
|
||||
} else if(this.urlValidationMode === 'host_relative') {
|
||||
html = this.makeUrlsHostRelative(html);
|
||||
} else if(this.urlValidationMode === 'absolute') {
|
||||
// Trident always use absolute URL so we don't need to do anything.
|
||||
//
|
||||
// html = this.makeUrlsAbsolute(html);
|
||||
}
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
lowerTagNamesAndUniformizeQuotation: function(html) {
|
||||
this.pAttrQuotation1 = xq.compilePattern("\\s(\\w+?)=\\s+\"([^\"]+)\"", "mg");
|
||||
this.pAttrQuotation2 = xq.compilePattern("\\s(\\w+?)=([^ \"]+)", "mg");
|
||||
this.pAttrQuotation3 = xq.compilePattern("\\sNAME=\"(\\w+?)\" VALUE=\"(\\w+?)\"", "mg");
|
||||
|
||||
// Uniformize quotation, turn tag names and attribute names into lower case
|
||||
html = html.replace(/<(\/?)(\w+)([^>]*?)>/img, function(str, closingMark, tagName, attrs) {
|
||||
return "<" + closingMark + tagName.toLowerCase() + this.correctHtmlAttrQuotation(attrs) + ">";
|
||||
}.bind(this));
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
correctHtmlAttrQuotation: function(html) {
|
||||
html = html.replace(this.pAttrQuotation1, function (str, name, value) {return " " + name.toLowerCase() + '=' + '"' + value + '"'});
|
||||
html = html.replace(this.pAttrQuotation2, function (str, name, value) {return " " + name.toLowerCase() + '=' + '"' + value + '"'});
|
||||
html = html.replace(this.pAttrQuotation3, function (str, name, value) {return " name=\"" + name + "\" value=\"" + value + "\""});
|
||||
return html;
|
||||
}
|
||||
});
|
||||
84
modules/editor/skins/xquared/javascripts/validator/W3.js
Normal file
84
modules/editor/skins/xquared/javascripts/validator/W3.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires validator/Base.js
|
||||
*/
|
||||
xq.validator.W3 = xq.Class(xq.validator.Base,
|
||||
/**
|
||||
* @name xq.validator.W3
|
||||
* @lends xq.validator.W3.prototype
|
||||
* @extends xq.validator.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
validateDom: function(element) {
|
||||
var rdom = xq.rdom.Base.createInstance();
|
||||
rdom.setRoot(element);
|
||||
this.removeDangerousElements(element);
|
||||
rdom.removePlaceHoldersAndEmptyNodes(element);
|
||||
this.validateFont(element);
|
||||
},
|
||||
|
||||
validateString: function(html) {
|
||||
try {
|
||||
html = this.replaceTag(html, "b", "strong");
|
||||
html = this.replaceTag(html, "i", "em");
|
||||
|
||||
html = this.validateStrike(html);
|
||||
html = this.validateUnderline(html);
|
||||
html = this.addNbspToEmptyBlocks(html);
|
||||
html = this.performFullValidation(html);
|
||||
html = this.insertNewlineBetweenBlockElements(html);
|
||||
} catch(ignored) {}
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
invalidateDom: function(element) {
|
||||
this.invalidateFont(element);
|
||||
this.invalidateStrikesAndUnderlines(element);
|
||||
},
|
||||
|
||||
invalidateString: function(html) {
|
||||
html = this.replaceTag(html, "strong", "b");
|
||||
html = this.replaceTag(html, "em", "i");
|
||||
html = this.removeComments(html);
|
||||
html = this.replaceNbspToBr(html);
|
||||
return html;
|
||||
},
|
||||
|
||||
performFullValidation: function(html) {
|
||||
html = this.validateSelfClosingTags(html);
|
||||
html = this.applyWhitelist(html);
|
||||
|
||||
if(this.urlValidationMode === 'relative') {
|
||||
html = this.makeUrlsRelative(html);
|
||||
} else if(this.urlValidationMode === 'host_relative') {
|
||||
html = this.makeUrlsHostRelative(html);
|
||||
} else if(this.urlValidationMode === 'absolute') {
|
||||
html = this.makeUrlsAbsolute(html);
|
||||
}
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
insertNewlineBetweenBlockElements: function(html) {
|
||||
var blocks = new xq.DomTree().getBlockTags().join("|");
|
||||
var regex = new RegExp("</(" + blocks + ")>([^\n])", "img");
|
||||
return html.replace(regex, '</$1>\n$2');
|
||||
},
|
||||
|
||||
addNbspToEmptyBlocks: function(content) {
|
||||
var blocks = new xq.DomTree().getBlockTags().join("|");
|
||||
var regex = new RegExp("<(" + blocks + ")>\\s*?</(" + blocks + ")>", "img");
|
||||
return content.replace(regex, '<$1> </$2>');
|
||||
},
|
||||
|
||||
replaceNbspToBr: function(content) {
|
||||
var blocks = new xq.DomTree().getBlockTags().join("|");
|
||||
|
||||
// Safari replaces into \xA0
|
||||
var regex = new RegExp("<(" + blocks + ")>( |\xA0)?</(" + blocks + ")>", "img");
|
||||
var rdom = xq.rdom.Base.createInstance();
|
||||
return content.replace(regex, '<$1>' + rdom.makePlaceHolderString() + '</$3>');
|
||||
}
|
||||
});
|
||||
145
modules/editor/skins/xquared/javascripts/validator/Webkit.js
Normal file
145
modules/editor/skins/xquared/javascripts/validator/Webkit.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires validator/W3.js
|
||||
*/
|
||||
xq.validator.Webkit = xq.Class(xq.validator.W3,
|
||||
/**
|
||||
* @name xq.validator.Webkit
|
||||
* @lends xq.validator.Webkit.prototype
|
||||
* @extends xq.validator.W3
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
validateDom: function(element) {
|
||||
var rdom = xq.rdom.Base.createInstance();
|
||||
rdom.setRoot(element);
|
||||
this.removeDangerousElements(element);
|
||||
rdom.removePlaceHoldersAndEmptyNodes(element);
|
||||
this.validateAppleStyleTags(element);
|
||||
},
|
||||
|
||||
validateString: function(html) {
|
||||
try {
|
||||
html = this.addNbspToEmptyBlocks(html);
|
||||
html = this.performFullValidation(html);
|
||||
html = this.insertNewlineBetweenBlockElements(html);
|
||||
} catch(ignored) {}
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
invalidateDom: function(element) {
|
||||
this.invalidateAppleStyleTags(element);
|
||||
},
|
||||
|
||||
invalidateString: function(html) {
|
||||
html = this.replaceTag(html, "strong", "b");
|
||||
html = this.replaceTag(html, "em", "i");
|
||||
html = this.removeComments(html);
|
||||
html = this.replaceNbspToBr(html);
|
||||
return html;
|
||||
},
|
||||
|
||||
validateAppleStyleTags: function(element) {
|
||||
var rdom = xq.rdom.Base.createInstance();
|
||||
rdom.setRoot(element);
|
||||
|
||||
var nodes = xq.getElementsByClassName(rdom.getRoot(), "apple-style-span");
|
||||
for(var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
|
||||
if(node.style.fontStyle === "italic") {
|
||||
// span -> em
|
||||
node = rdom.replaceTag("em", node);
|
||||
node.removeAttribute("class");
|
||||
node.style.fontStyle = "";
|
||||
} else if(node.style.fontWeight === "bold") {
|
||||
// span -> strong
|
||||
node = rdom.replaceTag("strong", node);
|
||||
node.removeAttribute("class");
|
||||
node.style.fontWeight = "";
|
||||
} else if(node.style.textDecoration === "underline") {
|
||||
// span -> em.underline
|
||||
node = rdom.replaceTag("em", node);
|
||||
node.className = "underline";
|
||||
node.style.textDecoration = "";
|
||||
} else if(node.style.textDecoration === "line-through") {
|
||||
// span -> span.strike
|
||||
node.className = "strike";
|
||||
node.style.textDecoration = "";
|
||||
} else if(node.style.verticalAlign === "super") {
|
||||
// span -> sup
|
||||
node = rdom.replaceTag("sup", node);
|
||||
node.removeAttribute("class");
|
||||
node.style.verticalAlign = "";
|
||||
} else if(node.style.verticalAlign === "sub") {
|
||||
// span -> sup
|
||||
node = rdom.replaceTag("sub", node);
|
||||
node.removeAttribute("class");
|
||||
node.style.verticalAlign = "";
|
||||
} else if(node.style.fontFamily) {
|
||||
// span -> span font-family
|
||||
node.removeAttribute("class");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
invalidateAppleStyleTags: function(element) {
|
||||
var rdom = xq.rdom.Base.createInstance();
|
||||
rdom.setRoot(element);
|
||||
|
||||
// span.strike -> span, span... -> span
|
||||
var spans = rdom.getRoot().getElementsByTagName("span");
|
||||
for(var i = 0; i < spans.length; i++) {
|
||||
var node = spans[i];
|
||||
if(node.className == "strike") {
|
||||
node.className = "Apple-style-span";
|
||||
node.style.textDecoration = "line-through";
|
||||
} else if(node.style.fontFamily) {
|
||||
node.className = "Apple-style-span";
|
||||
}
|
||||
// TODO: bg/fg/font-size
|
||||
}
|
||||
|
||||
// em -> span, em.underline -> span
|
||||
var ems = rdom.getRoot().getElementsByTagName("em");
|
||||
for(var i = 0; i < ems.length; i++) {
|
||||
var node = ems[i];
|
||||
node = rdom.replaceTag("span", node);
|
||||
if(node.className === "underline") {
|
||||
node.className = "apple-style-span";
|
||||
node.style.textDecoration = "underline";
|
||||
} else {
|
||||
node.className = "apple-style-span";
|
||||
node.style.fontStyle = "italic";
|
||||
}
|
||||
}
|
||||
|
||||
// strong -> span
|
||||
var strongs = rdom.getRoot().getElementsByTagName("strong");
|
||||
for(var i = 0; i < strongs.length; i++) {
|
||||
var node = strongs[i];
|
||||
node = rdom.replaceTag("span", node);
|
||||
node.className = "Apple-style-span";
|
||||
node.style.fontWeight = "bold";
|
||||
}
|
||||
|
||||
// sup -> span
|
||||
var sups = rdom.getRoot().getElementsByTagName("sup");
|
||||
for(var i = 0; i < sups.length; i++) {
|
||||
var node = sups[i];
|
||||
node = rdom.replaceTag("span", node);
|
||||
node.className = "Apple-style-span";
|
||||
node.style.verticalAlign = "super";
|
||||
}
|
||||
|
||||
// sub -> span
|
||||
var subs = rdom.getRoot().getElementsByTagName("sub");
|
||||
for(var i = 0; i < subs.length; i++) {
|
||||
var node = subs[i];
|
||||
node = rdom.replaceTag("span", node);
|
||||
node.className = "Apple-style-span";
|
||||
node.style.verticalAlign = "sub";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -30,10 +30,8 @@ function editorGetContent_xq(editor_sequence) {
|
|||
|
||||
function editorStart_xq(editor, element, editor_sequence, content_key, editor_height, primary_key) {
|
||||
editor = new xq.Editor(element);
|
||||
editor.config.imagePathForDefaultToobar = request_uri+editor_path.substring(2)+'images/toolbar/';
|
||||
editor.config.imagePathForContent = request_uri+editor_path.substring(2)+'images/content/';
|
||||
editor.config.allowedAttributes.push('editor_component', 'poll_srl','multimedia_src', 'auto_start', 'link_url', 'editor_sequence', 'use_folder', 'folder_opener', 'folder_closer', 'color', 'border_thickness', 'border_color', 'bg_color', 'border_style', 'margin', 'padding', 'bold', 'nx', 'ny', 'gx', 'gy', 'address', 'reg_sinpic', 'language','align');
|
||||
editor.config.allowedTags.push('embed', 'param', 'object');
|
||||
//editor.config.imagePathForDefaultToobar = request_uri+editor_path.substring(2)+'images/toolbar/';
|
||||
//editor.config.imagePathForContent = request_uri+editor_path.substring(2)+'images/content/';
|
||||
|
||||
editorRelKeys[editor_sequence] = new Array();
|
||||
editorRelKeys[editor_sequence]['editor'] = editor;
|
||||
|
|
@ -62,8 +60,9 @@ function editorStart_xq(editor, element, editor_sequence, content_key, editor_he
|
|||
}
|
||||
|
||||
editor.setStaticContent(fo_obj[content_key].value);
|
||||
editor.config.imagePathForDefaultToolbar = request_uri+editor_path+'images/toolbar/';
|
||||
editor.config.contentCssList = [request_uri+editor_path+"/stylesheets/xq_contents.css"];
|
||||
editor.setEditMode('wysiwyg');
|
||||
editor.loadStylesheet(request_uri+editor_path+"/css/xq_contents.css");
|
||||
editor.getFrame().style.width = "100%";
|
||||
editor.getFrame().parentNode.style.height = editor_height;
|
||||
editor.getBody().setAttribute('editor_sequence', editor_sequence);
|
||||
2851
modules/editor/skins/xquared/js/xquared-min.js
vendored
2851
modules/editor/skins/xquared/js/xquared-min.js
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -69,15 +69,15 @@
|
|||
font-family: monospace;
|
||||
list-style-type: none;
|
||||
border-color: #ffb781;
|
||||
background: url('../images/content/code.gif') no-repeat 0 0;
|
||||
background: url(../images/content/code.gif) no-repeat 0 0;
|
||||
}
|
||||
.xed div {
|
||||
border-color: #8ccfff;
|
||||
background: url('../images/content/div.gif') no-repeat 0 0;
|
||||
background: url(../images/content/div.gif) no-repeat 0 0;
|
||||
}
|
||||
.xed blockquote {
|
||||
border-color: #c9c9c9;
|
||||
background: url('../images/content/blockquote.gif') no-repeat 0 0;
|
||||
background: url(../images/content/blockquote.gif) no-repeat 0 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -119,4 +119,4 @@
|
|||
.xed table.datatable td {
|
||||
border-bottom: 1px solid #000;
|
||||
border-right: 1px solid #000;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,25 @@
|
|||
/**
|
||||
* Default Toolbar
|
||||
*/
|
||||
.xquared {
|
||||
border: 1px solid #c2c2c2;
|
||||
}
|
||||
.xquared .toolbar {
|
||||
border: 1px solid #c2c2c2;
|
||||
border-bottom: none;
|
||||
}
|
||||
.xquared .editor {
|
||||
border: 1px solid #c2c2c2;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.xquared div.toolbar {
|
||||
position: relative;
|
||||
background-color: #ebebeb;
|
||||
background-position: 0 0;
|
||||
background-repeat: repeat-x;
|
||||
background-image: url('../images/toolbar/toolbarBg.gif');
|
||||
background-image: url(../images/toolbar/toolbarBg.gif);
|
||||
}
|
||||
|
||||
.xquared ul.buttons {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 5px 4px 2px;
|
||||
list-style: none;
|
||||
|
|
@ -26,14 +33,14 @@
|
|||
padding-bottom: 3px;
|
||||
background-position: 0 0;
|
||||
background-repeat: repeat-x;
|
||||
background-image: url('../images/toolbar/toolbarButtonBg.gif');
|
||||
background-image: url(../images/toolbar/toolbarButtonBg.gif);
|
||||
}
|
||||
.xquared ul.buttons li.xq_separator {
|
||||
padding-left: 8px;
|
||||
margin-left: 8px;
|
||||
background-position: 0 0;
|
||||
background-repeat: repeat-x;
|
||||
background-image: url('../images/toolbar/toolbarSeparator.gif');
|
||||
background-image: url(../images/toolbar/toolbarSeparator.gif);
|
||||
}
|
||||
.xquared ul.buttons li a {
|
||||
display: block;
|
||||
|
|
@ -47,6 +54,7 @@
|
|||
z-index: 0;
|
||||
}
|
||||
.xquared ul.buttons li a img {
|
||||
height: 15px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
|
|
@ -90,9 +98,8 @@
|
|||
border: 1px solid #dbdbdb;
|
||||
}
|
||||
|
||||
/* editor */
|
||||
.xquared .editor {
|
||||
border: 0 none;
|
||||
border-top:1px solid #c2c2c2;
|
||||
height:300px;
|
||||
}
|
||||
.xquared .editor textarea,
|
||||
|
|
@ -105,10 +112,12 @@
|
|||
}
|
||||
|
||||
.xquared .editor textarea {
|
||||
_height: expression(this.parentNode.clientHeight - 2); /* TODO remove IE6 hack */
|
||||
/* TODO remove IE6 hack */
|
||||
_height: expression(this.parentNode.clientHeight - 2 - parseInt(this.currentStyle.borderTopWidth) - parseInt(this.currentStyle.borderBottomWidth));
|
||||
}
|
||||
*+html .xquared .editor textarea {
|
||||
height: expression(this.parentNode.clientHeight - 1); /* TODO remove IE7 hack */
|
||||
/* TODO remove IE7 hack */
|
||||
height: expression(this.parentNode.clientHeight - 1 - parseInt(this.currentStyle.borderTopWidth) - parseInt(this.currentStyle.borderBottomWidth));
|
||||
}
|
||||
|
||||
.xquared .source_editor {
|
||||
|
|
@ -230,4 +239,4 @@
|
|||
|
||||
.xqQuickSearch li.selected {
|
||||
background-color: #ffd;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue