mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-22 04:39:55 +09:00
17223554 : xquared upgrade to 0.7
git-svn-id: http://xe-core.googlecode.com/svn/sandbox@4968 201d5d3c-b55e-5fd7-737f-ddc643e51545
This commit is contained in:
parent
5956e254e7
commit
7c3b336e41
59 changed files with 34562 additions and 8454 deletions
2347
modules/editor/skins/xquared/javascripts/rdom/Base.js
Normal file
2347
modules/editor/skins/xquared/javascripts/rdom/Base.js
Normal file
File diff suppressed because it is too large
Load diff
18
modules/editor/skins/xquared/javascripts/rdom/Factory.js
Normal file
18
modules/editor/skins/xquared/javascripts/rdom/Factory.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Creates and returns instance of browser specific implementation.
|
||||
*
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/Base.js
|
||||
* @requires rdom/Trident.js
|
||||
* @requires rdom/Gecko.js
|
||||
* @requires rdom/Webkit.js
|
||||
*/
|
||||
xq.rdom.Base.createInstance = function() {
|
||||
if(xq.Browser.isTrident) {
|
||||
return new xq.rdom.Trident();
|
||||
} else if(xq.Browser.isWebkit) {
|
||||
return new xq.rdom.Webkit();
|
||||
} else {
|
||||
return new xq.rdom.Gecko();
|
||||
}
|
||||
}
|
||||
48
modules/editor/skins/xquared/javascripts/rdom/Gecko.js
Normal file
48
modules/editor/skins/xquared/javascripts/rdom/Gecko.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/W3.js
|
||||
*/
|
||||
xq.rdom.Gecko = xq.Class(xq.rdom.W3,
|
||||
/**
|
||||
* @name xq.rdom.Gecko
|
||||
* @lends xq.rdom.Gecko.prototype
|
||||
* @extends xq.rdom.W3
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
makePlaceHolder: function() {
|
||||
var holder = this.createElement("BR");
|
||||
holder.setAttribute("type", "_moz");
|
||||
return holder;
|
||||
},
|
||||
|
||||
makePlaceHolderString: function() {
|
||||
return '<br type="_moz" />';
|
||||
},
|
||||
|
||||
makeEmptyParagraph: function() {
|
||||
return this.createElementFromHtml('<p><br type="_moz" /></p>');
|
||||
},
|
||||
|
||||
isPlaceHolder: function(node) {
|
||||
return node.nodeName === "BR" && (node.getAttribute("type") === "_moz" || !this.getNextSibling(node));
|
||||
},
|
||||
|
||||
selectElement: function(element, entireElement) {
|
||||
if(!element) throw "[element] is null";
|
||||
if(element.nodeType !== 1) throw "[element] is not an element";
|
||||
|
||||
// @WORKAROUND: required to avoid Windows FF selection bug.
|
||||
try {
|
||||
if(!xq.Browser.isMac) this.getDoc().execCommand("SelectAll", false, null);
|
||||
} catch(ignored) {}
|
||||
|
||||
var rng = this.rng() || this.getDoc().createRange();
|
||||
|
||||
if(entireElement) {
|
||||
rng.selectNode(element);
|
||||
} else {
|
||||
rng.selectNodeContents(element);
|
||||
}
|
||||
}
|
||||
});
|
||||
397
modules/editor/skins/xquared/javascripts/rdom/Trident.js
Normal file
397
modules/editor/skins/xquared/javascripts/rdom/Trident.js
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/Base.js
|
||||
*/
|
||||
xq.rdom.Trident = xq.Class(xq.rdom.Base,
|
||||
/**
|
||||
* @name xq.rdom.Trident
|
||||
* @lends xq.rdom.Trident.prototype
|
||||
* @extends xq.rdom.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
makePlaceHolder: function() {
|
||||
return this.createTextNode(" ");
|
||||
},
|
||||
|
||||
makePlaceHolderString: function() {
|
||||
return ' ';
|
||||
},
|
||||
|
||||
makeEmptyParagraph: function() {
|
||||
return this.createElementFromHtml("<p> </p>");
|
||||
},
|
||||
|
||||
isPlaceHolder: function(node) {
|
||||
return false;
|
||||
},
|
||||
|
||||
getOuterHTML: function(element) {
|
||||
return element.outerHTML;
|
||||
},
|
||||
|
||||
getCurrentBlockElement: function() {
|
||||
var cur = this.getCurrentElement();
|
||||
if(!cur) return null;
|
||||
|
||||
var block = this.getParentBlockElementOf(cur);
|
||||
if(!block) return null;
|
||||
|
||||
if(block.nodeName === "BODY") {
|
||||
// Atomic block such as HR
|
||||
var newParagraph = this.insertNode(this.makeEmptyParagraph());
|
||||
var next = newParagraph.nextSibling;
|
||||
if(this.tree.isAtomic(next)) {
|
||||
this.deleteNode(newParagraph);
|
||||
return next;
|
||||
}
|
||||
} else {
|
||||
return block;
|
||||
}
|
||||
},
|
||||
|
||||
insertNode: function(node) {
|
||||
if(this.hasSelection()) this.collapseSelection(true);
|
||||
|
||||
this.rng().pasteHTML('<span id="xquared_temp"></span>');
|
||||
var marker = this.$('xquared_temp');
|
||||
if(node.id === 'xquared_temp') return marker;
|
||||
|
||||
if(marker) marker.replaceNode(node);
|
||||
return node;
|
||||
},
|
||||
|
||||
removeTrailingWhitespace: function(block) {
|
||||
if(!block) return;
|
||||
|
||||
// @TODO: reimplement to handle atomic tags and so on. (use DomTree)
|
||||
if(this.tree.isBlockOnlyContainer(block)) return;
|
||||
if(this.isEmptyBlock(block)) return;
|
||||
|
||||
var text = block.innerText;
|
||||
var html = block.innerHTML;
|
||||
var lastCharCode = text.charCodeAt(text.length - 1);
|
||||
if(text.length <= 1 || [32,160].indexOf(lastCharCode) === -1) return;
|
||||
|
||||
// shortcut for most common case
|
||||
if(text == html.replace(/ /g, " ")) {
|
||||
block.innerHTML = html.replace(/ $/, "");
|
||||
return;
|
||||
}
|
||||
|
||||
var node = block;
|
||||
while(node && node.nodeType !== 3) node = node.lastChild;
|
||||
if(!node) return;
|
||||
|
||||
// DO NOT REMOVE OR MODIFY FOLLOWING CODE. Modifying following code will crash IE7
|
||||
var nodeValue = node.nodeValue;
|
||||
if(nodeValue.length <= 1) {
|
||||
this.deleteNode(node, true);
|
||||
} else {
|
||||
node.nodeValue = nodeValue.substring(0, nodeValue.length - 1);
|
||||
}
|
||||
},
|
||||
|
||||
correctEmptyElement: function(element) {
|
||||
if(!element || element.nodeType !== 1 || this.tree.isAtomic(element)) return;
|
||||
|
||||
if(element.firstChild) {
|
||||
this.correctEmptyElement(element.firstChild);
|
||||
} else {
|
||||
element.innerHTML = " ";
|
||||
}
|
||||
},
|
||||
|
||||
copyAttributes: function(from, to, copyId) {
|
||||
to.mergeAttributes(from, !copyId);
|
||||
},
|
||||
|
||||
correctParagraph: function() {
|
||||
if(!this.hasFocus()) return false;
|
||||
if(this.hasSelection()) return false;
|
||||
|
||||
var block = this.getCurrentElement();
|
||||
|
||||
// if caret is at
|
||||
// * atomic block level elements(HR) or
|
||||
// * ...
|
||||
// then following is true
|
||||
if(this.tree.isBlockOnlyContainer(block)) {
|
||||
// check for atomic block element such as HR
|
||||
block = this.insertNode(this.makeEmptyParagraph());
|
||||
if(this.tree.isAtomic(block.nextSibling)) {
|
||||
// @WORKAROUND:
|
||||
// At this point, HR has a caret but getCurrentElement() doesn't return the HR and
|
||||
// I couldn't find a way to get this HR. So I have to keep this reference.
|
||||
// I will be used in Editor._handleEnter.
|
||||
this.recentHR = block.nextSibling;
|
||||
this.deleteNode(block);
|
||||
return false;
|
||||
} else {
|
||||
// I can't remember exactly when following is executed and what it does :-(
|
||||
// * Case 1: Performing Ctrl+A and Ctrl+X repeatedly
|
||||
// * ...
|
||||
var nextBlock = this.tree.findForward(
|
||||
block,
|
||||
function(node) {return this.tree.isBlock(node) && !this.tree.isBlockOnlyContainer(node)}.bind(this)
|
||||
);
|
||||
|
||||
if(nextBlock) {
|
||||
this.deleteNode(block);
|
||||
this.placeCaretAtStartOf(nextBlock);
|
||||
} else {
|
||||
this.placeCaretAtStartOf(block);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
block = this.getCurrentBlockElement();
|
||||
if(block.nodeType === 3) block = block.parentNode;
|
||||
|
||||
if(this.tree.hasMixedContents(block)) {
|
||||
var marker = this.pushMarker();
|
||||
this.wrapAllInlineOrTextNodesAs("P", block, true);
|
||||
this.popMarker(true);
|
||||
return true;
|
||||
} else if((this.tree.isTextOrInlineNode(block.previousSibling) || this.tree.isTextOrInlineNode(block.nextSibling)) && this.tree.hasMixedContents(block.parentNode)) {
|
||||
// @WORKAROUND:
|
||||
// IE에서는 Block과 Inline/Text가 인접한 경우 getCurrentElement 등이 오작동한다.
|
||||
// 따라서 현재 Block 주변까지 한번에 잡아주어야 한다.
|
||||
this.wrapAllInlineOrTextNodesAs("P", block.parentNode, true);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
//////
|
||||
// Commands
|
||||
execCommand: function(commandId, param) {
|
||||
return this.getDoc().execCommand(commandId, false, param);
|
||||
},
|
||||
|
||||
applyBackgroundColor: function(color) {
|
||||
this.execCommand("BackColor", color);
|
||||
},
|
||||
|
||||
applyEmphasis: function() {
|
||||
// Generate <i> tag. It will be replaced with <emphasis> tag during cleanup phase.
|
||||
this.execCommand("Italic");
|
||||
},
|
||||
applyStrongEmphasis: function() {
|
||||
// Generate <b> tag. It will be replaced with <strong> tag during cleanup phase.
|
||||
this.execCommand("Bold");
|
||||
},
|
||||
applyStrike: function() {
|
||||
// Generate <strike> tag. It will be replaced with <style class="strike"> tag during cleanup phase.
|
||||
this.execCommand("strikethrough");
|
||||
},
|
||||
applyUnderline: function() {
|
||||
// Generate <u> tag. It will be replaced with <em class="underline"> tag during cleanup phase.
|
||||
this.execCommand("underline");
|
||||
},
|
||||
applyRemoveFormat: function() {
|
||||
this.execCommand("RemoveFormat");
|
||||
},
|
||||
applyRemoveLink: function() {
|
||||
this.execCommand("Unlink");
|
||||
},
|
||||
|
||||
|
||||
|
||||
//////
|
||||
// Focus/Caret/Selection
|
||||
|
||||
focus: function() {
|
||||
this.getWin().focus();
|
||||
},
|
||||
|
||||
sel: function() {
|
||||
return this.getDoc().selection;
|
||||
},
|
||||
|
||||
crng: function() {
|
||||
return this.getDoc().body.createControlRange();
|
||||
},
|
||||
|
||||
rng: function() {
|
||||
try {
|
||||
var sel = this.sel();
|
||||
return (sel === null) ? null : sel.createRange();
|
||||
} catch(ignored) {
|
||||
// IE often fails
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
hasSelection: function() {
|
||||
var selectionType = this.sel().type.toLowerCase();
|
||||
if("none" === selectionType) return false;
|
||||
if("text" === selectionType && this.getSelectionAsHtml().length === 0) return false;
|
||||
return true;
|
||||
},
|
||||
|
||||
deleteSelection: function() {
|
||||
if(this.getSelectionAsText() !== "") this.sel().clear();
|
||||
},
|
||||
|
||||
placeCaretAtStartOf: function(element) {
|
||||
// If there's no empty span, caret sometimes moves into a previous node.
|
||||
var ph = this.insertNodeAt(this.createElement("SPAN"), element, "start");
|
||||
this.selectElement(ph);
|
||||
this.collapseSelection(false);
|
||||
this.deleteNode(ph);
|
||||
},
|
||||
|
||||
selectElement: function(element, entireElement, forceTextSelection) {
|
||||
if(!element) throw "[element] is null";
|
||||
if(element.nodeType !== 1) throw "[element] is not an element";
|
||||
|
||||
var rng = null;
|
||||
if(!forceTextSelection && this.tree.isAtomic(element)) {
|
||||
rng = this.crng();
|
||||
rng.addElement(element);
|
||||
} else {
|
||||
var rng = this.rng();
|
||||
rng.moveToElementText(element);
|
||||
}
|
||||
rng.select();
|
||||
},
|
||||
|
||||
selectBlocksBetween: function(start, end) {
|
||||
var rng = this.rng();
|
||||
var rngTemp = this.rng();
|
||||
|
||||
rngTemp.moveToElementText(start);
|
||||
rng.setEndPoint("StartToStart", rngTemp);
|
||||
|
||||
rngTemp.moveToElementText(end);
|
||||
rng.setEndPoint("EndToEnd", rngTemp);
|
||||
|
||||
rng.select();
|
||||
},
|
||||
|
||||
collapseSelection: function(toStart) {
|
||||
if(this.sel().type.toLowerCase() === "control") {
|
||||
var curElement = this.getCurrentElement();
|
||||
this.sel().empty();
|
||||
this.selectElement(curElement, false, true);
|
||||
}
|
||||
var rng = this.rng();
|
||||
rng.collapse(toStart);
|
||||
rng.select();
|
||||
},
|
||||
|
||||
getSelectionAsHtml: function() {
|
||||
var rng = this.rng()
|
||||
return rng && rng.htmlText ? rng.htmlText : ""
|
||||
},
|
||||
|
||||
getSelectionAsText: function() {
|
||||
var rng = this.rng();
|
||||
return rng && rng.text ? rng.text : "";
|
||||
},
|
||||
|
||||
hasImportantAttributes: function(element) {
|
||||
return !!(element.id || element.className || element.style.cssText);
|
||||
},
|
||||
|
||||
isEmptyBlock: function(element) {
|
||||
if(!element.hasChildNodes()) return true;
|
||||
if(element.nodeType === 3 && !element.nodeValue) return true;
|
||||
if([" ", " ", ""].indexOf(element.innerHTML) !== -1) return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getLastChild: function(element) {
|
||||
if(!element || !element.hasChildNodes()) return null;
|
||||
|
||||
var nodes = xq.$A(element.childNodes).reverse();
|
||||
|
||||
for(var i = 0; i < nodes.length; i++) {
|
||||
if(nodes[i].nodeType !== 3 || nodes[i].nodeValue.length !== 0) return nodes[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getCurrentElement: function() {
|
||||
if(this.sel().type.toLowerCase() === "control") return this.rng().item(0);
|
||||
|
||||
var rng = this.rng();
|
||||
if(!rng) return false;
|
||||
|
||||
var element = rng.parentElement();
|
||||
if(element.nodeName == "BODY" && this.hasSelection()) return null;
|
||||
return element;
|
||||
},
|
||||
|
||||
getBlockElementAtSelectionStart: function() {
|
||||
var rng = this.rng();
|
||||
var dup = rng.duplicate();
|
||||
dup.collapse(true);
|
||||
|
||||
var result = this.getParentBlockElementOf(dup.parentElement());
|
||||
if(result.nodeName === "BODY") result = result.firstChild;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getBlockElementAtSelectionEnd: function() {
|
||||
var rng = this.rng();
|
||||
var dup = rng.duplicate();
|
||||
dup.collapse(false);
|
||||
|
||||
var result = this.getParentBlockElementOf(dup.parentElement());
|
||||
if(result.nodeName === "BODY") result = result.lastChild;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getBlockElementsAtSelectionEdge: function(naturalOrder, ignoreEmptyEdges) {
|
||||
return [
|
||||
this.getBlockElementAtSelectionStart(),
|
||||
this.getBlockElementAtSelectionEnd()
|
||||
];
|
||||
},
|
||||
|
||||
isCaretAtBlockEnd: function() {
|
||||
if(this.isCaretAtEmptyBlock()) return true;
|
||||
if(this.hasSelection()) return false;
|
||||
|
||||
var node = this.getCurrentBlockElement();
|
||||
var marker = this.pushMarker();
|
||||
var isTrue = false;
|
||||
while (node = this.getLastChild(node)) {
|
||||
var nodeValue = node.nodeValue;
|
||||
|
||||
if (node === marker) {
|
||||
isTrue = true;
|
||||
break;
|
||||
} else if(
|
||||
node.nodeType === 3 &&
|
||||
node.previousSibling === marker &&
|
||||
(nodeValue === " " || (nodeValue.length === 1 && nodeValue.charCodeAt(0) === 160))
|
||||
) {
|
||||
isTrue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.popMarker();
|
||||
return isTrue;
|
||||
},
|
||||
|
||||
saveSelection: function() {
|
||||
return this.rng();
|
||||
},
|
||||
|
||||
restoreSelection: function(bookmark) {
|
||||
bookmark.select();
|
||||
}
|
||||
});
|
||||
375
modules/editor/skins/xquared/javascripts/rdom/W3.js
Normal file
375
modules/editor/skins/xquared/javascripts/rdom/W3.js
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
/**
|
||||
* Base for W3C Standard Engine
|
||||
*
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/Base.js
|
||||
*/
|
||||
xq.rdom.W3 = xq.Class(xq.rdom.Base,
|
||||
/**
|
||||
* @name xq.rdom.W3
|
||||
* @lends xq.rdom.W3.prototype
|
||||
* @extends xq.rdom.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
insertNode: function(node) {
|
||||
var rng = this.rng();
|
||||
|
||||
if(!rng) {
|
||||
this.getRoot().appendChild(node);
|
||||
} else {
|
||||
rng.insertNode(node);
|
||||
rng.selectNode(node);
|
||||
rng.collapse(false);
|
||||
}
|
||||
return node;
|
||||
},
|
||||
|
||||
removeTrailingWhitespace: function(block) {
|
||||
// TODO: do nothing
|
||||
},
|
||||
|
||||
getOuterHTML: function(element) {
|
||||
var div = element.ownerDocument.createElement("div");
|
||||
div.appendChild(element.cloneNode(true));
|
||||
return div.innerHTML;
|
||||
},
|
||||
|
||||
correctEmptyElement: function(element) {
|
||||
if(!element || element.nodeType !== 1 || this.tree.isAtomic(element)) return;
|
||||
|
||||
if(element.firstChild)
|
||||
this.correctEmptyElement(element.firstChild);
|
||||
else
|
||||
element.appendChild(this.makePlaceHolder());
|
||||
},
|
||||
|
||||
correctParagraph: function() {
|
||||
if(this.hasSelection()) return false;
|
||||
|
||||
var block = this.getCurrentBlockElement();
|
||||
var modified = false;
|
||||
|
||||
if(!block) {
|
||||
try {
|
||||
this.execCommand("InsertParagraph");
|
||||
modified = true;
|
||||
} catch(ignored) {}
|
||||
} else if(this.tree.isBlockOnlyContainer(block)) {
|
||||
this.execCommand("InsertParagraph");
|
||||
|
||||
// check for HR
|
||||
var newBlock = this.getCurrentElement();
|
||||
|
||||
if(this.tree.isAtomic(newBlock.previousSibling) && newBlock.previousSibling.nodeName === "HR") {
|
||||
var nextBlock = this.tree.findForward(
|
||||
newBlock,
|
||||
function(node) {return this.tree.isBlock(node) && !this.tree.isBlockOnlyContainer(node)}.bind(this)
|
||||
);
|
||||
if(nextBlock) {
|
||||
this.deleteNode(newBlock);
|
||||
this.placeCaretAtStartOf(nextBlock);
|
||||
}
|
||||
}
|
||||
modified = true;
|
||||
} else if(this.tree.hasMixedContents(block)) {
|
||||
this.wrapAllInlineOrTextNodesAs("P", block, true);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// insert placeholder - part 1
|
||||
block = this.getCurrentBlockElement();
|
||||
if(this.tree.isBlock(block) && !this._hasPlaceHolderAtEnd(block)) {
|
||||
block.appendChild(this.makePlaceHolder());
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// insert placeholder - part 2
|
||||
if(this.tree.isBlock(block)) {
|
||||
var parentsLastChild = block.parentNode.lastChild;
|
||||
if(this.isPlaceHolder(parentsLastChild)) {
|
||||
this.deleteNode(parentsLastChild);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// remove empty elements
|
||||
if(this.tree.isBlock(block)) {
|
||||
var nodes = block.childNodes;
|
||||
for(var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if(node.nodeType === 1 && !this.tree.isAtomic(node) && !node.hasChildNodes() && !this.isPlaceHolder(node)) {
|
||||
this.deleteNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modified;
|
||||
},
|
||||
|
||||
_hasPlaceHolderAtEnd: function(block) {
|
||||
if(!block.hasChildNodes()) return false;
|
||||
return this.isPlaceHolder(block.lastChild) || this._hasPlaceHolderAtEnd(block.lastChild);
|
||||
},
|
||||
|
||||
applyBackgroundColor: function(color) {
|
||||
this.execCommand("styleWithCSS", "true");
|
||||
this.execCommand("hilitecolor", color);
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
|
||||
// 0. Save current selection
|
||||
var bookmark = this.saveSelection();
|
||||
|
||||
// 1. Get selected blocks
|
||||
var blocks = this.getSelectedBlockElements();
|
||||
if(blocks.length === 0) return;
|
||||
|
||||
// 2. Apply background-color to all adjust inline elements
|
||||
// 3. Remove background-color from blocks
|
||||
for(var i = 0; i < blocks.length; i++) {
|
||||
if((i === 0 || i === blocks.length-1) && !blocks[i].style.backgroundColor) continue;
|
||||
|
||||
var spans = this.wrapAllInlineOrTextNodesAs("SPAN", blocks[i], true);
|
||||
for(var j = 0; j < spans.length; j++) {
|
||||
spans[j].style.backgroundColor = color;
|
||||
}
|
||||
blocks[i].style.backgroundColor = "";
|
||||
}
|
||||
|
||||
// 4. Restore selection
|
||||
this.restoreSelection(bookmark);
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
//////
|
||||
// Commands
|
||||
execCommand: function(commandId, param) {
|
||||
return this.getDoc().execCommand(commandId, false, param || null);
|
||||
},
|
||||
|
||||
applyRemoveFormat: function() {
|
||||
this.execCommand("RemoveFormat");
|
||||
},
|
||||
applyRemoveLink: function() {
|
||||
this.execCommand("Unlink");
|
||||
},
|
||||
applyEmphasis: function() {
|
||||
// Generate <i> tag. It will be replaced with <emphasis> tag during cleanup phase.
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
this.execCommand("italic");
|
||||
},
|
||||
applyStrongEmphasis: function() {
|
||||
// Generate <b> tag. It will be replaced with <strong> tag during cleanup phase.
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
this.execCommand("bold");
|
||||
},
|
||||
applyStrike: function() {
|
||||
// Generate <strike> tag. It will be replaced with <style class="strike"> tag during cleanup phase.
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
this.execCommand("strikethrough");
|
||||
},
|
||||
applyUnderline: function() {
|
||||
// Generate <u> tag. It will be replaced with <em class="underline"> tag during cleanup phase.
|
||||
this.execCommand("styleWithCSS", "false");
|
||||
this.execCommand("underline");
|
||||
},
|
||||
|
||||
|
||||
|
||||
//////
|
||||
// Focus/Caret/Selection
|
||||
|
||||
focus: function() {
|
||||
this.getWin().focus();
|
||||
},
|
||||
|
||||
sel: function() {
|
||||
return this.getWin().getSelection();
|
||||
},
|
||||
|
||||
rng: function() {
|
||||
var sel = this.sel();
|
||||
return (sel === null || sel.rangeCount === 0) ? null : sel.getRangeAt(0);
|
||||
},
|
||||
|
||||
saveSelection: function() {
|
||||
var rng = this.rng();
|
||||
return [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];
|
||||
},
|
||||
|
||||
restoreSelection: function(bookmark) {
|
||||
var rng = this.rng();
|
||||
rng.setStart(bookmark[0], bookmark[1]);
|
||||
rng.setEnd(bookmark[2], bookmark[3]);
|
||||
},
|
||||
|
||||
hasSelection: function() {
|
||||
var sel = this.sel();
|
||||
return sel && !sel.isCollapsed;
|
||||
},
|
||||
|
||||
deleteSelection: function() {
|
||||
this.rng().deleteContents();
|
||||
this.sel().collapseToStart();
|
||||
},
|
||||
|
||||
selectElement: function(element, entireElement) {throw "Not implemented yet"},
|
||||
|
||||
selectBlocksBetween: function(start, end) {
|
||||
// @WORKAROUND: required to avoid FF selection bug.
|
||||
try {
|
||||
if(!xq.Browser.isMac) this.getDoc().execCommand("SelectAll", false, null);
|
||||
} catch(ignored) {}
|
||||
|
||||
var rng = this.rng();
|
||||
rng.setStart(start.firstChild, 0);
|
||||
rng.setEnd(end, end.childNodes.length);
|
||||
},
|
||||
|
||||
collapseSelection: function(toStart) {
|
||||
var rng = this.rng();
|
||||
if(rng) rng.collapse(toStart);
|
||||
},
|
||||
|
||||
placeCaretAtStartOf: function(element) {
|
||||
while(this.tree.isBlock(element.firstChild)) {
|
||||
element = element.firstChild;
|
||||
}
|
||||
this.selectElement(element, false);
|
||||
this.collapseSelection(true);
|
||||
},
|
||||
|
||||
placeCaretAtEndOf: function(element) {
|
||||
while(this.tree.isBlock(element.lastChild)) {
|
||||
element = element.lastChild;
|
||||
}
|
||||
this.selectElement(element, false);
|
||||
this.collapseSelection(false);
|
||||
},
|
||||
|
||||
getSelectionAsHtml: function() {
|
||||
var container = document.createElement("div");
|
||||
container.appendChild(this.rng().cloneContents());
|
||||
return container.innerHTML;
|
||||
},
|
||||
|
||||
getSelectionAsText: function() {
|
||||
return this.rng().toString()
|
||||
},
|
||||
|
||||
hasImportantAttributes: function(element) {
|
||||
return !!(element.id || element.className || element.style.cssText);
|
||||
},
|
||||
|
||||
isEmptyBlock: function(element) {
|
||||
if(!element.hasChildNodes()) return true;
|
||||
var children = element.childNodes;
|
||||
for(var i = 0; i < children.length; i++) {
|
||||
if(!this.isPlaceHolder(children[i]) && !this.isEmptyTextNode(children[i])) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
getLastChild: function(element) {
|
||||
if(!element || !element.hasChildNodes()) return null;
|
||||
|
||||
var nodes = xq.$A(element.childNodes).reverse();
|
||||
|
||||
for(var i = 0; i < nodes.length; i++) {
|
||||
if(!this.isPlaceHolder(nodes[i]) && !this.isEmptyTextNode(nodes[i])) return nodes[i];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getCurrentElement: function() {
|
||||
var rng = this.rng();
|
||||
if(!rng) return null;
|
||||
|
||||
var container = rng.startContainer;
|
||||
|
||||
if(container.nodeType === 3) {
|
||||
return container.parentNode;
|
||||
} else if(this.tree.isBlockOnlyContainer(container)) {
|
||||
return container.childNodes[rng.startOffset];
|
||||
} else {
|
||||
return container;
|
||||
}
|
||||
},
|
||||
|
||||
getBlockElementsAtSelectionEdge: function(naturalOrder, ignoreEmptyEdges) {
|
||||
var start = this.getBlockElementAtSelectionStart();
|
||||
var end = this.getBlockElementAtSelectionEnd();
|
||||
|
||||
var reversed = false;
|
||||
|
||||
if(naturalOrder && start !== end && this.tree.checkTargetBackward(start, end)) {
|
||||
var temp = start;
|
||||
start = end;
|
||||
end = temp;
|
||||
|
||||
reversed = true;
|
||||
}
|
||||
|
||||
if(ignoreEmptyEdges && start !== end) {
|
||||
// @TODO: Firefox sometimes selects one more block.
|
||||
/*
|
||||
|
||||
var sel = this.sel();
|
||||
if(reversed) {
|
||||
if(sel.focusNode.nodeType === 1) start = start.nextSibling;
|
||||
if(sel.anchorNode.nodeType === 3 && sel.focusOffset === 0) end = end.previousSibling;
|
||||
} else {
|
||||
if(sel.anchorNode.nodeType === 1) start = start.nextSibling;
|
||||
if(sel.focusNode.nodeType === 3 && sel.focusOffset === 0) end = end.previousSibling;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return [start, end];
|
||||
},
|
||||
|
||||
isCaretAtBlockEnd: function() {
|
||||
if(this.isCaretAtEmptyBlock()) return true;
|
||||
if(this.hasSelection()) return false;
|
||||
|
||||
var node = this.getCurrentBlockElement();
|
||||
var marker = this.pushMarker();
|
||||
|
||||
var isTrue = false;
|
||||
while (node = this.getLastChild(node)) {
|
||||
var nodeValue = node.nodeValue;
|
||||
|
||||
if (node === marker) {
|
||||
isTrue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.popMarker();
|
||||
return isTrue;
|
||||
},
|
||||
|
||||
getBlockElementAtSelectionStart: function() {
|
||||
var block = this.getParentBlockElementOf(this.sel().anchorNode);
|
||||
|
||||
// find bottom-most first block child
|
||||
while(this.tree.isBlockContainer(block) && block.firstChild && this.tree.isBlock(block.firstChild)) {
|
||||
block = block.firstChild;
|
||||
}
|
||||
|
||||
return block;
|
||||
},
|
||||
|
||||
getBlockElementAtSelectionEnd: function() {
|
||||
var block = this.getParentBlockElementOf(this.sel().focusNode);
|
||||
|
||||
// find bottom-most last block child
|
||||
while(this.tree.isBlockContainer(block) && block.lastChild && this.tree.isBlock(block.lastChild)) {
|
||||
block = block.lastChild;
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
});
|
||||
63
modules/editor/skins/xquared/javascripts/rdom/Webkit.js
Normal file
63
modules/editor/skins/xquared/javascripts/rdom/Webkit.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @requires Xquared.js
|
||||
* @requires rdom/W3.js
|
||||
*/
|
||||
xq.rdom.Webkit = xq.Class(xq.rdom.W3,
|
||||
/**
|
||||
* @name xq.rdom.Webkit
|
||||
* @lends xq.rdom.Webkit.prototype
|
||||
* @extends xq.rdom.Base
|
||||
* @constructor
|
||||
*/
|
||||
{
|
||||
makePlaceHolder: function() {
|
||||
var holder = this.createElement("BR");
|
||||
holder.className = "webkit-block-placeholder";
|
||||
return holder;
|
||||
},
|
||||
|
||||
makePlaceHolderString: function() {
|
||||
return '<br class="webkit-block-placeholder" />';
|
||||
},
|
||||
|
||||
makeEmptyParagraph: function() {
|
||||
return this.createElementFromHtml('<p><br class="webkit-block-placeholder" /></p>');
|
||||
},
|
||||
|
||||
isPlaceHolder: function(node) {
|
||||
return node.className === "webkit-block-placeholder";
|
||||
},
|
||||
|
||||
selectElement: function(element, entireElement) {
|
||||
if(!element) throw "[element] is null";
|
||||
if(element.nodeType !== 1) throw "[element] is not an element";
|
||||
|
||||
var rng = this.rng() || this.getDoc().createRange();
|
||||
if(entireElement) {
|
||||
rng.selectNode(element);
|
||||
} else {
|
||||
rng.selectNodeContents(element);
|
||||
}
|
||||
|
||||
this._setSelectionByRange(rng);
|
||||
},
|
||||
|
||||
getSelectionAsHtml: function() {
|
||||
var container = this.createElement("div");
|
||||
var rng = this.rng();
|
||||
var contents = this.rng().cloneContents();
|
||||
if(contents) container.appendChild(contents);
|
||||
return container.innerHTML;
|
||||
},
|
||||
|
||||
collapseSelection: function(toStart) {
|
||||
var rng = this.rng();
|
||||
rng.collapse(toStart);
|
||||
this._setSelectionByRange(rng);
|
||||
},
|
||||
|
||||
_setSelectionByRange: function(rng) {
|
||||
var sel = this.sel();
|
||||
sel.setBaseAndExtent(rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset);
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue