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:
haneul 2008-11-24 08:52:19 +00:00
parent 5956e254e7
commit 7c3b336e41
59 changed files with 34562 additions and 8454 deletions

View file

@ -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

View file

@ -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.

View file

@ -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 -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 B

View 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
};

View 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));
}
});

View 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);
}
}
});

File diff suppressed because it is too large Load diff

View 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');
}
};
}();
}

View 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";
}
});

View 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;
}

View 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
}

View 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;
}
}
}
}
});

View 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();
}

View 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('');
}
})

View 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);
}
})

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;
}

View 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";

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
/**
* @requires Xquared.js
* @requires Editor.js
*/
xq.moduleName = "Minimal"

View 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

View 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 [];}
});

View file

@ -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;
}
});

View file

@ -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;
}
});

View file

@ -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;
}
}
});

View file

@ -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;
}
}
});

View file

@ -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;
}
}
});

View 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));
}
});

View file

@ -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|\&nbsp\;)*(\*|-)(\s|\&nbsp\;).+$/, handler: function(xed, rdom, block, text) {
rdom.pushMarker();
rdom.removePlaceHoldersAndEmptyNodes(block);
block.innerHTML = block.innerHTML.replace(/((\s|&nbsp;)*(\*|\-)\s*)/, "");
if(block.nodeName === "LI") xed.handleIndent();
if(block.parentNode.nodeName !== "UL") xed.handleList('UL');
rdom.popMarker(true);
}},
{id:'numbering', criteria: /^(\s|\&nbsp\;)*(\d\.|#)(\s|\&nbsp\;).+$/, handler: function(xed, rdom, block, text) {
rdom.pushMarker();
rdom.removePlaceHoldersAndEmptyNodes(block);
block.innerHTML = block.innerHTML.replace(/(\s|&nbsp;)*(\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: /^---+(\&nbsp;|\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: /^\=+[^=]*\=+(\&nbsp;|\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;
});
}
}
];
}
});

File diff suppressed because it is too large Load diff

View 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();
}
}

View 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);
}
}
});

View 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 '&nbsp;';
},
makeEmptyParagraph: function() {
return this.createElementFromHtml("<p>&nbsp;</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(/&nbsp;/g, " ")) {
block.innerHTML = html.replace(/&nbsp;$/, "");
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 = "&nbsp;";
}
},
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(["&nbsp;", " ", ""].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();
}
});

View 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;
}
});

View 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);
}
});

View file

@ -0,0 +1,6 @@
/**
* @namespace UI Controls
*
* @requires Xquared.js
*/
xq.ui = {};

View 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";
}
});

View 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);
}
}
}
});

View 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>';

View 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;
}
});
}
});

View file

@ -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);
}
}

View 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
*/
{
});

View file

@ -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;
}
});

View 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>&nbsp;</$2>');
},
replaceNbspToBr: function(content) {
var blocks = new xq.DomTree().getBlockTags().join("|");
// Safari replaces &nbsp; into \xA0
var regex = new RegExp("<(" + blocks + ")>(&nbsp;|\xA0)?</(" + blocks + ")>", "img");
var rdom = xq.rdom.Base.createInstance();
return content.replace(regex, '<$1>' + rdom.makePlaceHolderString() + '</$3>');
}
});

View 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";
}
}
});

View file

@ -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);

File diff suppressed because it is too large Load diff

View file

@ -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;
}
}

View file

@ -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;
}
}