User:Jitse Niesen/Client-side preferences/Main.js
From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Firefox/Mozilla/Safari: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Internet Explorer: press Ctrl-F5, Opera/Konqueror: press F5.
//<pre><nowiki> // Thanks to [[User:Lupin]] for much useful advice. /********** Configuration **********/ cspEntryPage = 'Wikipedia:WikiProject_User_scripts/User-script_manager'; cspMainScript = 'User:Jitse_Niesen/Client-side_preferences/Main.js'; cspScriptRepos = 'Wikipedia:WikiProject_User_scripts/Scripts/'; cspSummary = 'Update using the experimental [[' + cspEntryPage + '|User-script manager]]'; /* List of JavaScript fragments: id = a unique identifier, usually the name of the main routine type = 0 (default): Script to be included verbatim 1: Script to be included via <script> element 2: Chapter heading page = name of the page where the fragment is stored rev = revision of the page (can be found by looking at the 'permanent link') deps = list of dependencies, can be left out if there are none desc = description; leave out for helper functions */ cspFragments = [ { desc: 'Popup windows', type: 2 }, { id: 'setupPopups', page: 'User:Lupin/popups.js', type: 1, desc: 'Lupin\'s navigation popups (popup windows that appear when you hover over links' }, { desc: 'Editing', type: 2 }, { id: 'addEditSection0', page: cspScriptRepos + 'Add_edit_section_0', rev: 33908474, desc: 'Add \'0\' tab to edit section of page before headings' }, { id: 'addTab_ee', page: cspScriptRepos + 'External_editor', rev: 38650024, deps: ['addTab'], desc: 'Add \'ee\' tab to edit page with an external editor' }, { id: 'editTop', page: cspScriptRepos + 'Edit_Top', rev: 87854342, desc: 'Add an [edit] link below the page heading to edit only the lead section' }, { desc: 'History browsing', type: 2 }, { id: 'addSinceTab', page: cspScriptRepos + 'Changes_since_I_last_edited', rev: 60911506, deps: ['addlilink'], desc: 'Add \'since\' tab to show changes since user last edited given page' }, { id: 'addLastDiff', page: cspScriptRepos + 'Show_last_diff', rev: 67477415, deps: ['addTab'], desc: 'Add \'last\' tab to show diff of last edit to this page' }, { id: 'addAdahLinks', page: cspScriptRepos + 'All_diffs_above_here', rev: 87965058, deps: ['getPname'], desc: 'Add \'adah\' links to watchlist to open all diffs above the current entry' }, /* hideOwn.js is obsolete */ { desc: 'Adding maintenance tags', type: 2 }, { id: 'addQwikify', page: cspScriptRepos + 'Quick_wikify', rev: 56374346, deps: ['addTab'], desc: 'Add \'wikify\' tab to edit pages to add {{wikify-date}} at the top and save' }, { id: 'addCleanup', page: cspScriptRepos + 'CleanupTab.js', rev: 47809636, deps: ['addTab'], desc: 'Add \'cleanup\' tab to edit pages to add {{cleanup-date}} at the top and save' }, { id: 'autocopyvio', page: cspScriptRepos + 'Autocopyvio.js', rev: 66876201, desc: 'Add \'copyvio\' tab on edit page to mark a page as a copyright violation and submit it to Copyright problems' }, { desc: 'Deletion tools', type: 2 }, { id: 'easyDb', page: cspScriptRepos + 'Easy_db', rev: 68004829, deps: ['addTab'], desc: 'Add \'db\' tabs to nominate an article for speedy deletion' }, { id: 'autoafd', page: cspScriptRepos + 'AutoAFD.js', rev: 75520536, desc: 'Add \'AfD\' tab to edit page to nominate an article for deletion' }, { desc: 'Users', type: 2 }, { id: 'addUserTabs', page: cspScriptRepos + 'User_tabs', rev: 73795341, deps: ['addTab'], desc: 'Add \'contrib\', \'page moves\', \'block log\' and \'edit count\' tabs to user pages' }, { id: 'add_testn_Tabs', page: cspScriptRepos + 'test-enhanced', rev: 87954483, desc: 'Add tabs that automate the adding of {{test-n}} templates when editing user talk pages' }, { desc: 'Miscellaneous', type: 2 }, { id: 'addPurgeTab', page: cspScriptRepos + 'Add_purge_to_tabs', rev: 76509603, deps: ['addLink'], desc: 'Add \'purge\' tab to clear Wikipedia cache of page' }, { id: 'fixLowercaseProblem', page: cspScriptRepos + 'Fix_lowercase_first_letter_problem', rev: 84530466, desc: 'Fix the lowercase first letter problem by changing the page title to the correct one and hiding the template' }, /* 'Duplicate tabs at bottom' also needs CSS */ { id: 'googleLink', page: cspScriptRepos + 'Google_link', rev: 87257636, deps: ['addLink'], desc: 'Add toolbox links to search Google and Yahoo! for the title of the page' }, { desc: 'Tools for administrators', type: 2 }, { id: 'autoafd_add_afd_tabs', page: cspScriptRepos + 'CloseAFD.js', rev: 75520859, deps: ['addLink'], desc: 'Add \'close\' and \'relist\' tabs to edit page of AfD debates to close/relist them' }, { desc: 'Generate script', type: 2 }, /* Helper functions */ { id: 'addlilink', page: cspScriptRepos + 'Add_LI_link', rev: 73544143 }, { id: 'addTab', page: cspScriptRepos + 'Add_tab', rev: 73544604, deps: ['addlilink'] }, { id: 'getPname', page: cspScriptRepos + 'Get_Page_Name', rev: 73544616 }, { id: 'addLink', page: cspScriptRepos + 'addLink', rev: 73544644 } ]; cspStartComment = '// jncsp-start'; cspStartComment2 = ' The section below (up to jncsp-end) is maintained by [[' + cspEntryPage + ']]'; cspConfigComment = '// jncsp-config'; cspEndComment = '// jncsp-end'; /********** Utility functions **********/ /* Construct an XMLHttpRequest */ function cspConstructHttpRequest() { var res; // Copied from http://developer.mozilla.org/en/docs/AJAX:Getting_Started if (window.XMLHttpRequest) { // Mozilla, Safari, ... res = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE res = new ActiveXObject("Microsoft.XMLHTTP"); } return res; } function getXmlObj(req) { if (req.responseXML && req.responseXML.documentElement) return req.responseXML; // Note: this doesn't work in konqueror, and the natural fix // var doc=document.implementation.createDocument(); doc.loadXML(req.responseXML) // results in a segfault :-( try { var doc = new ActiveXObject("Microsoft.XMLDOM"); doc.async = "false"; doc.loadXML(req.responseText); } catch (err) { return null; } return doc; } /* Checks whether the request has finished successfully req = XMLHtmlRequest Returns true or false */ function cspRequestDone(req) { if (req.readyState != 4) { // still not ready return false; } if (window.http_request.status != 200) { alert('There was a problem with the request.'); return false; } return true; } // Source: http://www.albionresearch.com/misc/urlencode.php function URLEncode( plaintext ) { // The Javascript escape and unescape functions do not correspond // with what browsers actually do... var SAFECHARS = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "-_.!~*'()"; var HEX = "0123456789ABCDEF"; var encoded = ""; for (var i = 0; i < plaintext.length; i++ ) { var ch = plaintext.charAt(i); if (ch == " ") { encoded += "+"; // x-www-urlencoded, rather than %20 } else if (SAFECHARS.indexOf(ch) != -1) { encoded += ch; } else { var charCode = ch.charCodeAt(0); if (charCode > 255) { alert( "Unicode Character '" + ch + "' cannot be encoded using standard URL encoding.\n" + "(URL encoding only supports 8-bit characters.)\n" + "A space (+) will be substituted." ); encoded += "+"; } else { encoded += "%"; encoded += HEX.charAt((charCode >> 4) & 0xF); encoded += HEX.charAt(charCode & 0xF); } } } // for return encoded; } /* Returns dictionary with form data */ function cspFormData(form) { var res = new Object(); for (var i=0; i<form.elements.length; ++i) { var el = form.elements[i]; var data; if (!el.name) continue; if (el.tagname && el.tagname == 'textarea') data = el.childNodes[0].data; else if (el.checked) data = ((el.checked == 'checked') ? 'on' : 'off'); else if (el.value) data = el.value; else continue; res[el.name] = data; } return res; } /* Replace body contents by x */ function cspReplacePage(x) { var el = document.getElementById('bodyContent').childNodes[0]; while (el.nextSibling) { el = el.nextSibling; if (el.tagName && el.tagName.toLowerCase() == 'p') break; } el.parentNode.replaceChild(x, el); } /* Returns string with HTML Form in which the user can set their preferences */ function cspGetForm(oldconfig) { var f, i, res; res = '<p>Select the features that you want to have included in ' + 'your user script and then press the button at the bottom of the page.</p>\n' + '<form>\n'; for (i=0; i<cspFragments.length; i++) { f = cspFragments[i]; if (!f.desc) continue; if (f.type == 2) { /* a chapter heading */ if (i != 0) res += '</p>\n'; res += '<h2><span class="mw-headline">' + f.desc + '</span></h2>\n' + '<p>\n'; } else { /* a script fragment */ res += '<input type="checkbox" name="' + f.id + '" '; if (oldconfig.indexOf(f.id) != -1) res += 'checked '; res += '/> ' + f.desc + '<br/>\n'; } } res += '<input type="button" onclick="cspProcessForm();" value="OK" /></p>\n' + '</form>\n'; return res; } /* Returns index of fragment with given id in fraglist, or -1 if it is not there. */ function cspFindFragment(id, fraglist) { for (var n=0; n<fraglist.length; n++) { if (fraglist[n].id == id) return n; } return -1; } /* Add fragment f to the list of fragments to be downloaded */ function cspAddFragment(f) { var d, pos, i, tmp; /* If f is already on the list, we're done */ if (cspFindFragment(f.id, window.fragments) != -1) return; pos = -1; if (f.deps) { /* Add all dependencies */ for (i=0; i<f.deps.length; i++) { tmp = cspFindFragment(f.deps[i], cspFragments); if (tmp == -1) alert("Error in cspAddFragment: Cannot find " + f.deps[i]); cspAddFragment(cspFragments[tmp]); } /* Find the last dependency */ for (i=0; i<f.deps.length; i++) { tmp = cspFindFragment(f.deps[i], window.fragments); if (tmp>pos) pos = tmp; } } /* Add f after all its dependencies */ window.fragments.splice(pos+1, 0, f); } /********** Main script **********/ /* This function is called if the user visits the entry page. */ function clientSidePreferencesLoadFunction() { var p = document.createElement("p"); p.innerHTML = "<p>Please wait while your old user script is retrieved …</p>"; cspReplacePage(p); window.http_request = cspConstructHttpRequest(); window.http_request.onreadystatechange = cspReceivedOldScript; var url = wgServer + wgScriptPath + '/index.php?title=User:' + wgUserName + '/monobook.js&action=raw'; window.http_request.open('GET', url, true); window.http_request.send(null); } /* This function is called when the old user script is received. */ function cspReceivedOldScript() { if (!cspRequestDone(window.http_request)) return; window.oldscript = window.http_request.responseText; var oldconfig = []; var i = window.oldscript.indexOf(cspConfigComment); if (i != -1) { var i2 = window.oldscript.indexOf('\n', i); var oldconfig = window.oldscript.substring(i+cspConfigComment.length+1, i2).split(' '); } cspDisplayForm(oldconfig); } /* Creates and display the HTML form. */ function cspDisplayForm(oldconfig) { var cspForm = document.createElement("form"); cspForm.innerHTML = cspGetForm(oldconfig); cspForm.name = 'cspForm'; cspReplacePage(cspForm); } /* This function is called when the user presses the "Generate script" button. It collects all fragments that should be downloaded in window.fragments and starts building the new script in window.newscript */ function cspProcessForm() { window.fragments = new Array(); var newconfig = ''; for (var i=0; i<cspFragments.length; i++) { var f = cspFragments[i]; if (f.desc && f.type != 2 && document.cspForm[f.id].checked) { cspAddFragment(f); newconfig += ' ' + f.id; } } window.newscript = ''; var i = window.oldscript.indexOf(cspStartComment); if (i != -1) window.newscript += window.oldscript.substr(0, i) window.newscript += cspStartComment + cspStartComment2 + '\n'; window.newscript += cspConfigComment + newconfig + '\n\n'; var s = 'if (location.href == "' + wgServer + wgArticlePath.replace("$1", cspEntryPage) + '") \n' + ' document.write(\'<script type="text/javascript" src="' + wgServer + wgScriptPath + '\' + \n' + ' \'/index.php?title=' + cspMainScript + '&action=raw&ctype=text/javascript"></script>\'); \n'; window.newscript += s; window.bottom = document.cspForm; cspGetFragments(); } /* Go through the fragments listed in window.fragments and build window.newscript */ function cspGetFragments() { if (window.fragments.length == 0) { cspFinalizeScript(); return; } var f = window.fragments[0]; if (f.type == 1) cspImportFragment(f); else cspDownloadFragment(f) } /* Download a JavaScript fragment to be included in the user script */ function cspDownloadFragment(f) { var msg = document.createTextNode('Loading ' + f.page + ' ...'); window.bottom = window.bottom.parentNode.insertBefore(msg, bottom.nextSibling); window.http_request = cspConstructHttpRequest(); window.http_request.onreadystatechange = cspReceivedFragment; var url = wgServer + wgScriptPath + '/index.php?title=' + f.page + '&oldid=' + f.rev + '&action=raw&ctype=text/javascript'; window.http_request.open('GET', url, true); window.http_request.send(null); } /* Callback for HTTP request initiated in cspDownloadFragment() */ function cspReceivedFragment() { if (!cspRequestDone(window.http_request)) return; var msg = document.createTextNode(' done'); window.bottom = window.bottom.parentNode.insertBefore(msg, bottom.nextSibling); window.bottom = window.bottom.parentNode.insertBefore(document.createElement('BR'), bottom.nextSibling); /* Chop off preamble and postamble */ var txt = window.http_request.responseText; /* First look for the script between "// <pre><nowiki>" and "// </ nowiki></ pre>" */ var tmp = /\/\/[ ]*<pre><nowiki>[ ]*\n/.exec(txt); if (tmp) txt = txt.slice(tmp.index + tmp[0].length); tmp = /\/\/[ ]*<\/nowiki><\/pre>[ ]*(\n|$)/.exec(txt); if (tmp) txt = txt.slice(0, tmp.index); else { /* Otherwise, see if it is between "/ * <pre> * /" and "/ * </ pre> * /" (without the spaces) */ tmp = /\/\*.*<pre>.*\*\/\n/.exec(txt); if (tmp) txt = txt.slice(tmp.index + tmp[0].length); tmp = /\/\*.*<\/pre>.*\*\/(\n|$)/.exec(txt); if (tmp) txt = txt.slice(0, tmp.index); else { /* Otherwise, see if it is between "<pre>" and "</ pre>" */ tmp = /<pre>[ ]*\n/.exec(txt); if (tmp) txt = txt.slice(tmp.index + tmp[0].length); tmp = /<\/pre>[ ]*(\n|$)/.exec(txt); if (tmp) txt = txt.slice(0, tmp.index); } } window.newscript += '\n// From [[' + (window.fragments[0].page).replace('_', ' ') + ']], revision ' + window.fragments[0].rev + '\n\n' + txt + '\n'; window.fragments = window.fragments.slice(1); cspGetFragments(); } /* Import JavaScript using a <script> tag */ function cspImportFragment(f) { var msg = document.createTextNode('Importing ' + f.page); window.bottom = window.bottom.parentNode.insertBefore(msg, bottom.nextSibling); window.bottom = window.bottom.parentNode.insertBefore(document.createElement('BR'), bottom.nextSibling); window.newscript += '\n// Import [[' + (window.fragments[0].page).replace('_', ' ') + ']]\n\n' + 'document.write(\'<script type="text/javascript" src="' + wgServer + wgScriptPath + '\' + \n' + ' \'/index.php?title=' + f.page + '&action=raw&ctype=text/javascript"></script>\'); \n'; window.fragments = window.fragments.slice(1); cspGetFragments(); } function cspFinalizeScript() { window.newscript += '\n' + cspEndComment; var i = window.oldscript.indexOf(cspEndComment); if (i != -1) window.newscript += window.oldscript.substr(i+cspEndComment.length) cspLoadEditPage(); } function cspLoadEditPage() { var msg = document.createTextNode('Loading your user script ...'); window.bottom = window.bottom.parentNode.insertBefore(msg, bottom.nextSibling); window.http_request = cspConstructHttpRequest(); window.http_request.overrideMimeType('text/xml'); window.http_request.onreadystatechange = cspReceivedEditPage; var url = wgServer + wgScriptPath + '/index.php?title=User:' + wgUserName + '/monobook.js&action=edit'; window.http_request.open('GET', url, true); window.http_request.send(null); } function cspReceivedEditPage() { if (!cspRequestDone(window.http_request)) return; var doc = window.http_request.responseXML; var editform = doc.getElementById('editform'); var data = cspFormData(editform); data['wpTextbox1'] = window.newscript; data['wpSummary'] = cspSummary; data['wpMinoredit'] = 'on'; data['wpPreview'] = null; data['wpDiff'] = null; var msg = document.createTextNode(' saving new script ...'); window.bottom = window.bottom.parentNode.insertBefore(msg, bottom.nextSibling); window.http_request = cspConstructHttpRequest(); window.http_request.overrideMimeType('text/xml'); window.http_request.onreadystatechange = cspEditPageSaved; var url = wgServer + wgScriptPath + '/index.php?title=User:' + wgUserName + '/monobook.js&action=submit'; window.http_request.open('POST', url, true); window.http_request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); sendMe = ''; for (var name in data) { if (data[name]) { if (sendMe.length > 0) sendMe += '&'; sendMe += URLEncode(name) + '=' + URLEncode(data[name]); } } window.http_request.send(sendMe); } function cspEditPageSaved() { if (!cspRequestDone(window.http_request)) return; var msg = document.createTextNode(' done'); window.bottom = window.bottom.parentNode.insertBefore(msg, bottom.nextSibling); window.bottom = window.bottom.parentNode.insertBefore(document.createElement('BR'), bottom.nextSibling); } addOnloadHook(clientSidePreferencesLoadFunction); //</nowiki></pre>