Site Notice |
---|
We have a limited coverage policy. Please check our coverage page to see which articles are allowed. |
Difference between revisions of "MediaWiki:Common.js"
Jump to navigation
Jump to search
m |
m (let's see if this breaks anything) |
||
(5 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
+ | |||
+ | // -------------------------------------------------------- | ||
+ | // addPurge | ||
+ | // adds a "purge" tab (after "watch") | ||
+ | // -------------------------------------------------------- | ||
+ | addOnloadHook(function () { | ||
+ | if (wgAction != 'edit' && wgCanonicalNamespace != 'Special' && wgAction != 'history' && wgAction != 'delete' && wgAction != 'watch' && wgAction | ||
+ | != 'unwatch' && wgAction != 'protect' && wgAction != 'markpatrolled' && wgAction != 'rollback' && document.URL.indexOf('diff=') <= 0 | ||
+ | && document.URL.indexOf('oldid=') <=0) | ||
+ | { var hist; var url; | ||
+ | if (!(hist = document.getElementById('ca-history') )) return; | ||
+ | if (!(url = hist.getElementsByTagName('a')[0] )) return; | ||
+ | if (!(url = url.href )) return; | ||
+ | addPortletLink('p-cactions', url.replace(/([?&]action=)history([&#]|$)/, '$1purge$2'), | ||
+ | 'purge', 'ca-purge', 'Purge server cache for this page', '0'); | ||
+ | } | ||
+ | }); | ||
+ | // | ||
+ | |||
+ | // -------------------------------------------------------- | ||
+ | // Test if an element has a certain class | ||
+ | // Description: Uses regular expressions and caching for better performance. | ||
+ | // Maintainers: [[User:Mike Dillon]], [[User:R. Koot]], [[User:SG]] | ||
+ | // -------------------------------------------------------- | ||
+ | |||
+ | var hasClass = (function () { | ||
+ | var reCache = {}; | ||
+ | return function (element, className) { | ||
+ | return (reCache[className] ? reCache[className] : (reCache[className] = new RegExp("(?:\\s|^)" + className + "(?:\\s|$)"))).test(element.className); | ||
+ | }; | ||
+ | })(); | ||
+ | |||
+ | |||
+ | |||
+ | |||
// -------------------------------------------------------- | // -------------------------------------------------------- | ||
− | // | + | // Dynamic Navigation Bars (experimental) |
− | // Description: | + | // Description: See [[Wikipedia:NavFrame]]. |
− | |||
// -------------------------------------------------------- | // -------------------------------------------------------- | ||
− | var | + | // set up the words in your language |
− | var | + | var NavigationBarHide = '[' + collapseCaption + ']'; |
− | + | var NavigationBarShow = '[' + expandCaption + ']'; | |
− | function | + | // shows and hides content and picture (if available) of navigation bars |
− | var | + | // Parameters: |
− | var | + | // indexNavigationBar: the index of navigation bar to be toggled |
+ | function toggleNavigationBar(indexNavigationBar) { | ||
+ | var NavToggle = document.getElementById("NavToggle" + indexNavigationBar); | ||
+ | var NavFrame = document.getElementById("NavFrame" + indexNavigationBar); | ||
− | if ( ! | + | if (!NavFrame || !NavToggle) { |
return false; | return false; | ||
} | } | ||
− | var | + | // if shown now |
+ | if (NavToggle.firstChild.data == NavigationBarHide) { | ||
+ | for ( | ||
+ | var NavChild = NavFrame.firstChild; | ||
+ | NavChild != null; | ||
+ | NavChild = NavChild.nextSibling | ||
+ | ) { | ||
+ | if ( hasClass( NavChild, 'NavPic' ) ) { | ||
+ | NavChild.style.display = 'none'; | ||
+ | } | ||
+ | if ( hasClass( NavChild, 'NavContent') ) { | ||
+ | NavChild.style.display = 'none'; | ||
+ | } | ||
+ | } | ||
+ | NavToggle.firstChild.data = NavigationBarShow; | ||
− | if ( | + | // if hidden now |
− | + | } else if (NavToggle.firstChild.data == NavigationBarShow) { | |
− | + | for ( | |
+ | var NavChild = NavFrame.firstChild; | ||
+ | NavChild != null; | ||
+ | NavChild = NavChild.nextSibling | ||
+ | ) { | ||
+ | if( hasClass(NavChild, 'NavPic') ) { | ||
+ | NavChild.style.display = 'block'; | ||
} | } | ||
− | + | if( hasClass(NavChild, 'NavContent') ) { | |
− | + | NavChild.style.display = 'block'; | |
− | |||
− | |||
} | } | ||
− | + | } | |
+ | NavToggle.firstChild.data = NavigationBarHide; | ||
} | } | ||
} | } | ||
− | function | + | // adds show/hide-button to navigation bars |
− | var | + | function createNavigationBarToggleButton() { |
− | var | + | var indexNavigationBar = 0; |
− | var | + | // iterate over all < div >-elements |
+ | var divs = document.getElementsByTagName("div"); | ||
+ | for( | ||
+ | var i=0; | ||
+ | NavFrame = divs[i]; | ||
+ | i++ | ||
+ | ) { | ||
+ | // if found a navigation bar | ||
+ | if( hasClass(NavFrame, "NavFrame") ) { | ||
− | + | indexNavigationBar++; | |
− | + | var NavToggle = document.createElement("a"); | |
+ | NavToggle.className = 'NavToggle'; | ||
+ | NavToggle.setAttribute('id', 'NavToggle' + indexNavigationBar); | ||
+ | NavToggle.setAttribute('href', 'javascript:toggleNavigationBar(' + indexNavigationBar + ');'); | ||
− | + | var NavToggleText = document.createTextNode(NavigationBarHide); | |
− | + | for ( | |
− | + | var NavChild = NavFrame.firstChild; | |
− | var | + | NavChild != null; |
− | + | NavChild = NavChild.nextSibling | |
− | + | ) { | |
− | + | if ( hasClass( NavChild, 'NavPic' ) || hasClass( NavChild, 'NavContent' ) ) { | |
− | + | if (NavChild.style.display == 'none') { | |
− | + | NavToggleText = document.createTextNode(NavigationBarShow); | |
− | + | break; | |
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
} | } | ||
− | + | NavToggle.appendChild(NavToggleText); | |
− | + | // Find the NavHead and attach the toggle link (Must be this complicated because Moz's firstChild handling is borked) | |
− | + | for( | |
+ | var j=0; | ||
+ | j < NavFrame.childNodes.length; | ||
+ | j++ | ||
+ | ) { | ||
+ | if( hasClass(NavFrame.childNodes[j], "NavHead") ) { | ||
+ | NavFrame.childNodes[j].appendChild(NavToggle); | ||
+ | } | ||
+ | } | ||
+ | NavFrame.setAttribute('id', 'NavFrame' + indexNavigationBar); | ||
} | } | ||
} | } | ||
} | } | ||
− | addOnloadHook( | + | addOnloadHook( createNavigationBarToggleButton ); |
+ | |||
+ | // -------------------------------------------------------- | ||
+ | // Rights | ||
+ | // Sets a variable "rights" which will return "false" if the | ||
+ | // currently logged in user is a bureaucrat, administrator, or autoconfirmed user. It will return true otherwise. | ||
+ | // it also defines variables which may be used elsewhere in scripts. | ||
+ | // -------------------------------------------------------- | ||
+ | |||
+ | var rights_isAdmin = (wgUserGroups.toString().indexOf('sysop') != -1); | ||
+ | var rights_isAuto = (wgUserGroups.toString().indexOf('autoconfirmed') != -1); | ||
+ | var rights_isCrat = (wgUserGroups.toString().indexOf('bureaucrat') != -1); | ||
+ | var rights = true; | ||
+ | if (rights_isCrat || rights_isAdmin || rights_isAuto) | ||
+ | {rights=false} | ||
+ | // | ||
// -------------------------------------------------------- | // -------------------------------------------------------- | ||
− | // | + | // addLogs |
− | // adds a " | + | // adds a 'page logs' link to the toolbox bar (if the page is a special page, then no link is displayed) |
+ | // -------------------------------------------------------- | ||
+ | addOnloadHook(function () { | ||
+ | if ( wgCanonicalNamespace == "Special" ) | ||
+ | return; // don't display link for special pages | ||
+ | |||
+ | url = wgServer + "/wiki/index.php?title=Special:Log&page=" + encodeURIComponent(wgPageName); | ||
+ | |||
+ | addPortletLink("p-tb", url, "Page logs", "pt-logs"); | ||
+ | }); | ||
+ | // | ||
+ | // -------------------------------------------------------- | ||
+ | // user rights | ||
+ | // adds a link in the tool box while on user pages to a user's rights management page. | ||
// -------------------------------------------------------- | // -------------------------------------------------------- | ||
addOnloadHook(function () { | addOnloadHook(function () { | ||
− | if ( | + | if (!rights_isAdmin) |
− | + | return; //Restrict this feature to admins. | |
− | && | + | if (wgNamespaceNumber != "2" && wgNamespaceNumber != "3") |
− | { | + | return; // restrict to User and User talk |
− | + | ||
− | + | var title = wgTitle; | |
− | + | ||
− | + | addPortletLink('p-tb', '/wiki/index.php?title=Special:Userrights/'+title, | |
− | + | 'User rights', 't-userrights', 'User rights for "'+title+'"'); | |
+ | |||
+ | }); | ||
+ | // | ||
+ | |||
+ | // -------------------------------------------------------- | ||
+ | // adminrights.js (adapted from http://en.wikipedia.org/wiki/User:Ais523/adminrights.js) | ||
+ | // This script changes the color of links to admins' userpages in the bodyContent of Special, History pages, diff pages, | ||
+ | // and old page revisions. | ||
+ | // ("bodyContent" being everything but the tabs,personal links at the top of the screen and sidebar). | ||
+ | // -------------------------------------------------------- | ||
+ | |||
+ | var adminrights=new Array(); | ||
+ | |||
+ | importScript('MediaWiki:Adminlist.js'); | ||
+ | |||
+ | //Highlighting script. Based on [[User:ais523/highlightmyname.js]]. | ||
+ | |||
+ | function highlightadmins(n,p) //node, parent node | ||
+ | { | ||
+ | while(n!=null) | ||
+ | { | ||
+ | if(n.nodeType==1&&n.tagName.toLowerCase()=="a") //anchor | ||
+ | { | ||
+ | if(n.href.indexOf("/wiki/index.php?title=User:")!=-1) | ||
+ | { | ||
+ | var u=n.href.split("/wiki/index.php?title=User:")[1]; | ||
+ | if(adminrights[u.split("_").join("%20")]==1) | ||
+ | { | ||
+ | n.style.color="#00CC00"; | ||
+ | if(n.className==null||n.className=="") n.className="ais523_adminrights_admin"; | ||
+ | else n.className+="ais523_adminrights_admin"; | ||
+ | } | ||
+ | n=n.nextSibling; | ||
+ | } | ||
+ | else if(n.href.indexOf("/wiki/index.php?title=User:")!=-1) | ||
+ | { | ||
+ | var u=n.href.split("/wiki/index.php?title=User:")[1]; | ||
+ | if(adminrights[u.split("_").join("%20")]==1) | ||
+ | { | ||
+ | n.style.color="#00CC00"; | ||
+ | if(n.className==null||n.className=="") n.className="ais523_adminrights_admin"; | ||
+ | else n.className+=" ais523_adminrights_admin"; | ||
+ | } | ||
+ | n=n.nextSibling; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | if(n.firstChild!=null) highlightadmins(n.firstChild,n); | ||
+ | n=n.nextSibling; | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | if(n.firstChild!=null) highlightadmins(n.firstChild,n); | ||
+ | n=n.nextSibling; | ||
+ | } | ||
+ | } | ||
} | } | ||
+ | |||
+ | |||
+ | if (wgCanonicalNamespace == 'Special' || wgAction == 'history' || document.URL.indexOf('diff=') > 0 || document.URL.indexOf('oldid=') > 0) | ||
+ | { | ||
+ | addOnloadHook(function() { | ||
+ | highlightadmins(document.getElementById('bodyContent').firstChild, | ||
+ | document.getElementById('bodyContent')); | ||
}); | }); | ||
− | // | + | } |
+ | |||
+ | /*gotten from http://en.wiktionary.org/w/index.php?title=MediaWiki:Common.js on 10/16/2010*/ | ||
+ | /* | ||
+ | === DOM creation === | ||
+ | <pre>*/ | ||
+ | /** | ||
+ | * Create a new DOM node for the current document. | ||
+ | * Basic usage: var mySpan = newNode('span', "Hello World!") | ||
+ | * Supports attributes and event handlers*: var mySpan = newNode('span', {style:"color: red", focus: function(){alert(this)}, id:"hello"}, "World, Hello!") | ||
+ | * Also allows nesting to create trees: var myPar = newNode('p', newNode('b',{style:"color: blue"},"Hello"), mySpan) | ||
+ | * | ||
+ | * *event handlers, there are some issues with IE6 not registering event handlers on some nodes that are not yet attached to the DOM, | ||
+ | * it may be safer to add event handlers later manually. | ||
+ | **/ | ||
+ | /* | ||
+ | function newNode(tagname){ | ||
+ | |||
+ | var node = document.createElement(tagname); | ||
+ | |||
+ | for( var i=1;i<arguments.length;i++ ){ | ||
+ | |||
+ | if(typeof arguments[i] == 'string'){ //Text | ||
+ | node.appendChild( document.createTextNode(arguments[i]) ); | ||
+ | |||
+ | }else if(typeof arguments[i] == 'object'){ | ||
+ | |||
+ | if(arguments[i].nodeName){ //If it is a DOM Node | ||
+ | node.appendChild(arguments[i]); | ||
+ | |||
+ | }else{ //Attributes (hopefully) | ||
+ | for(var j in arguments[i]){ | ||
+ | if(j == 'class'){ //Classname different because... | ||
+ | node.className = arguments[i][j]; | ||
+ | |||
+ | }else if(j == 'style'){ //Style is special | ||
+ | node.style.cssText = arguments[i][j]; | ||
+ | |||
+ | }else if(typeof arguments[i][j] == 'function'){ //Basic event handlers | ||
+ | try{ node.addEventListener(j,arguments[i][j],false); //W3C | ||
+ | }catch(e){try{ node.attachEvent('on'+j,arguments[i][j],"Language"); //MSIE | ||
+ | }catch(e){ node['on'+j]=arguments[i][j]; }}; //Legacy | ||
+ | |||
+ | }else{ | ||
+ | node.setAttribute(j,arguments[i][j]); //Normal attributes | ||
+ | |||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | return node; | ||
+ | } | ||
+ | */ |
Latest revision as of 01:14, 23 March 2014
/* Any JavaScript here will be loaded for all users on every page load. */ // -------------------------------------------------------- // addPurge // adds a "purge" tab (after "watch") // -------------------------------------------------------- addOnloadHook(function () { if (wgAction != 'edit' && wgCanonicalNamespace != 'Special' && wgAction != 'history' && wgAction != 'delete' && wgAction != 'watch' && wgAction != 'unwatch' && wgAction != 'protect' && wgAction != 'markpatrolled' && wgAction != 'rollback' && document.URL.indexOf('diff=') <= 0 && document.URL.indexOf('oldid=') <=0) { var hist; var url; if (!(hist = document.getElementById('ca-history') )) return; if (!(url = hist.getElementsByTagName('a')[0] )) return; if (!(url = url.href )) return; addPortletLink('p-cactions', url.replace(/([?&]action=)history([&#]|$)/, '$1purge$2'), 'purge', 'ca-purge', 'Purge server cache for this page', '0'); } }); // // -------------------------------------------------------- // Test if an element has a certain class // Description: Uses regular expressions and caching for better performance. // Maintainers: [[User:Mike Dillon]], [[User:R. Koot]], [[User:SG]] // -------------------------------------------------------- var hasClass = (function () { var reCache = {}; return function (element, className) { return (reCache[className] ? reCache[className] : (reCache[className] = new RegExp("(?:\\s|^)" + className + "(?:\\s|$)"))).test(element.className); }; })(); // -------------------------------------------------------- // Dynamic Navigation Bars (experimental) // Description: See [[Wikipedia:NavFrame]]. // -------------------------------------------------------- // set up the words in your language var NavigationBarHide = '[' + collapseCaption + ']'; var NavigationBarShow = '[' + expandCaption + ']'; // shows and hides content and picture (if available) of navigation bars // Parameters: // indexNavigationBar: the index of navigation bar to be toggled function toggleNavigationBar(indexNavigationBar) { var NavToggle = document.getElementById("NavToggle" + indexNavigationBar); var NavFrame = document.getElementById("NavFrame" + indexNavigationBar); if (!NavFrame || !NavToggle) { return false; } // if shown now if (NavToggle.firstChild.data == NavigationBarHide) { for ( var NavChild = NavFrame.firstChild; NavChild != null; NavChild = NavChild.nextSibling ) { if ( hasClass( NavChild, 'NavPic' ) ) { NavChild.style.display = 'none'; } if ( hasClass( NavChild, 'NavContent') ) { NavChild.style.display = 'none'; } } NavToggle.firstChild.data = NavigationBarShow; // if hidden now } else if (NavToggle.firstChild.data == NavigationBarShow) { for ( var NavChild = NavFrame.firstChild; NavChild != null; NavChild = NavChild.nextSibling ) { if( hasClass(NavChild, 'NavPic') ) { NavChild.style.display = 'block'; } if( hasClass(NavChild, 'NavContent') ) { NavChild.style.display = 'block'; } } NavToggle.firstChild.data = NavigationBarHide; } } // adds show/hide-button to navigation bars function createNavigationBarToggleButton() { var indexNavigationBar = 0; // iterate over all < div >-elements var divs = document.getElementsByTagName("div"); for( var i=0; NavFrame = divs[i]; i++ ) { // if found a navigation bar if( hasClass(NavFrame, "NavFrame") ) { indexNavigationBar++; var NavToggle = document.createElement("a"); NavToggle.className = 'NavToggle'; NavToggle.setAttribute('id', 'NavToggle' + indexNavigationBar); NavToggle.setAttribute('href', 'javascript:toggleNavigationBar(' + indexNavigationBar + ');'); var NavToggleText = document.createTextNode(NavigationBarHide); for ( var NavChild = NavFrame.firstChild; NavChild != null; NavChild = NavChild.nextSibling ) { if ( hasClass( NavChild, 'NavPic' ) || hasClass( NavChild, 'NavContent' ) ) { if (NavChild.style.display == 'none') { NavToggleText = document.createTextNode(NavigationBarShow); break; } } } NavToggle.appendChild(NavToggleText); // Find the NavHead and attach the toggle link (Must be this complicated because Moz's firstChild handling is borked) for( var j=0; j < NavFrame.childNodes.length; j++ ) { if( hasClass(NavFrame.childNodes[j], "NavHead") ) { NavFrame.childNodes[j].appendChild(NavToggle); } } NavFrame.setAttribute('id', 'NavFrame' + indexNavigationBar); } } } addOnloadHook( createNavigationBarToggleButton ); // -------------------------------------------------------- // Rights // Sets a variable "rights" which will return "false" if the // currently logged in user is a bureaucrat, administrator, or autoconfirmed user. It will return true otherwise. // it also defines variables which may be used elsewhere in scripts. // -------------------------------------------------------- var rights_isAdmin = (wgUserGroups.toString().indexOf('sysop') != -1); var rights_isAuto = (wgUserGroups.toString().indexOf('autoconfirmed') != -1); var rights_isCrat = (wgUserGroups.toString().indexOf('bureaucrat') != -1); var rights = true; if (rights_isCrat || rights_isAdmin || rights_isAuto) {rights=false} // // -------------------------------------------------------- // addLogs // adds a 'page logs' link to the toolbox bar (if the page is a special page, then no link is displayed) // -------------------------------------------------------- addOnloadHook(function () { if ( wgCanonicalNamespace == "Special" ) return; // don't display link for special pages url = wgServer + "/wiki/index.php?title=Special:Log&page=" + encodeURIComponent(wgPageName); addPortletLink("p-tb", url, "Page logs", "pt-logs"); }); // // -------------------------------------------------------- // user rights // adds a link in the tool box while on user pages to a user's rights management page. // -------------------------------------------------------- addOnloadHook(function () { if (!rights_isAdmin) return; //Restrict this feature to admins. if (wgNamespaceNumber != "2" && wgNamespaceNumber != "3") return; // restrict to User and User talk var title = wgTitle; addPortletLink('p-tb', '/wiki/index.php?title=Special:Userrights/'+title, 'User rights', 't-userrights', 'User rights for "'+title+'"'); }); // // -------------------------------------------------------- // adminrights.js (adapted from http://en.wikipedia.org/wiki/User:Ais523/adminrights.js) // This script changes the color of links to admins' userpages in the bodyContent of Special, History pages, diff pages, // and old page revisions. // ("bodyContent" being everything but the tabs,personal links at the top of the screen and sidebar). // -------------------------------------------------------- var adminrights=new Array(); importScript('MediaWiki:Adminlist.js'); //Highlighting script. Based on [[User:ais523/highlightmyname.js]]. function highlightadmins(n,p) //node, parent node { while(n!=null) { if(n.nodeType==1&&n.tagName.toLowerCase()=="a") //anchor { if(n.href.indexOf("/wiki/index.php?title=User:")!=-1) { var u=n.href.split("/wiki/index.php?title=User:")[1]; if(adminrights[u.split("_").join("%20")]==1) { n.style.color="#00CC00"; if(n.className==null||n.className=="") n.className="ais523_adminrights_admin"; else n.className+="ais523_adminrights_admin"; } n=n.nextSibling; } else if(n.href.indexOf("/wiki/index.php?title=User:")!=-1) { var u=n.href.split("/wiki/index.php?title=User:")[1]; if(adminrights[u.split("_").join("%20")]==1) { n.style.color="#00CC00"; if(n.className==null||n.className=="") n.className="ais523_adminrights_admin"; else n.className+=" ais523_adminrights_admin"; } n=n.nextSibling; } else { if(n.firstChild!=null) highlightadmins(n.firstChild,n); n=n.nextSibling; } } else { if(n.firstChild!=null) highlightadmins(n.firstChild,n); n=n.nextSibling; } } } if (wgCanonicalNamespace == 'Special' || wgAction == 'history' || document.URL.indexOf('diff=') > 0 || document.URL.indexOf('oldid=') > 0) { addOnloadHook(function() { highlightadmins(document.getElementById('bodyContent').firstChild, document.getElementById('bodyContent')); }); } /*gotten from http://en.wiktionary.org/w/index.php?title=MediaWiki:Common.js on 10/16/2010*/ /* === DOM creation === <pre>*/ /** * Create a new DOM node for the current document. * Basic usage: var mySpan = newNode('span', "Hello World!") * Supports attributes and event handlers*: var mySpan = newNode('span', {style:"color: red", focus: function(){alert(this)}, id:"hello"}, "World, Hello!") * Also allows nesting to create trees: var myPar = newNode('p', newNode('b',{style:"color: blue"},"Hello"), mySpan) * * *event handlers, there are some issues with IE6 not registering event handlers on some nodes that are not yet attached to the DOM, * it may be safer to add event handlers later manually. **/ /* function newNode(tagname){ var node = document.createElement(tagname); for( var i=1;i<arguments.length;i++ ){ if(typeof arguments[i] == 'string'){ //Text node.appendChild( document.createTextNode(arguments[i]) ); }else if(typeof arguments[i] == 'object'){ if(arguments[i].nodeName){ //If it is a DOM Node node.appendChild(arguments[i]); }else{ //Attributes (hopefully) for(var j in arguments[i]){ if(j == 'class'){ //Classname different because... node.className = arguments[i][j]; }else if(j == 'style'){ //Style is special node.style.cssText = arguments[i][j]; }else if(typeof arguments[i][j] == 'function'){ //Basic event handlers try{ node.addEventListener(j,arguments[i][j],false); //W3C }catch(e){try{ node.attachEvent('on'+j,arguments[i][j],"Language"); //MSIE }catch(e){ node['on'+j]=arguments[i][j]; }}; //Legacy }else{ node.setAttribute(j,arguments[i][j]); //Normal attributes } } } } } return node; } */