525 lines
15 KiB
JavaScript
525 lines
15 KiB
JavaScript
|
//>>built
|
||
|
define("dojox/editor/plugins/Blockquote", [
|
||
|
"dojo",
|
||
|
"dijit",
|
||
|
"dojox",
|
||
|
"dijit/_editor/range",
|
||
|
"dijit/_editor/selection",
|
||
|
"dijit/_editor/_Plugin",
|
||
|
"dijit/form/ToggleButton",
|
||
|
"dojo/_base/connect",
|
||
|
"dojo/_base/declare",
|
||
|
"dojo/i18n",
|
||
|
"dojo/i18n!dojox/editor/plugins/nls/Blockquote"
|
||
|
], function(dojo, dijit, dojox) {
|
||
|
|
||
|
dojo.declare("dojox.editor.plugins.Blockquote",dijit._editor._Plugin,{
|
||
|
// summary:
|
||
|
// This plugin provides Blockquote cabability to the editor.
|
||
|
// window/tab
|
||
|
|
||
|
// iconClassPrefix: [const] String
|
||
|
// The CSS class name for the button node icon.
|
||
|
iconClassPrefix: "dijitAdditionalEditorIcon",
|
||
|
|
||
|
_initButton: function(){
|
||
|
// summary:
|
||
|
// Over-ride for creation of the preview button.
|
||
|
this._nlsResources = dojo.i18n.getLocalization("dojox.editor.plugins", "Blockquote");
|
||
|
this.button = new dijit.form.ToggleButton({
|
||
|
label: this._nlsResources["blockquote"],
|
||
|
showLabel: false,
|
||
|
iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "Blockquote",
|
||
|
tabIndex: "-1",
|
||
|
onClick: dojo.hitch(this, "_toggleQuote")
|
||
|
});
|
||
|
},
|
||
|
|
||
|
setEditor: function(editor){
|
||
|
// summary:
|
||
|
// Over-ride for the setting of the editor.
|
||
|
// editor: Object
|
||
|
// The editor to configure for this plugin to use.
|
||
|
this.editor = editor;
|
||
|
this._initButton();
|
||
|
this.connect(this.editor, "onNormalizedDisplayChanged", "updateState");
|
||
|
// We need the custom undo code since we manipulate the dom
|
||
|
// outside of the browser natives and only customUndo really handles
|
||
|
// that. It will incur a performance hit, but should hopefully be
|
||
|
// relatively small.
|
||
|
editor.customUndo = true;
|
||
|
},
|
||
|
|
||
|
_toggleQuote: function(arg){
|
||
|
// summary:
|
||
|
// Function to trigger previewing of the editor document
|
||
|
// tags:
|
||
|
// private
|
||
|
try{
|
||
|
var ed = this.editor;
|
||
|
ed.focus();
|
||
|
|
||
|
var quoteIt = this.button.get("checked");
|
||
|
var sel = dijit.range.getSelection(ed.window);
|
||
|
var range, elem, start, end;
|
||
|
if(sel && sel.rangeCount > 0){
|
||
|
range = sel.getRangeAt(0);
|
||
|
}
|
||
|
if(range){
|
||
|
ed.beginEditing();
|
||
|
if(quoteIt){
|
||
|
// Lets see what we've got as a selection...
|
||
|
var bq, tag;
|
||
|
if(range.startContainer === range.endContainer){
|
||
|
// No selection, just cursor point, we need to see if we're
|
||
|
// in an indentable block, or similar.
|
||
|
if(this._isRootInline(range.startContainer)){
|
||
|
// Text at the 'root' of the document, so we need to gather all of it.,
|
||
|
|
||
|
// First, we need to find the toplevel inline element that is rooted
|
||
|
// to the document 'editNode'
|
||
|
start = range.startContainer;
|
||
|
while(start && start.parentNode !== ed.editNode){
|
||
|
start = start.parentNode;
|
||
|
}
|
||
|
// Now we need to walk up its siblings and look for the first one in the rooting
|
||
|
// that isn't inline or text, as we want to grab all of that for indent.
|
||
|
while(start && start.previousSibling && (
|
||
|
this._isTextElement(start) ||
|
||
|
(start.nodeType === 1 &&
|
||
|
this._isInlineFormat(this._getTagName(start))
|
||
|
))){
|
||
|
start = start.previousSibling;
|
||
|
}
|
||
|
if(start && start.nodeType === 1 &&
|
||
|
!this._isInlineFormat(this._getTagName(start))){
|
||
|
// Adjust slightly, we're one node too far back in this case.
|
||
|
start = start.nextSibling;
|
||
|
}
|
||
|
|
||
|
// Okay, we have a configured start, lets grab everything following it that's
|
||
|
// inline and make it part of the blockquote!
|
||
|
if(start){
|
||
|
bq = ed.document.createElement("blockquote");
|
||
|
dojo.place(bq, start, "after");
|
||
|
bq.appendChild(start);
|
||
|
end = bq.nextSibling;
|
||
|
while(end && (
|
||
|
this._isTextElement(end) ||
|
||
|
(end.nodeType === 1 &&
|
||
|
this._isInlineFormat(this._getTagName(end)))
|
||
|
)){
|
||
|
// Add it.
|
||
|
bq.appendChild(end);
|
||
|
end = bq.nextSibling;
|
||
|
}
|
||
|
}
|
||
|
}else{
|
||
|
// Figure out what to do when not root inline....
|
||
|
var node = range.startContainer;
|
||
|
while ((this._isTextElement(node) ||
|
||
|
this._isInlineFormat(this._getTagName(node))
|
||
|
|| this._getTagName(node) === "li") &&
|
||
|
node !== ed.editNode && node !== ed.document.body){
|
||
|
node = node.parentNode;
|
||
|
}
|
||
|
if(node !== ed.editNode && node !== node.ownerDocument.documentElement){
|
||
|
bq = ed.document.createElement("blockquote");
|
||
|
dojo.place(bq, node, "after");
|
||
|
bq.appendChild(node);
|
||
|
}
|
||
|
}
|
||
|
if(bq){
|
||
|
dojo.withGlobal(ed.window,
|
||
|
"selectElementChildren", dijit._editor.selection, [bq]);
|
||
|
dojo.withGlobal(ed.window,
|
||
|
"collapse", dijit._editor.selection, [true]);
|
||
|
}
|
||
|
}else{
|
||
|
var curNode;
|
||
|
// multi-node select. We need to scan over them.
|
||
|
// Find the two containing nodes at start and end.
|
||
|
// then move the end one node past. Then ... lets see
|
||
|
// what we can blockquote!
|
||
|
start = range.startContainer;
|
||
|
end = range.endContainer;
|
||
|
// Find the non-text nodes.
|
||
|
|
||
|
while(start && this._isTextElement(start) && start.parentNode !== ed.editNode){
|
||
|
start = start.parentNode;
|
||
|
}
|
||
|
|
||
|
// Try to find the end node. We have to check the selection junk
|
||
|
curNode = start;
|
||
|
while(curNode.nextSibling && dojo.withGlobal(ed.window,
|
||
|
"inSelection", dijit._editor.selection, [curNode])){
|
||
|
curNode = curNode.nextSibling;
|
||
|
}
|
||
|
end = curNode;
|
||
|
if(end === ed.editNode || end === ed.document.body){
|
||
|
// Unable to determine real selection end, so just make it
|
||
|
// a single node indent of start + all following inline styles, if
|
||
|
// present, then just exit.
|
||
|
bq = ed.document.createElement("blockquote");
|
||
|
dojo.place(bq, start, "after");
|
||
|
tag = this._getTagName(start);
|
||
|
if(this._isTextElement(start) || this._isInlineFormat(tag)){
|
||
|
// inline element or textnode
|
||
|
// Find and move all inline tags following the one we inserted also into the
|
||
|
// blockquote so we don't split up content funny.
|
||
|
var next = start;
|
||
|
while(next && (
|
||
|
this._isTextElement(next) ||
|
||
|
(next.nodeType === 1 &&
|
||
|
this._isInlineFormat(this._getTagName(next))))){
|
||
|
bq.appendChild(next);
|
||
|
next = bq.nextSibling;
|
||
|
}
|
||
|
}else{
|
||
|
bq.appendChild(start);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Has a definite end somewhere, so lets try to blockquote up to it.
|
||
|
// requires looking at the selections and in some cases, moving nodes
|
||
|
// into separate blockquotes.
|
||
|
end = end.nextSibling;
|
||
|
curNode = start;
|
||
|
while(curNode && curNode !== end){
|
||
|
if(curNode.nodeType === 1){
|
||
|
tag = this._getTagName(curNode);
|
||
|
if(tag !== "br"){
|
||
|
if(!window.getSelection){
|
||
|
// IE sometimes inserts blank P tags, which we want to skip
|
||
|
// as they end up blockquoted, which messes up layout.
|
||
|
if(tag === "p" && this._isEmpty(curNode)){
|
||
|
curNode = curNode.nextSibling;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
if(this._isInlineFormat(tag)){
|
||
|
// inline tag.
|
||
|
if(!bq){
|
||
|
bq = ed.document.createElement("blockquote");
|
||
|
dojo.place(bq, curNode, "after");
|
||
|
bq.appendChild(curNode);
|
||
|
}else{
|
||
|
bq.appendChild(curNode);
|
||
|
}
|
||
|
curNode = bq;
|
||
|
}else{
|
||
|
if(bq){
|
||
|
if(this._isEmpty(bq)){
|
||
|
bq.parentNode.removeChild(bq);
|
||
|
}
|
||
|
}
|
||
|
bq = ed.document.createElement("blockquote");
|
||
|
dojo.place(bq, curNode, "after");
|
||
|
bq.appendChild(curNode);
|
||
|
curNode = bq;
|
||
|
}
|
||
|
}
|
||
|
}else if(this._isTextElement(curNode)){
|
||
|
if(!bq){
|
||
|
bq = ed.document.createElement("blockquote");
|
||
|
dojo.place(bq, curNode, "after");
|
||
|
bq.appendChild(curNode);
|
||
|
}else{
|
||
|
bq.appendChild(curNode);
|
||
|
}
|
||
|
curNode = bq;
|
||
|
}
|
||
|
curNode = curNode.nextSibling;
|
||
|
}
|
||
|
// Okay, check the last bq, remove it if no content.
|
||
|
if(bq){
|
||
|
if(this._isEmpty(bq)){
|
||
|
bq.parentNode.removeChild(bq);
|
||
|
}else{
|
||
|
dojo.withGlobal(ed.window,
|
||
|
"selectElementChildren", dijit._editor.selection, [bq]);
|
||
|
dojo.withGlobal(ed.window,
|
||
|
"collapse", dijit._editor.selection, [true]);
|
||
|
}
|
||
|
bq = null;
|
||
|
}
|
||
|
}
|
||
|
}else{
|
||
|
var found = false;
|
||
|
if(range.startContainer === range.endContainer){
|
||
|
elem = range.endContainer;
|
||
|
// Okay, now see if we can find one of the formatting types we're in.
|
||
|
while(elem && elem !== ed.editNode && elem !== ed.document.body){
|
||
|
var tg = elem.tagName?elem.tagName.toLowerCase():"";
|
||
|
if(tg === "blockquote"){
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
elem = elem.parentNode;
|
||
|
}
|
||
|
if(found){
|
||
|
var lastChild;
|
||
|
while(elem.firstChild){
|
||
|
lastChild = elem.firstChild;
|
||
|
dojo.place(lastChild, elem, "before");
|
||
|
}
|
||
|
elem.parentNode.removeChild(elem);
|
||
|
if(lastChild){
|
||
|
dojo.withGlobal(ed.window,
|
||
|
"selectElementChildren", dijit._editor.selection, [lastChild]);
|
||
|
dojo.withGlobal(ed.window,
|
||
|
"collapse", dijit._editor.selection, [true]);
|
||
|
}
|
||
|
}
|
||
|
}else{
|
||
|
// Multi-select! Gotta find all the blockquotes contained within the selection area.
|
||
|
start = range.startContainer;
|
||
|
end = range.endContainer;
|
||
|
while(start && this._isTextElement(start) && start.parentNode !== ed.editNode){
|
||
|
start = start.parentNode;
|
||
|
}
|
||
|
var selectedNodes = [];
|
||
|
var cNode = start;
|
||
|
while(cNode && cNode.nextSibling && dojo.withGlobal(ed.window,
|
||
|
"inSelection", dijit._editor.selection, [cNode])){
|
||
|
if(cNode.parentNode && this._getTagName(cNode.parentNode) === "blockquote"){
|
||
|
cNode = cNode.parentNode;
|
||
|
}
|
||
|
selectedNodes.push(cNode);
|
||
|
cNode = cNode.nextSibling;
|
||
|
}
|
||
|
|
||
|
// Find all the blocknodes now that we know the selection area.
|
||
|
var bnNodes = this._findBlockQuotes(selectedNodes);
|
||
|
while(bnNodes.length){
|
||
|
var bn = bnNodes.pop();
|
||
|
if(bn.parentNode){
|
||
|
// Make sure we haven't seen this before and removed it.
|
||
|
while(bn.firstChild){
|
||
|
dojo.place(bn.firstChild, bn, "before");
|
||
|
}
|
||
|
bn.parentNode.removeChild(bn);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ed.endEditing();
|
||
|
}
|
||
|
ed.onNormalizedDisplayChanged();
|
||
|
}catch(e){ /* Squelch */ }
|
||
|
},
|
||
|
|
||
|
updateState: function(){
|
||
|
// summary:
|
||
|
// Overrides _Plugin.updateState(). This controls whether or not the current
|
||
|
// cursor position should toggle on the quote button or not.
|
||
|
// tags:
|
||
|
// protected
|
||
|
var ed = this.editor;
|
||
|
var disabled = this.get("disabled");
|
||
|
|
||
|
if(!ed || !ed.isLoaded){ return; }
|
||
|
if(this.button){
|
||
|
this.button.set("disabled", disabled);
|
||
|
if(disabled){
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Some browsers (WebKit) doesn't actually get the tag info right.
|
||
|
// So ... lets check it manually.
|
||
|
var elem;
|
||
|
var found = false;
|
||
|
|
||
|
// Try to find the ansestor element (and see if it is blockquote)
|
||
|
var sel = dijit.range.getSelection(ed.window);
|
||
|
if(sel && sel.rangeCount > 0){
|
||
|
var range = sel.getRangeAt(0);
|
||
|
if(range){
|
||
|
elem = range.endContainer;
|
||
|
}
|
||
|
}
|
||
|
// Okay, now see if we can find one of the formatting types we're in.
|
||
|
while(elem && elem !== ed.editNode && elem !== ed.document){
|
||
|
var tg = elem.tagName?elem.tagName.toLowerCase():"";
|
||
|
if(tg === "blockquote"){
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
elem = elem.parentNode;
|
||
|
}
|
||
|
// toggle whether or not the current selection is blockquoted.
|
||
|
this.button.set("checked", found);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_findBlockQuotes: function(nodeList){
|
||
|
// summary:
|
||
|
// function to find a ll the blocknode elements in a collection of
|
||
|
// nodes
|
||
|
// nodeList:
|
||
|
// The list of nodes.
|
||
|
// tags:
|
||
|
// private
|
||
|
var bnList = [];
|
||
|
if(nodeList){
|
||
|
var i;
|
||
|
for(i = 0; i < nodeList.length; i++){
|
||
|
var node = nodeList[i];
|
||
|
if(node.nodeType === 1){
|
||
|
if(this._getTagName(node) === "blockquote"){
|
||
|
bnList.push(node);
|
||
|
}
|
||
|
if(node.childNodes && node.childNodes.length > 0){
|
||
|
bnList = bnList.concat(this._findBlockQuotes(node.childNodes));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bnList;
|
||
|
},
|
||
|
|
||
|
/*****************************************************************/
|
||
|
/* Functions borrowed from NormalizeIndentOutdent */
|
||
|
/*****************************************************************/
|
||
|
|
||
|
_getTagName: function(node){
|
||
|
// summary:
|
||
|
// Internal function to get the tag name of an element
|
||
|
// if any.
|
||
|
// node:
|
||
|
// The node to look at.
|
||
|
// tags:
|
||
|
// private
|
||
|
var tag = "";
|
||
|
if(node && node.nodeType === 1){
|
||
|
tag = node.tagName?node.tagName.toLowerCase():"";
|
||
|
}
|
||
|
return tag;
|
||
|
},
|
||
|
|
||
|
_isRootInline: function(node){
|
||
|
// summary:
|
||
|
// This functions tests whether an indicated node is in root as inline
|
||
|
// or rooted inline elements in the page.
|
||
|
// node:
|
||
|
// The node to start at.
|
||
|
// tags:
|
||
|
// private
|
||
|
var ed = this.editor;
|
||
|
if(this._isTextElement(node) && node.parentNode === ed.editNode){
|
||
|
return true;
|
||
|
}else if(node.nodeType === 1 && this._isInlineFormat(node) && node.parentNode === ed.editNode){
|
||
|
return true;
|
||
|
}else if(this._isTextElement(node) && this._isInlineFormat(this._getTagName(node.parentNode))){
|
||
|
node = node.parentNode;
|
||
|
while(node && node !== ed.editNode && this._isInlineFormat(this._getTagName(node))){
|
||
|
node = node.parentNode;
|
||
|
}
|
||
|
if(node === ed.editNode){
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
_isTextElement: function(node){
|
||
|
// summary:
|
||
|
// Helper function to check for text nodes.
|
||
|
// node:
|
||
|
// The node to check.
|
||
|
// tags:
|
||
|
// private
|
||
|
if(node && node.nodeType === 3 || node.nodeType === 4){
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
_isEmpty: function(node){
|
||
|
// summary:
|
||
|
// Internal function to determine if a node is 'empty'
|
||
|
// Eg, contains only blank text. Used to determine if
|
||
|
// an empty list element should be removed or not.
|
||
|
// node:
|
||
|
// The node to check.
|
||
|
// tags:
|
||
|
// private
|
||
|
if(node.childNodes){
|
||
|
var empty = true;
|
||
|
var i;
|
||
|
for(i = 0; i < node.childNodes.length; i++){
|
||
|
var n = node.childNodes[i];
|
||
|
if(n.nodeType === 1){
|
||
|
if(this._getTagName(n) === "p"){
|
||
|
if(!dojo.trim(n.innerHTML)){
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
empty = false;
|
||
|
break;
|
||
|
}else if(this._isTextElement(n)){
|
||
|
// Check for empty text.
|
||
|
var nv = dojo.trim(n.nodeValue);
|
||
|
if(nv && nv !==" " && nv !== "\u00A0"){
|
||
|
empty = false;
|
||
|
break;
|
||
|
}
|
||
|
}else{
|
||
|
empty = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return empty;
|
||
|
}else{
|
||
|
return true;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_isInlineFormat: function(tag){
|
||
|
// summary:
|
||
|
// Function to determine if the current tag is an inline
|
||
|
// element that does formatting, as we don't want to
|
||
|
// break/indent around it, as it can screw up text.
|
||
|
// tag:
|
||
|
// The tag to examine
|
||
|
// tags:
|
||
|
// private
|
||
|
switch(tag){
|
||
|
case "a":
|
||
|
case "b":
|
||
|
case "strong":
|
||
|
case "s":
|
||
|
case "strike":
|
||
|
case "i":
|
||
|
case "u":
|
||
|
case "em":
|
||
|
case "sup":
|
||
|
case "sub":
|
||
|
case "span":
|
||
|
case "font":
|
||
|
case "big":
|
||
|
case "cite":
|
||
|
case "q":
|
||
|
case "img":
|
||
|
case "small":
|
||
|
return true;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Register this plugin.
|
||
|
dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
|
||
|
if(o.plugin){ return; }
|
||
|
var name = o.args.name.toLowerCase();
|
||
|
if(name === "blockquote"){
|
||
|
o.plugin = new dojox.editor.plugins.Blockquote({});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return dojox.editor.plugins.Blockquote;
|
||
|
|
||
|
});
|