Changeset 672
- Timestamp:
- 01/18/07 15:13:28 (6 years ago)
- Location:
- trunk
- Files:
-
- 4 modified
-
XinhaCore.js (modified) (11 diffs)
-
examples/testbed.html (modified) (1 diff)
-
functionsIE.js (modified) (8 diffs)
-
functionsMozilla.js (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/XinhaCore.js
r671 r672 374 374 // see if the text just typed looks like a URL, or email address 375 375 // and link it appropriatly 376 this.convertUrlsToLinks = false;376 this.convertUrlsToLinks = true; 377 377 378 378 // size of color picker cells … … 1446 1446 var i; 1447 1447 var editor = this; // we'll need "this" in some nested functions 1448 1449 // Now load a specific browser plugin which will implement the above for us. 1450 if (Xinha.is_ie) 1451 { 1452 if ( typeof InternetExplorer == 'undefined' ) 1453 { 1454 Xinha.loadPlugin("InternetExplorer", function() { editor.generate(); }, _editor_url + 'functionsIE.js' ); 1455 return false; 1456 } 1457 editor._browserSpecificPlugin = editor.registerPlugin('InternetExplorer'); 1458 } 1459 else 1460 { 1461 if ( typeof Gecko == 'undefined' ) 1462 { 1463 Xinha.loadPlugin("Gecko", function() { editor.generate(); }, _editor_url + 'functionsMozilla.js' ); 1464 return false; 1465 } 1466 editor._browserSpecificPlugin = editor.registerPlugin('Gecko'); 1467 } 1468 1448 1469 this.setLoadingMessage('Generate Xinha object'); 1449 1470 … … 2529 2550 }; 2530 2551 2531 Xinha.loadPlugin = function(pluginName, callback )2552 Xinha.loadPlugin = function(pluginName, callback, plugin_file) 2532 2553 { 2533 2554 // @todo : try to avoid the use of eval() … … 2542 2563 } 2543 2564 2544 var dir = this.getPluginDir(pluginName); 2545 var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, function (str, l1, l2, l3) { return l1 + "-" + l2.toLowerCase() + l3; }).toLowerCase() + ".js"; 2546 var plugin_file = dir + "/" + plugin; 2547 2565 if(!plugin_file) 2566 { 2567 var dir = this.getPluginDir(pluginName); 2568 var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, function (str, l1, l2, l3) { return l1 + "-" + l2.toLowerCase() + l3; }).toLowerCase() + ".js"; 2569 plugin_file = dir + "/" + plugin; 2570 } 2571 2548 2572 Xinha._loadback(plugin_file, callback ? function() { callback(pluginName); } : null); 2549 2573 return false; … … 2551 2575 2552 2576 Xinha._pluginLoadStatus = {}; 2553 Xinha._browserSpecificFunctionsLoaded = false; 2577 2554 2578 Xinha.loadPlugins = function(plugins, callbackIfNotReady) 2555 2579 { … … 2558 2582 var nuPlugins = Xinha.cloneObject(plugins); 2559 2583 2560 if ( !Xinha._browserSpecificFunctionsLoaded )2561 {2562 if (Xinha.is_ie)2563 {2564 Xinha._loadback(_editor_url + "functionsIE.js",callbackIfNotReady);2565 }2566 else2567 {2568 Xinha._loadback(_editor_url + "functionsMozilla.js",callbackIfNotReady);2569 }2570 return false;2571 }2572 2584 while ( nuPlugins.length ) 2573 2585 { … … 3853 3865 { 3854 3866 var editor = this; 3855 var keyEvent = (Xinha.is_ie && ev.type == "keydown") || (!Xinha.is_ie && ev.type == "keypress");3867 var keyEvent = this.isKeyEvent(ev); 3856 3868 3857 3869 //call events of textarea … … 3860 3872 editor._textArea['on'+ev.type](); 3861 3873 } 3862 3863 if ( Xinha.is_gecko && keyEvent && ev.ctrlKey && this._unLink && this._unlinkOnUndo ) 3864 { 3865 if ( String.fromCharCode(ev.charCode).toLowerCase() == 'z' ) 3866 { 3867 Xinha._stopEvent(ev); 3868 this._unLink(); 3869 editor.updateToolbar(); 3870 return; 3871 } 3872 } 3873 3874 3874 3875 if ( keyEvent ) 3875 3876 { 3877 // Run the ordinary plugins first 3876 3878 for ( var i in editor.plugins ) 3877 3879 { 3878 3880 var plugin = editor.plugins[i].instance; 3881 3882 if ( plugin == editor._browserSpecificPlugin) continue; 3883 3879 3884 if ( plugin && typeof plugin.onKeyPress == "function" ) 3880 3885 { … … 3885 3890 } 3886 3891 } 3892 3893 // Now run the browser specific one 3894 if(typeof editor._browserSpecificPlugin.onKeyPress == "function") 3895 { 3896 if(editor._browserSpecificPlugin.onKeyPress(ev)) 3897 { 3898 return false; 3899 } 3900 } 3887 3901 } 3888 3902 … … 3891 3905 this._shortCuts(ev); 3892 3906 } 3893 else if ( keyEvent && Xinha.is_gecko ) 3894 { 3895 this.mozKey( ev, keyEvent ); 3896 } 3897 3907 3898 3908 // update the toolbar state after some time 3899 3909 if ( editor._timerToolbar ) … … 5820 5830 } 5821 5831 }; 5822 // backward compatibility 5832 5833 5834 // The following methods may be over-ridden or extended by the browser specific 5835 // javascript files. 5836 5837 5838 /** Insert a node at the current selection point. 5839 * @param toBeInserted DomNode 5840 */ 5841 5842 Xinha.prototype.insertNodeAtSelection = function(toBeInserted) { Xinha.notImplemented("insertNodeAtSelection"); } 5843 5844 /** Get the parent element of the supplied or current selection. 5845 * @param sel optional selection as returned by getSelection 5846 * @returns DomNode 5847 */ 5848 5849 Xinha.prototype.getParentElement = function(sel) { Xinha.notImplemented("getParentElement"); } 5850 5851 /** 5852 * Returns the selected element, if any. That is, 5853 * the element that you have last selected in the "path" 5854 * at the bottom of the editor, or a "control" (eg image) 5855 * 5856 * @returns null | DomNode 5857 */ 5858 5859 Xinha.prototype.activeElement = function(sel) { Xinha.notImplemented("activeElement"); } 5860 5861 /** 5862 * Determines if the given selection is empty (collapsed). 5863 * @param selection Selection object as returned by getSelection 5864 * @returns true|false 5865 */ 5866 5867 Xinha.prototype.selectionEmpty = function(sel) { Xinha.notImplemented("selectionEmpty"); } 5868 5869 /** 5870 * Selects the contents of the given node. If the node is a "control" type element, (image, form input, table) 5871 * the node itself is selected for manipulation. 5872 * 5873 * @param node DomNode 5874 * @param pos Set to a numeric position inside the node to collapse the cursor here if possible. 5875 */ 5876 5877 Xinha.prototype.selectNodeContents = function(node,pos) { Xinha.notImplemented("selectNodeContents"); } 5878 5879 /** Insert HTML at the current position, deleting the selection if any. 5880 * 5881 * @param html string 5882 */ 5883 5884 Xinha.prototype.insertHTML = function(html) { Xinha.notImplemented("insertHTML"); } 5885 5886 /** Get the HTML of the current selection. HTML returned has not been passed through outwardHTML. 5887 * 5888 * @returns string 5889 */ 5890 Xinha.prototype.getSelectedHTML = function() { Xinha.notImplemented("getSelectedHTML"); } 5891 5892 /** Get a Selection object of the current selection. Note that selection objects are browser specific. 5893 * 5894 * @returns Selection 5895 */ 5896 5897 Xinha.prototype.getSelection = function() { Xinha.notImplemented("getSelection"); } 5898 5899 /** Create a Range object from the given selection. Note that range objects are browser specific. 5900 * 5901 * @param sel Selection object (see getSelection) 5902 * @returns Range 5903 */ 5904 5905 Xinha.prototype.createRange = function(sel) { Xinha.notImplemented("createRange"); } 5906 5907 /** Determine if the given event object is a keydown/press event. 5908 * 5909 * @param event Event 5910 * @returns true|false 5911 */ 5912 5913 Xinha.prototype.isKeyEvent = function(event) { Xinha.notImplemented("isKeyEvent"); } 5914 5915 /** Return the HTML string of the given Element, including the Element. 5916 * 5917 * @param element HTML Element DomNode 5918 * @returns string 5919 */ 5920 5921 Xinha.getOuterHTML = function(element) { Xinha.notImplemented("getOuterHTML"); } 5922 5923 5924 5925 // Compatability - all these names are deprecated and will be removed in a future version 5926 Xinha.prototype._activeElement = function(sel) { return this.activeElement(sel); } 5927 Xinha.prototype._selectionEmpty = function(sel) { return this.selectionEmpty(sel); } 5928 Xinha.prototype._getSelection = function() { return this.getSelection(); } 5929 Xinha.prototype._createRange = function(sel) { return this.createRange(sel); } 5823 5930 HTMLArea = Xinha; 5931 5824 5932 Xinha.init(); 5825 5933 Xinha.addDom0Event(window,'unload',Xinha.collectGarbageForIE); 5934 5935 Xinha.notImplemented = function(methodName) 5936 { 5937 throw new Error("Method Not Implemented", "Part of Xinha has tried to call the " + methodName + " method which has not been implemented."); 5938 } -
trunk/examples/testbed.html
r664 r672 55 55 xinha_plugins = xinha_plugins ? xinha_plugins : 56 56 [ 57 'CharacterMap' 57 'CharacterMap', 'SpellChecker', 'Linker' 58 58 ]; 59 59 // THIS BIT OF JAVASCRIPT LOADS THE PLUGINS, NO TOUCHING :) -
trunk/functionsIE.js
r649 r672 1 /** Returns a node after which we can insert other nodes, in the current 2 * selection. The selection is removed. It splits a text node, if needed. 3 */ 1 2 /*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:-- 3 -- Xinha (is not htmlArea) - http://xinha.gogo.co.nz/ 4 -- 5 -- Use of Xinha is granted by the terms of the htmlArea License (based on 6 -- BSD license) please read license.txt in this package for details. 7 -- 8 -- Xinha was originally based on work by Mihai Bazon which is: 9 -- Copyright (c) 2003-2004 dynarch.com. 10 -- Copyright (c) 2002-2003 interactivetools.com, inc. 11 -- This copyright notice MUST stay intact for use. 12 -- 13 -- This is the Internet Explorer compatability plugin, part of the 14 -- Xinha core. 15 -- 16 -- The file is loaded as a special plugin by the Xinha Core when 17 -- Xinha is being run under an Internet Explorer based browser. 18 -- 19 -- It provides implementation and specialisation for various methods 20 -- in the core where different approaches per browser are required. 21 -- 22 -- Design Notes:: 23 -- Most methods here will simply be overriding Xinha.prototype.<method> 24 -- and should be called that, but methods specific to IE should 25 -- be a part of the InternetExplorer.prototype, we won't trample on 26 -- namespace that way. 27 -- 28 -- $HeadURL$ 29 -- $LastChangedDate$ 30 -- $LastChangedRevision$ 31 -- $LastChangedBy$ 32 --------------------------------------------------------------------------*/ 33 34 InternetExplorer._pluginInfo = { 35 name : "Internet Explorer", 36 origin : "Xinha Core", 37 version : "$LastChangedRevision$", 38 developer : "The Xinha Core Developer Team", 39 developer_url : "$HeadURL$", 40 license : "htmlArea" 41 }; 42 43 function InternetExplorer(editor) { 44 this.editor = editor; 45 editor.InternetExplorer = this; // So we can do my_editor.InternetExplorer.doSomethingIESpecific(); 46 } 47 48 /** Allow Internet Explorer to handle some key events in a special way. 49 */ 50 51 InternetExplorer.prototype.onKeyPress = function(ev) 52 { 53 switch(ev.keyCode) 54 { 55 case 8: // KEY backspace 56 case 46: // KEY delete 57 { 58 if(this.handleBackspace()) 59 { 60 Xinha._stopEvent(ev); 61 return true; 62 } 63 } 64 break; 65 } 66 67 return false; 68 } 69 70 /** When backspace is hit, the IE onKeyPress will execute this method. 71 * It preserves links when you backspace over them and apparently 72 * deletes control elements (tables, images, form fields) in a better 73 * way. 74 * 75 * @returns true|false True when backspace has been handled specially 76 * false otherwise (should pass through). 77 */ 78 79 InternetExplorer.prototype.handleBackspace = function() 80 { 81 var editor = this.editor; 82 var sel = editor._getSelection(); 83 if ( sel.type == 'Control' ) 84 { 85 var elm = editor._activeElement(sel); 86 Xinha.removeFromParent(elm); 87 return true; 88 } 89 90 // This bit of code preseves links when you backspace over the 91 // endpoint of the link in IE. Without it, if you have something like 92 // link_here | 93 // where | is the cursor, and backspace over the last e, then the link 94 // will de-link, which is a bit tedious 95 var range = editor._createRange(sel); 96 var r2 = range.duplicate(); 97 r2.moveStart("character", -1); 98 var a = r2.parentElement(); 99 // @fixme: why using again a regex to test a single string ??? 100 if ( a != range.parentElement() && ( /^a$/i.test(a.tagName) ) ) 101 { 102 r2.collapse(true); 103 r2.moveEnd("character", 1); 104 r2.pasteHTML(''); 105 r2.select(); 106 return true; 107 } 108 }; 109 110 /*--------------------------------------------------------------------------*/ 111 /*------- IMPLEMENTATION OF THE ABSTRACT "Xinha.prototype" METHODS ---------*/ 112 /*--------------------------------------------------------------------------*/ 113 114 /** Insert a node at the current selection point. 115 * @param toBeInserted DomNode 116 */ 117 4 118 Xinha.prototype.insertNodeAtSelection = function(toBeInserted) 5 119 { 6 return null; // this function not yet used for IE <FIXME> 7 }; 8 9 // Returns the deepest node that contains both endpoints of the selection. 120 Xinha.notImplemented('insertNodeAtSelection'); 121 }; 122 123 124 /** Get the parent element of the supplied or current selection. 125 * @param sel optional selection as returned by getSelection 126 * @returns DomNode 127 */ 128 10 129 Xinha.prototype.getParentElement = function(sel) 11 130 { … … 55 174 * at the bottom of the editor, or a "control" (eg image) 56 175 * 57 * @returns null | element 58 */ 176 * @returns null | DomNode 177 */ 178 59 179 Xinha.prototype._activeElement = function(sel) 60 180 { … … 104 224 }; 105 225 106 Xinha.prototype._selectionEmpty = function(sel) 226 /** 227 * Determines if the given selection is empty (collapsed). 228 * @param selection Selection object as returned by getSelection 229 * @returns true|false 230 */ 231 232 Xinha.prototype.selectionEmpty = function(sel) 107 233 { 108 234 if ( !sel ) … … 114 240 }; 115 241 116 // Selects the contents inside the given node 242 /** 243 * Selects the contents of the given node. If the node is a "control" type element, (image, form input, table) 244 * the node itself is selected for manipulation. 245 * 246 * @param node DomNode 247 * @param pos Set to a numeric position inside the node to collapse the cursor here if possible. 248 */ 249 117 250 Xinha.prototype.selectNodeContents = function(node, pos) 118 251 { … … 136 269 }; 137 270 138 /** Call this function to insert HTML code at the current position. It deletes 139 * the selection, if any. 140 */ 271 /** Insert HTML at the current position, deleting the selection if any. 272 * 273 * @param html string 274 */ 275 141 276 Xinha.prototype.insertHTML = function(html) 142 277 { … … 148 283 149 284 150 // Retrieve the selected block 285 /** Get the HTML of the current selection. HTML returned has not been passed through outwardHTML. 286 * 287 * @returns string 288 */ 289 151 290 Xinha.prototype.getSelectedHTML = function() 152 291 { … … 166 305 return ''; 167 306 }; 168 169 Xinha.prototype.checkBackspace = function() 170 { 171 var sel = this._getSelection(); 172 if ( sel.type == 'Control' ) 173 { 174 var elm = this._activeElement(sel); 175 Xinha.removeFromParent(elm); 176 return true; 177 } 178 179 // This bit of code preseves links when you backspace over the 180 // endpoint of the link in IE. Without it, if you have something like 181 // link_here | 182 // where | is the cursor, and backspace over the last e, then the link 183 // will de-link, which is a bit tedious 184 var range = this._createRange(sel); 185 var r2 = range.duplicate(); 186 r2.moveStart("character", -1); 187 var a = r2.parentElement(); 188 // @fixme: why using again a regex to test a single string ??? 189 if ( a != range.parentElement() && ( /^a$/i.test(a.tagName) ) ) 190 { 191 r2.collapse(true); 192 r2.moveEnd("character", 1); 193 r2.pasteHTML(''); 194 r2.select(); 195 return true; 196 } 197 }; 198 199 // returns the current selection object 200 Xinha.prototype._getSelection = function() 307 308 /** Get a Selection object of the current selection. Note that selection objects are browser specific. 309 * 310 * @returns Selection 311 */ 312 313 Xinha.prototype.getSelection = function() 201 314 { 202 315 return this._doc.selection; 203 316 }; 204 317 205 // returns a range for the current selection 206 Xinha.prototype._createRange = function(sel) 318 /** Create a Range object from the given selection. Note that range objects are browser specific. 319 * 320 * @param sel Selection object (see getSelection) 321 * @returns Range 322 */ 323 324 Xinha.prototype.createRange = function(sel) 207 325 { 208 326 return sel.createRange(); 209 327 }; 210 328 329 /** Determine if the given event object is a keydown/press event. 330 * 331 * @param event Event 332 * @returns true|false 333 */ 334 335 Xinha.prototype.isKeyEvent = function(event) 336 { 337 return event.type == "keydown"; 338 } 339 340 /** Return the HTML string of the given Element, including the Element. 341 * 342 * @param element HTML Element DomNode 343 * @returns string 344 */ 345 211 346 Xinha.getOuterHTML = function(element) 212 347 { … … 214 349 }; 215 350 216 //What is this supposed to do??? it's never used217 Xinha.prototype._formatBlock = function(block_format)218 {219 220 };221 222 Xinha._browserSpecificFunctionsLoaded = true; -
trunk/functionsMozilla.js
r649 r672 1 /** Returns a node after which we can insert other nodes, in the current 2 * selection. The selection is removed. It splits a text node, if needed. 3 */ 4 Xinha.prototype.insertNodeAtSelection = function(toBeInserted) 5 { 6 var sel = this._getSelection(); 7 var range = this._createRange(sel); 8 // remove the current selection 9 sel.removeAllRanges(); 10 range.deleteContents(); 11 var node = range.startContainer; 12 var pos = range.startOffset; 13 var selnode = toBeInserted; 14 switch ( node.nodeType ) 15 { 16 case 3: // Node.TEXT_NODE 17 // we have to split it at the caret position. 18 if ( toBeInserted.nodeType == 3 ) 19 { 20 // do optimized insertion 21 node.insertData(pos, toBeInserted.data); 22 range = this._createRange(); 23 range.setEnd(node, pos + toBeInserted.length); 24 range.setStart(node, pos + toBeInserted.length); 25 sel.addRange(range); 26 } 27 else 28 { 29 node = node.splitText(pos); 30 if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ ) 31 { 32 selnode = selnode.firstChild; 33 } 34 node.parentNode.insertBefore(toBeInserted, node); 35 this.selectNodeContents(selnode); 36 this.updateToolbar(); 37 } 38 break; 39 case 1: // Node.ELEMENT_NODE 40 if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ ) 41 { 42 selnode = selnode.firstChild; 43 } 44 node.insertBefore(toBeInserted, node.childNodes[pos]); 45 this.selectNodeContents(selnode); 46 this.updateToolbar(); 47 break; 48 } 49 }; 50 51 // Returns the deepest node that contains both endpoints of the selection. 52 Xinha.prototype.getParentElement = function(sel) 53 { 54 if ( typeof sel == 'undefined' ) 55 { 56 sel = this._getSelection(); 57 } 58 var range = this._createRange(sel); 59 try 60 { 61 var p = range.commonAncestorContainer; 62 if ( !range.collapsed && range.startContainer == range.endContainer && 63 range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes() ) 64 { 65 p = range.startContainer.childNodes[range.startOffset]; 66 } 67 /* 68 alert(range.startContainer + ":" + range.startOffset + "\n" + 69 range.endContainer + ":" + range.endOffset); 70 */ 71 while ( p.nodeType == 3 ) 72 { 73 p = p.parentNode; 74 } 75 return p; 76 } 77 catch (ex) 78 { 79 return null; 80 } 81 }; 82 83 /** 84 * Returns the selected element, if any. That is, 85 * the element that you have last selected in the "path" 86 * at the bottom of the editor, or a "control" (eg image) 87 * 88 * @returns null | element 89 */ 90 Xinha.prototype._activeElement = function(sel) 91 { 92 if ( ( sel === null ) || this._selectionEmpty(sel) ) 93 { 94 return null; 95 } 96 97 // For Mozilla we just see if the selection is not collapsed (something is selected) 98 // and that the anchor (start of selection) is an element. This might not be totally 99 // correct, we possibly should do a simlar check to IE? 100 if ( !sel.isCollapsed ) 101 { 102 if ( sel.anchorNode.childNodes.length > sel.anchorOffset && sel.anchorNode.childNodes[sel.anchorOffset].nodeType == 1 ) 103 { 104 return sel.anchorNode.childNodes[sel.anchorOffset]; 105 } 106 else if ( sel.anchorNode.nodeType == 1 ) 107 { 108 return sel.anchorNode; 109 } 110 else 111 { 112 return null; // return sel.anchorNode.parentNode; 113 } 114 } 115 return null; 116 }; 117 118 Xinha.prototype._selectionEmpty = function(sel) 119 { 120 if ( !sel ) 121 { 122 return true; 123 } 124 125 if ( typeof sel.isCollapsed != 'undefined' ) 126 { 127 return sel.isCollapsed; 128 } 129 130 return true; 131 }; 132 133 // Selects the contents inside the given node 134 Xinha.prototype.selectNodeContents = function(node, pos) 135 { 136 this.focusEditor(); 137 this.forceRedraw(); 138 var range; 139 var collapsed = typeof pos == "undefined" ? true : false; 140 var sel = this._getSelection(); 141 range = this._doc.createRange(); 142 // Tables and Images get selected as "objects" rather than the text contents 143 if ( collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|textarea|select/) ) 144 { 145 range.selectNode(node); 146 } 147 else 148 { 149 range.selectNodeContents(node); 150 //(collapsed) && range.collapse(pos); 151 } 152 sel.removeAllRanges(); 153 sel.addRange(range); 154 }; 155 156 /** Call this function to insert HTML code at the current position. It deletes 157 * the selection, if any. 158 */ 159 Xinha.prototype.insertHTML = function(html) 160 { 161 var sel = this._getSelection(); 162 var range = this._createRange(sel); 163 this.focusEditor(); 164 // construct a new document fragment with the given HTML 165 var fragment = this._doc.createDocumentFragment(); 166 var div = this._doc.createElement("div"); 167 div.innerHTML = html; 168 while ( div.firstChild ) 169 { 170 // the following call also removes the node from div 171 fragment.appendChild(div.firstChild); 172 } 173 // this also removes the selection 174 var node = this.insertNodeAtSelection(fragment); 175 }; 176 177 // Retrieve the selected block 178 Xinha.prototype.getSelectedHTML = function() 179 { 180 var sel = this._getSelection(); 181 var range = this._createRange(sel); 182 return Xinha.getHTML(range.cloneContents(), false, this); 183 }; 184 185 Xinha.prototype.checkBackspace = function() 186 { 187 var self = this; 188 setTimeout( 189 function() 190 { 191 var sel = self._getSelection(); 192 var range = self._createRange(sel); 193 var SC = range.startContainer; 194 var SO = range.startOffset; 195 var EC = range.endContainer; 196 var EO = range.endOffset; 197 var newr = SC.nextSibling; 198 if ( SC.nodeType == 3 ) 199 { 200 SC = SC.parentNode; 201 } 202 if ( ! ( /\S/.test(SC.tagName) ) ) 203 { 204 var p = document.createElement("p"); 205 while ( SC.firstChild ) 206 { 207 p.appendChild(SC.firstChild); 208 } 209 SC.parentNode.insertBefore(p, SC); 210 Xinha.removeFromParent(SC); 211 var r = range.cloneRange(); 212 r.setStartBefore(newr); 213 r.setEndAfter(newr); 214 r.extractContents(); 215 sel.removeAllRanges(); 216 sel.addRange(r); 217 } 218 }, 219 10); 220 }; 221 222 // returns the current selection object 223 Xinha.prototype._getSelection = function() 224 { 225 return this._iframe.contentWindow.getSelection(); 226 }; 227 228 // returns a range for the current selection 229 Xinha.prototype._createRange = function(sel) 230 { 231 this.activateEditor(); 232 if ( typeof sel != "undefined" ) 233 { 234 try 235 { 236 return sel.getRangeAt(0); 237 } 238 catch(ex) 239 { 240 return this._doc.createRange(); 241 } 242 } 243 else 244 { 245 return this._doc.createRange(); 246 } 247 }; 248 249 Xinha.getOuterHTML = function(element) 250 { 251 return (new XMLSerializer()).serializeToString(element); 252 }; 253 254 //What is this supposed to do??? it's never used 255 //ray 256 Xinha.prototype._formatBlock = function(block_format) 257 { 258 var ancestors = this.getAllAncestors(); 259 var apply_to, x = null; 260 // Block format can be a tag followed with class defs 261 // eg div.blue.left 262 var target_tag = null; 263 var target_classNames = [ ]; 264 265 if ( block_format.indexOf('.') >= 0 ) 266 { 267 target_tag = block_format.substr(0, block_format.indexOf('.')).toLowerCase(); 268 target_classNames = block_format.substr(block_format.indexOf('.'), block_format.length - block_format.indexOf('.')).replace(/\./g, '').replace(/^\s*/, '').replace(/\s*$/, '').split(' '); 269 } 270 else 271 { 272 target_tag = block_format.toLowerCase(); 273 } 274 275 var sel = this._getSelection(); 276 var rng = this._createRange(sel); 277 278 if ( Xinha.is_gecko ) 279 { 280 if ( sel.isCollapsed ) 281 { 282 // With no selection we want to apply to the whole contents of the ancestor block 283 apply_to = this._getAncestorBlock(sel); 284 if ( apply_to === null ) 285 { 286 // If there wasn't an ancestor, make one. 287 apply_to = this._createImplicitBlock(sel, target_tag); 288 } 289 } 290 else 291 { 292 // With a selection it's more tricky 293 switch ( target_tag ) 294 { 295 296 case 'h1': 297 case 'h2': 298 case 'h3': 299 case 'h4': 300 case 'h5': 301 case 'h6': 302 case 'h7': 303 apply_to = []; 304 var search_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7']; 305 for ( var y = 0; y < search_tags.length; y++ ) 306 { 307 var headers = this._doc.getElementsByTagName(search_tags[y]); 308 for ( x = 0; x < headers.length; x++ ) 309 { 310 if ( sel.containsNode(headers[x]) ) 311 { 312 apply_to[apply_to.length] = headers[x]; 313 } 314 } 315 } 316 if ( apply_to.length > 0) 317 { 318 break; 319 } 320 // If there wern't any in the selection drop through 321 case 'div': 322 apply_to = this._doc.createElement(target_tag); 323 apply_to.appendChild(rng.extractContents()); 324 rng.insertNode(apply_to); 325 break; 326 327 case 'p': 328 case 'center': 329 case 'pre': 330 case 'ins': 331 case 'del': 332 case 'blockquote': 333 case 'address': 334 apply_to = []; 335 var paras = this._doc.getElementsByTagName(target_tag); 336 for ( x = 0; x < paras.length; x++ ) 337 { 338 if ( sel.containsNode(paras[x]) ) 339 { 340 apply_to[apply_to.length] = paras[x]; 341 } 342 } 343 344 if ( apply_to.length === 0 ) 345 { 346 sel.collapseToStart(); 347 return this._formatBlock(block_format); 348 } 349 break; 350 } 351 } 352 } 353 }; 354 355 356 // IE's textRange and selection object is woefully inadequate, 357 // which means this fancy stuff is gecko only sorry :-| 358 // Die Bill, Die. (IE supports it somewhat nativly though) 359 Xinha.prototype.mozKey = function ( ev, keyEvent ) 360 { 361 var editor = this; 1 2 /*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:-- 3 -- Xinha (is not htmlArea) - http://xinha.gogo.co.nz/ 4 -- 5 -- Use of Xinha is granted by the terms of the htmlArea License (based on 6 -- BSD license) please read license.txt in this package for details. 7 -- 8 -- Xinha was originally based on work by Mihai Bazon which is: 9 -- Copyright (c) 2003-2004 dynarch.com. 10 -- Copyright (c) 2002-2003 interactivetools.com, inc. 11 -- This copyright notice MUST stay intact for use. 12 -- 13 -- This is the Gecko compatability plugin, part of the Xinha core. 14 -- 15 -- The file is loaded as a special plugin by the Xinha Core when 16 -- Xinha is being run under a Gecko based browser with the Midas 17 -- editing API. 18 -- 19 -- It provides implementation and specialisation for various methods 20 -- in the core where different approaches per browser are required. 21 -- 22 -- Design Notes:: 23 -- Most methods here will simply be overriding Xinha.prototype.<method> 24 -- and should be called that, but methods specific to Gecko should 25 -- be a part of the Gecko.prototype, we won't trample on namespace 26 -- that way. 27 -- 28 -- $HeadURL$ 29 -- $LastChangedDate$ 30 -- $LastChangedRevision$ 31 -- $LastChangedBy$ 32 --------------------------------------------------------------------------*/ 33 34 Gecko._pluginInfo = { 35 name : "Gecko", 36 origin : "Xinha Core", 37 version : "$LastChangedRevision$", 38 developer : "The Xinha Core Developer Team", 39 developer_url : "$HeadURL$", 40 license : "htmlArea" 41 }; 42 43 function Gecko(editor) { 44 this.editor = editor; 45 editor.Gecko = this; 46 } 47 48 /** Allow Gecko to handle some key events in a special way. 49 */ 50 51 Gecko.prototype.onKeyPress = function(ev) 52 { 53 var editor = this.editor; 54 55 if ( ev.ctrlKey && editor._unLink && editor._unlinkOnUndo ) 56 { 57 if ( String.fromCharCode(ev.charCode).toLowerCase() == 'z' ) 58 { 59 Xinha._stopEvent(ev); 60 editor._unLink(); 61 editor.updateToolbar(); 62 return true; // Stop further 63 } 64 } 65 362 66 var s = editor._getSelection(); 363 67 var autoWrap = function (textNode, tag) … … 373 77 rightText.data = ' ' + rightText.data; 374 78 375 if ( Xinha.is_ie ) 376 { 377 var r = editor._createRange(s); 378 s.moveToElementText(rightText); 379 s.move('character', 1); 380 } 381 else 382 { 383 s.collapse(rightText, 1); 384 } 385 Xinha._stopEvent(ev); 79 s.collapse(rightText, 1); 80 // Xinha._stopEvent(ev); 386 81 387 82 editor._unLink = function() … … 404 99 // and link it appropriatly 405 100 case 32: 406 if ( this.config.convertUrlsToLinks && s && s.isCollapsed && s.anchorNode.nodeType == 3 && s.anchorNode.data.length > 3 && s.anchorNode.data.indexOf('.') >= 0 )101 if ( editor.config.convertUrlsToLinks && s && s.isCollapsed && s.anchorNode.nodeType == 3 && s.anchorNode.data.length > 3 && s.anchorNode.data.indexOf('.') >= 0 ) 407 102 { 408 103 var midStart = s.anchorNode.data.substring(0,s.anchorOffset).search(/\S{4,}$/); … … 412 107 } 413 108 414 if ( this._getFirstAncestor(s, 'a') )109 if ( editor._getFirstAncestor(s, 'a') ) 415 110 { 416 111 break; // already in an anchor … … 452 147 453 148 default: 454 if ( ev.keyCode == 27 || ( this._unlinkOnUndo && ev.ctrlKey && ev.which == 122 ) )455 { 456 if ( this._unLink )457 { 458 this._unLink();149 if ( ev.keyCode == 27 || ( editor._unlinkOnUndo && ev.ctrlKey && ev.which == 122 ) ) 150 { 151 if ( editor._unLink ) 152 { 153 editor._unLink(); 459 154 Xinha._stopEvent(ev); 460 155 } … … 463 158 else if ( ev.which || ev.keyCode == 8 || ev.keyCode == 46 ) 464 159 { 465 this._unlinkOnUndo = false;160 editor._unlinkOnUndo = false; 466 161 467 162 if ( s.anchorNode && s.anchorNode.nodeType == 3 ) 468 163 { 469 164 // See if we might be changing a link 470 var a = this._getFirstAncestor(s, 'a');165 var a = editor._getFirstAncestor(s, 'a'); 471 166 // @todo: we probably need here to inform the setTimeout below that we not changing a link and not start another setTimeout 472 167 if ( !a ) … … 513 208 } 514 209 515 516 210 // other keys here 517 211 switch (ev.keyCode) 518 212 { 519 213 case 13: // KEY enter 520 if ( Xinha.is_gecko && !ev.shiftKey && this.config.mozParaHandler == 'dirty' )521 { 522 this.dom_checkInsertP();214 if( !ev.shiftKey && editor.config.mozParaHandler == 'dirty' ) 215 { 216 editor.dom_checkInsertP(); 523 217 Xinha._stopEvent(ev); 524 218 } … … 526 220 case 8: // KEY backspace 527 221 case 46: // KEY delete 528 if ( ( Xinha.is_gecko && !ev.shiftKey ) || Xinha.is_ie ) 529 { 530 if ( this.checkBackspace() ) 531 { 532 Xinha._stopEvent(ev); 533 } 222 if ( !ev.shiftKey && this.handleBackspace() ) 223 { 224 Xinha._stopEvent(ev); 534 225 } 535 226 break; 536 227 } 228 229 return false; // Let other plugins etc continue from here. 537 230 } 538 231 539 Xinha._browserSpecificFunctionsLoaded = true; 232 /** When backspace is hit, the Gecko onKeyPress will execute this method. 233 * I don't remember what the exact purpose of this is though :-( 234 */ 235 236 Gecko.prototype.handleBackspace = function() 237 { 238 var editor = this.editor; 239 setTimeout( 240 function() 241 { 242 var sel = editor.getSelection(); 243 var range = editor.createRange(sel); 244 var SC = range.startContainer; 245 var SO = range.startOffset; 246 var EC = range.endContainer; 247 var EO = range.endOffset; 248 var newr = SC.nextSibling; 249 if ( SC.nodeType == 3 ) 250 { 251 SC = SC.parentNode; 252 } 253 if ( ! ( /\S/.test(SC.tagName) ) ) 254 { 255 var p = document.createElement("p"); 256 while ( SC.firstChild ) 257 { 258 p.appendChild(SC.firstChild); 259 } 260 SC.parentNode.insertBefore(p, SC); 261 Xinha.removeFromParent(SC); 262 var r = range.cloneRange(); 263 r.setStartBefore(newr); 264 r.setEndAfter(newr); 265 r.extractContents(); 266 sel.removeAllRanges(); 267 sel.addRange(r); 268 } 269 }, 270 10); 271 }; 272 273 /*--------------------------------------------------------------------------*/ 274 /*------- IMPLEMENTATION OF THE ABSTRACT "Xinha.prototype" METHODS ---------*/ 275 /*--------------------------------------------------------------------------*/ 276 277 /** Insert a node at the current selection point. 278 * @param toBeInserted DomNode 279 */ 280 281 Xinha.prototype.insertNodeAtSelection = function(toBeInserted) 282 { 283 var sel = this._getSelection(); 284 var range = this._createRange(sel); 285 // remove the current selection 286 sel.removeAllRanges(); 287 range.deleteContents(); 288 var node = range.startContainer; 289 var pos = range.startOffset; 290 var selnode = toBeInserted; 291 switch ( node.nodeType ) 292 { 293 case 3: // Node.TEXT_NODE 294 // we have to split it at the caret position. 295 if ( toBeInserted.nodeType == 3 ) 296 { 297 // do optimized insertion 298 node.insertData(pos, toBeInserted.data); 299 range = this._createRange(); 300 range.setEnd(node, pos + toBeInserted.length); 301 range.setStart(node, pos + toBeInserted.length); 302 sel.addRange(range); 303 } 304 else 305 { 306 node = node.splitText(pos); 307 if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ ) 308 { 309 selnode = selnode.firstChild; 310 } 311 node.parentNode.insertBefore(toBeInserted, node); 312 this.selectNodeContents(selnode); 313 this.updateToolbar(); 314 } 315 break; 316 case 1: // Node.ELEMENT_NODE 317 if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ ) 318 { 319 selnode = selnode.firstChild; 320 } 321 node.insertBefore(toBeInserted, node.childNodes[pos]); 322 this.selectNodeContents(selnode); 323 this.updateToolbar(); 324 break; 325 } 326 }; 327 328 /** Get the parent element of the supplied or current selection. 329 * @param sel optional selection as returned by getSelection 330 * @returns DomNode 331 */ 332 333 Xinha.prototype.getParentElement = function(sel) 334 { 335 if ( typeof sel == 'undefined' ) 336 { 337 debugger; 338 sel = this._getSelection(); 339 } 340 var range = this._createRange(sel); 341 try 342 { 343 var p = range.commonAncestorContainer; 344 if ( !range.collapsed && range.startContainer == range.endContainer && 345 range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes() ) 346 { 347 p = range.startContainer.childNodes[range.startOffset]; 348 } 349 350 while ( p.nodeType == 3 ) 351 { 352 p = p.parentNode; 353 } 354 return p; 355 } 356 catch (ex) 357 { 358 return null; 359 } 360 }; 361 362 /** 363 * Returns the selected element, if any. That is, 364 * the element that you have last selected in the "path" 365 * at the bottom of the editor, or a "control" (eg image) 366 * 367 * @returns null | DomNode 368 */ 369 370 Xinha.prototype.activeElement = function(sel) 371 { 372 if ( ( sel === null ) || this._selectionEmpty(sel) ) 373 { 374 return null; 375 } 376 377 // For Mozilla we just see if the selection is not collapsed (something is selected) 378 // and that the anchor (start of selection) is an element. This might not be totally 379 // correct, we possibly should do a simlar check to IE? 380 if ( !sel.isCollapsed ) 381 { 382 if ( sel.anchorNode.childNodes.length > sel.anchorOffset && sel.anchorNode.childNodes[sel.anchorOffset].nodeType == 1 ) 383 { 384 return sel.anchorNode.childNodes[sel.anchorOffset]; 385 } 386 else if ( sel.anchorNode.nodeType == 1 ) 387 { 388 return sel.anchorNode; 389 } 390 else 391 { 392 return null; // return sel.anchorNode.parentNode; 393 } 394 } 395 return null; 396 }; 397 398 /** 399 * Determines if the given selection is empty (collapsed). 400 * @param selection Selection object as returned by getSelection 401 * @returns true|false 402 */ 403 404 Xinha.prototype.selectionEmpty = function(sel) 405 { 406 if ( !sel ) 407 { 408 return true; 409 } 410 411 if ( typeof sel.isCollapsed != 'undefined' ) 412 { 413 return sel.isCollapsed; 414 } 415 416 return true; 417 }; 418 419 420 /** 421 * Selects the contents of the given node. If the node is a "control" type element, (image, form input, table) 422 * the node itself is selected for manipulation. 423 * 424 * @param node DomNode 425 * @param pos Set to a numeric position inside the node to collapse the cursor here if possible. 426 */ 427 428 Xinha.prototype.selectNodeContents = function(node, pos) 429 { 430 this.focusEditor(); 431 this.forceRedraw(); 432 var range; 433 var collapsed = typeof pos == "undefined" ? true : false; 434 var sel = this._getSelection(); 435 range = this._doc.createRange(); 436 // Tables and Images get selected as "objects" rather than the text contents 437 if ( collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|textarea|select/) ) 438 { 439 range.selectNode(node); 440 } 441 else 442 { 443 range.selectNodeContents(node); 444 //(collapsed) && range.collapse(pos); 445 } 446 sel.removeAllRanges(); 447 sel.addRange(range); 448 }; 449 450 /** Insert HTML at the current position, deleting the selection if any. 451 * 452 * @param html string 453 */ 454 455 Xinha.prototype.insertHTML = function(html) 456 { 457 var sel = this._getSelection(); 458 var range = this._createRange(sel); 459 this.focusEditor(); 460 // construct a new document fragment with the given HTML 461 var fragment = this._doc.createDocumentFragment(); 462 var div = this._doc.createElement("div"); 463 div.innerHTML = html; 464 while ( div.firstChild ) 465 { 466 // the following call also removes the node from div 467 fragment.appendChild(div.firstChild); 468 } 469 // this also removes the selection 470 var node = this.insertNodeAtSelection(fragment); 471 }; 472 473 /** Get the HTML of the current selection. HTML returned has not been passed through outwardHTML. 474 * 475 * @returns string 476 */ 477 478 Xinha.prototype.getSelectedHTML = function() 479 { 480 var sel = this._getSelection(); 481 var range = this._createRange(sel); 482 return Xinha.getHTML(range.cloneContents(), false, this); 483 }; 484 485 486 /** Get a Selection object of the current selection. Note that selection objects are browser specific. 487 * 488 * @returns Selection 489 */ 490 491 Xinha.prototype.getSelection = function() 492 { 493 return this._iframe.contentWindow.getSelection(); 494 }; 495 496 /** Create a Range object from the given selection. Note that range objects are browser specific. 497 * 498 * @param sel Selection object (see getSelection) 499 * @returns Range 500 */ 501 502 Xinha.prototype.createRange = function(sel) 503 { 504 this.activateEditor(); 505 if ( typeof sel != "undefined" ) 506 { 507 try 508 { 509 return sel.getRangeAt(0); 510 } 511 catch(ex) 512 { 513 return this._doc.createRange(); 514 } 515 } 516 else 517 { 518 return this._doc.createRange(); 519 } 520 }; 521 522 /** Determine if the given event object is a keydown/press event. 523 * 524 * @param event Event 525 * @returns true|false 526 */ 527 528 Xinha.prototype.isKeyEvent = function(event) 529 { 530 return event.type == "keypress"; 531 } 532 533 /** Return the HTML string of the given Element, including the Element. 534 * 535 * @param element HTML Element DomNode 536 * @returns string 537 */ 538 539 Xinha.getOuterHTML = function(element) 540 { 541 return (new XMLSerializer()).serializeToString(element); 542 };
