Sorry for inconvenience, da war doch tatsächlich noch ein kleiner Bug drinnen, der bewirkt hat, dass mehrere Menustrips zwar generiert, bis auf den ersten aber nicht ins Dokument eingefügt werden (thanks to nikosch fürs Reporten!).
Hier nochmal der Code:
Achtung! Der folgende Code ist veraltet, bitte lade den aktuellen von http://userscripts.org/scripts/show/65984 herunter.
Hier nochmal der Code:
Achtung! Der folgende Code ist veraltet, bitte lade den aktuellen von http://userscripts.org/scripts/show/65984 herunter.
Code:
// ==UserScript== // @name vBulletin Editor Mod // @namespace tag:Manko10@php.de,2008-03-09:MankoEdMod // @description Customize the vBulletin default editor on php.de // @include http://www.php.de/* // ==/UserScript== /** * Part I: preparation */ // correct window.Node window.Node = { ELEMENT_NODE : 1, ATTRIBUTE_NODE : 2, TEXT_NODE : 3, CDATA_SECTION_NODE : 4, ENTITY_REFERENCE_NODE : 5, ENTITY_NODE : 6, PROCESSING_INSTRUCTION_NODE : 7, COMMENT_NODE : 8, DOCUMENT_NODE : 9, DOCUMENT_TYPE_NODE : 10, DOCUMENT_FRAGMENT_NODE : 11, NOTATION_NODE : 12 }; /** * Part II: internationalization */ var i18n = { phrases : { 'en-US' : { 'ok' : 'OK', 'cancel' : 'Cancel', 'confMenuEntry' : 'Add/Remove Buttons', 'defaultConf' : '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE editorOverlay SYSTEM "http://www.openwebboard.org/editor-mod/editor-mod-2.dtd">\n<editorOverlay xmlns="http://www.openwebboard.org/editor-mod">\n <menustrip>\n <section type="fieldset" legend="I am legend ;-)">\n <!-- Your buttons here -->\n </section>\n </menustrip>\n</editorOverlay>', 'cancelConfirmation' : 'Are you sure you want to cancel?\nYou\'ll loose all changes!', 'XMLCompilationError' : 'Failed the test of well-formedness.\nSee the error message below:\n\n', 'noDOMAvailable' : 'Sorry, but I couldn\'t process anything since there\'s no DOM available.', 'XMLNotValid' : 'Sorry, but your XML is not valid!', 'saveMsg' : 'You must reload the page for the changes to take effect.' }, 'de-DE' : { 'ok' : 'OK', 'cancel' : 'Abbrechen', 'confMenuEntry' : 'Buttons hinzufügen/entfernen', 'defaultConf' : '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE editorOverlay SYSTEM "http://www.openwebboard.org/editor-mod/editor-mod-2.dtd">\n<editorOverlay xmlns="http://www.openwebboard.org/editor-mod">\n <menustrip>\n <section type="fieldset" legend="I am legend ;-)">\n <!-- Hier kommen deine Buttons hin -->\n </section>\n </menustrip>\n</editorOverlay>', 'cancelConfirmation' : 'Bist du sicher, dass du abbrechen willst?\nAlle Änderungen gehen dabei verloren!', 'XMLCompilationError' : 'Test auf Wohlgeformtheit fehlgeschlagen.\nSiehe die folgende Fehlermeldung:\n\n', 'noDOMAvailable' : 'Entschuldigung, aber es war mir nicht möglich, etwas zu verarbeiten, weil schlicht kein DOM da ist.', 'XMLNotValid' : 'Entschuldige bitte, aber dein XML ist nicht valide!', 'saveMsg' : 'Du musst die Seite neu laden, damit die Änderungen sichtbar werden.' } }, curLocale : 'en-US', setLocale : function(locale) { if (this.phrases[locale]) { this.curLocale = locale; }; }, get : function() { return this.phrases[this.curLocale]; }, getPhrase : function(key) { return this.phrases[this.curLocale][key]; } }; /** * Part III: configuration */ GM_registerMenuCommand( i18n.get().confMenuEntry, function() { Cfg.showConfigLightBox(); } ); var Cfg = { showConfigLightBox : function() { var layer = document.createElement('div'); layer.appendChild(document.createElement('div')); Hlp_assignStyles(layer.lastChild, { 'backgroundColor' : '#000', 'opacity' : .6, 'position' : 'absolute', 'height' : '100%', 'width' : '100%', 'top' : document.getElementsByTagName('html')[0].scrollTop + 'px', 'left' : document.getElementsByTagName('html')[0].scrollLeft + 'px' }); Hlp_assignStyles(document.body, { 'overflow' : 'hidden' }); // create wrapped textarea var txtareaWrapper = document.createElement('div'); Hlp_assignStyles(txtareaWrapper, { 'width' : '70%', 'height' : '80%', 'position' : 'absolute', 'top' : '10%', 'left' : '15%', 'marginTop' : document.getElementsByTagName('html')[0].scrollTop + 'px', 'marginLeft' : document.getElementsByTagName('html')[0].scrollLeft + 'px' }); layer.appendChild(txtareaWrapper); var txtarea = document.createElement('textarea'); with (txtarea) { cols = 120; rows = 30; setAttribute ('wrap' , 'off' , false); appendChild(document.createTextNode(GM_getValue('xmlConfig', i18n.get().defaultConf))); }; Hlp_assignStyles(txtarea, { 'border' : '3px double #333', 'width' : '100%' , 'height' : '100%' , 'fontFamily' : 'monospace' , 'padding' : '.4em' , 'whiteSpace' : 'pre' , 'overflow' : 'scroll' , 'overflowX' : 'scroll' }); txtareaWrapper.appendChild(txtarea); txtareaWrapper.appendChild(document.createElement('div')); Hlp_assignStyles(txtareaWrapper.lastChild, { 'margin' : '5px', 'textAlign' : 'center' }); // create buttons var cmdOK = document.createElement('button'); cmdOK.appendChild(document.createTextNode(i18n.get().ok)); txtareaWrapper.lastChild.appendChild(cmdOK); Hlp_assignStyles(cmdOK, { 'marginRight' : '3px', 'width' : '80px' }); var cmdCancel = document.createElement('button'); cmdCancel.appendChild(document.createTextNode(i18n.get().cancel)); txtareaWrapper.lastChild.appendChild(cmdCancel); Hlp_assignStyles(cmdCancel, { 'marginLeft' : '3px', 'width' : '80px' }); // add event listeners to layer and buttons layer.addEventListener('click', function(e) { if (e.target == txtarea) { return; }; if (confirm(i18n.get().cancelConfirmation)) { Cfg.hideConfigLightBox(layer); }; e.stopPropagation(); }, false); cmdCancel.addEventListener('click', function(e) { if (confirm(i18n.get().cancelConfirmation)) { Cfg.hideConfigLightBox(layer); }; e.stopPropagation(); }, true); cmdOK.addEventListener('click', function(e) { if (Cfg.saveConfigFromLightBox(layer)) { Cfg.hideConfigLightBox(layer); }; e.stopPropagation(); }, true); // attach layer to body document.body.appendChild(layer); }, hideConfigLightBox : function(lightbox) { document.body.removeChild(lightbox); Hlp_assignStyles(document.body, { 'overflow' : 'visible' }); }, saveConfigFromLightBox : function(lightbox) { var cfgTxt = lightbox.getElementsByTagName('textarea')[0].value; // check for well-formedness if (Hlp_checkXMLWellFormedness(cfgTxt)) { // save to about:config GM_setValue('xmlConfig', cfgTxt); return true; }; return false; }, }; /** * Part IV: process buttons */ var editorMod = { srcDOM : null, editorElement : { element : null, insertPosition : null, editCounter : 1 }, // This method does the initialization automatically, you just have to call processButtons(). __init__ : function(onlyQuickEditor) { // retrieve and parse XML var srcText = GM_getValue('xmlConfig'); if (!Hlp_checkXMLWellFormedness(srcText, true)) { GM_log(i18n.get().XMLNotValid); return false; }; this.srcDOM = new DOMParser().parseFromString(srcText, 'text/xml'); if (null === this.srcDOM) { GM_log(i18n.get().noDOMAvailable); return false; }; return this.findEditor(onlyQuickEditor); }, findEditor : function(onlyQuickEditor) { if (!onlyQuickEditor) { if (document.getElementById('vB_Editor_QR')) { this.editorElement.element = document.getElementById('vB_Editor_QR'); this.editorElement.insertPosition = document.getElementById('vB_Editor_QR_controls'); return true; } else if (document.getElementById('vB_Editor_001')) { this.editorElement.element = document.getElementById('vB_Editor_001'); this.editorElement.insertPosition = document.getElementById('vB_Editor_001_controls'); return true; }; } else { this.editorElement.element = document.getElementById('vB_Editor_QE_' + this.editorElement.editCounter + '_editor'); this.editorElement.insertPosition = document.getElementById('vB_Editor_QE_' + this.editorElement.editCounter + '_controls'); return true; }; return false; }, // only call this method, all the others are called automatically. // this method does not cache the DOM once it's generated due to the cloneNode() problem // with DOM events. It would be more complicated to fix that than to parse // the code again. processButtons : function(onlyQuickEditor) { if (!this.__init__(onlyQuickEditor)) { return; }; // get source DOM (ignore leading whitespace) var XMLRoot = (Node.DOCUMENT_TYPE_NODE == this.srcDOM.firstChild.nodeType) ? this.srcDOM.childNodes[1] : this.srcDOM.firstChild; // begin processing the DOM recursively var resultDOM = this.traverseSourceDOM(XMLRoot); if (false === resultDOM) { GM_log('Could not process controls.'); return; }; // insert resultDOM into document // be careful not to use resultDOM.childNodes.length directly in the // loop since it will decrease when you move the elements! var len = resultDOM.childNodes.length; for (var i = 0; i < len; ++i) { this.editorElement.insertPosition.appendChild(resultDOM.firstChild); }; }, // this method will return false when element is not an element node or if no // specific handler could be found for that element, otherwise the processed // element is returned traverseSourceDOM : function(DOMNode) { try { // ignore non-element nodes if (Node.ELEMENT_NODE !== DOMNode.nodeType) { return false; }; // if no handler for current element available, continue // yes, this is one of the few meaningful purposes for eval() if (typeof CtrlHandlers[DOMNode.nodeName.toLowerCase()] == 'undefined') { GM_log('Could not find handler for element "' + DOMNode.nodeName.toLowerCase() + '", ignoring it.'); return false; }; return CtrlHandlers[DOMNode.nodeName.toLowerCase()](DOMNode); } catch (ex) { GM_log('Error processing DOM! Message: ' + ex); return false; }; } }; /** * Part V: Control handlers for processing the control types like menustrip, section, button, select etc. * Control handlers have the the lowercase name of the element they shall process. * * NOTE: it's each control handler's responsibility to check for child nodes and to append them. * This will give you the ability to control the processing of child nodes! * The return value must always be the resulting DOM. */ var CtrlHandlers = { // ROOT NODE, DON'T MODIFY THIS CONTROL HANDLER! editoroverlay : function(element) { // it's irrelevant which (block level) element it is, it's just a root // container to which all other elements will be appended: var resultDOM = document.createElement('div'); for (var i = 0; i < element.childNodes.length; ++i) { var processed = editorMod.traverseSourceDOM(element.childNodes[i]); if (false !== processed) { resultDOM.appendChild(processed); }; }; return resultDOM; }, // menustrips menustrip : function(element) { var resultDOM = Hlp_createTableStructure(); for (var i = 0; i < element.childNodes.length; ++i) { var td = document.createElement('td'); var processed = editorMod.traverseSourceDOM(element.childNodes[i]); if (false !== processed) { td.appendChild(processed); resultDOM.getElementsByTagName('tr')[0].appendChild(td); }; }; return resultDOM; }, // sections section : function(element) { var resultDOM = null; var sectionType = element.getAttribute('type'); // be generic to make it easy to implement other section types in future switch (sectionType) { case 'fieldset': resultDOM = document.createElement(sectionType); resultDOM.setAttribute('class', 'fieldseteditor'); if (element.getAttribute('legend')) { var legend = document.createElement('legend') legend.appendChild(document.createTextNode(element.getAttribute('legend'))); resultDOM.appendChild(legend); }; break; default: resultDOM = document.createElement('div'); }; resultDOM.appendChild(Hlp_createTableStructure()); for (var i = 0; i < element.childNodes.length; ++i) { var td = document.createElement('td'); var processed = editorMod.traverseSourceDOM(element.childNodes[i]); if (false !== processed) { td.appendChild(processed); resultDOM.getElementsByTagName('tr')[0].appendChild(td); }; }; return resultDOM; }, // buttons button : function(element) { var resultDOM = document.createElement('div'); // parse parameters var paramNodes = element.getElementsByTagName('param'); var params = { 'url' : null, 'text' : null, 'height' : null, 'width' : null, 'insertbefore' : null, 'insertafter' : null }; for (var i = 0; i < paramNodes.length; ++i) { params[paramNodes[i].getAttribute('name').toLowerCase()] = paramNodes[i].getAttribute('value'); }; if (typeof params['text'] == 'undefined') { GM_log('Could not process button since no parameter "text" was specified'); return; }; if (params.url !== null) { // image button resultDOM.setAttribute('class', 'imagebutton'); var img = document.createElement('img'); if (params.height !== null) { img.setAttribute('height', parseInt(params.height)); }; if (params.width !== null) { img.setAttribute('width', parseInt(params.width)); }; img.setAttribute('src', params.url); img.setAttribute('alt', params.text); img.setAttribute('title', params.text); resultDOM.appendChild(img); } else { // text button resultDOM.appendChild(document.createTextNode(params.text)); }; if (params.insertbefore !== null) { resultDOM.setAttribute('vbmod-insertBefore', params.insertbefore); } else { resultDOM.setAttribute('vbmod-insertBefore', ''); }; if (params.insertafter !== null) { resultDOM.setAttribute('vbmod-insertAfter', params.insertafter); } else { resultDOM.setAttribute('vbmod-insertAfter', ''); }; // set script if specified var scriptNodes = element.getElementsByTagName('script'); if (typeof scriptNodes[0] != 'undefined') { resultDOM.setAttribute('vbmod-script', scriptNodes[0].firstChild.nodeValue); }; Hlp_assignStyles(resultDOM, { 'border' : 'medium none', 'cursor' : 'default', 'padding' : '1px', 'background' : 'rgb(225, 225, 226) none repeat scroll 0% 0%', '-moz-background-clip' : 'border', '-moz-background-origin' : 'paddingm', '-moz-background-inline-policy' : 'continuous', 'color' : 'rgb(0, 0, 0)' }); // hover events resultDOM.addEventListener('mouseover', function() { Hlp_assignStyles(this, { 'border' : '1px solid rgb(49, 106, 197)', 'padding' : '0px', 'background' : 'rgb(193, 210, 238) none repeat scroll 0% 0%', 'MozBackgroundClip' : '-moz-initial', 'MozBackgroundOrigin' : '-moz-initial', 'MozBackgroundInlinePolicy': '-moz-initial', 'color' : 'rgb(0, 0, 0)' }); }, true); resultDOM.addEventListener('mouseout', function() { Hlp_assignStyles(this, { 'border' : 'medium none', 'padding' : '1px', 'background' : 'rgb(225, 225, 226) none repeat scroll 0% 0%', 'MozBackgroundClip' : '-moz-initial', 'MozBackgroundOrigin' : '-moz-initial', 'MozBackgroundInlinePolicy': '-moz-initial', 'color' : 'rgb(0, 0, 0)' }); }, true); // set editor information to make more than one editor at once working resultDOM.setAttribute('vbmod-editorId', editorMod.editorElement.element.getAttribute('id')); // click event Hlp_registerActionEvent('click', resultDOM); return resultDOM; }, // selects select : function(element) { var resultDOM = document.createElement('select'); var options = element.getElementsByTagName('option'); for (var i = 0; i < options.length; ++i) { var opt = document.createElement('option'); var params = { 'text' : null, 'insertbefore' : null, 'insertafter' : null }; // process paramters var paramNodes = options[i].getElementsByTagName('param'); for (var paramCnt = 0; paramCnt < paramNodes.length; ++paramCnt) { params[paramNodes[paramCnt].getAttribute('name').toLowerCase()] = paramNodes[paramCnt].getAttribute('value'); }; if (typeof params['text'] == 'undefined') { GM_log('Could not process option since no parameter "text" was specified'); return; }; if (params.insertbefore !== null) { resultDOM.setAttribute('vbmod-insertBefore', params.insertbefore); } else { resultDOM.setAttribute('vbmod-insertBefore', ''); }; if (params.insertafter !== null) { resultDOM.setAttribute('vbmod-insertAfter', params.insertafter); } else { resultDOM.setAttribute('vbmod-insertAfter', ''); }; opt.setAttribute('vbmod-insertbefore', params.insertbefore); opt.setAttribute('vbmod-insertafter', params.insertafter); var scriptNodes = options[i].getElementsByTagName('script'); if (typeof scriptNodes[0] != 'undefined') { opt.setAttribute('vbmod-script', scriptNodes[0].firstChild.nodeValue); }; opt.appendChild(document.createTextNode(params.text)); resultDOM.appendChild(opt); }; // set editor information to make more than one editor at once working resultDOM.setAttribute('vbmod-editorId', editorMod.editorElement.element.getAttribute('id')); // change event Hlp_registerActionEvent('change', resultDOM); return resultDOM; } }; /** * Part VI: Helper methods */ // assign styles to a specified element. These styles must have JavaScript syntax (camelCase instead of dashes -) function Hlp_assignStyles(el, styles) { for (var msCnt in styles) { el.style[msCnt] = styles[msCnt]; }; }; // check a XML string for well-formedness. If you don't set the second parameter to false an alert with // a error message will be shown in case of compilation errors function Hlp_checkXMLWellFormedness(xmlString, suppressAlert) { var DOM = new DOMParser().parseFromString(xmlString, 'text/xml'); with (DOM.documentElement) { if (tagName == 'parseerror' || namespaceURI == 'http://www.mozilla.org/newlayout/xml/parsererror.xml') { if (!suppressAlert) { alert(i18n.get().XMLCompilationError + firstChild.nodeValue); }; return false; }; }; return true; }; // create a table structure which vBulletin loves (yes, they're all the same stupid tables over and over...) function Hlp_createTableStructure() { var table = document.createElement('table'); // I'm sorry, but these bad attributes are preset by the original tables: with (table) { setAttribute('cellpadding', 0); setAttribute('cellspacing', 0); setAttribute('border', 0); appendChild(document.createElement('tbody')); lastChild.appendChild(document.createElement('tr')); }; return table; }; // replace variables with a replacement string function Hlp_replaceVariables(variableName, variableValue, originalString) { var pos = 0, prevPos = 0, newString = originalString; while ((pos = newString.indexOf('$' + variableName, prevPos)) > -1) { if (!/[^\w_]/.test(newString.charAt(pos + variableName.length + 1)) && pos + variableName.length + 1 < newString.length) { prevPos = pos + variableName.length + 1; continue; }; var escPos = pos; while (-1 < escPos && '\\' == newString.charAt(escPos - 1)) { --escPos; }; var before = newString.slice(0, escPos); var after = newString.slice(pos + 1 + variableName.length); var escPart = newString.slice(escPos, pos); var varPart = (escPart.length & 1) ? '$' + variableName : variableValue; escPart = escPart.substr(0, Math.floor(escPart.length / 2)); newString = before + escPart + varPart + after; prevPos = (before + escPart + varPart).length; }; if (!prevPos) { return originalString; }; return newString; }; // add click event listener to performan the defined action for the given control element function Hlp_registerActionEvent(event, element) { element.addEventListener(event, function() { var script = null; // if select element if (event == 'change') { var insBefore = this.options[this.selectedIndex].getAttribute('vbmod-insertbefore').replace(/\\n/g, '\n'); var insAfter = this.options[this.selectedIndex].getAttribute('vbmod-insertafter').replace(/\\n/g, '\n'); if (this.options[this.selectedIndex].hasAttribute('vbmod-script')) { script = this.options[this.selectedIndex].getAttribute('vbmod-script'); }; } else { var insBefore = this.getAttribute('vbmod-insertbefore').replace(/\\n/g, '\n'); var insAfter = this.getAttribute('vbmod-insertafter').replace(/\\n/g, '\n'); if (this.hasAttribute('vbmod-script')) { script = this.getAttribute('vbmod-script'); }; }; var editorId = this.getAttribute('vbmod-editorId'); if (script !== null) { try { var returnValues = eval('(function() { ' + script + ' })();'); if (typeof returnValues != 'object') { returnValues = [returnValues]; }; for (var i in returnValues) { insBefore = Hlp_replaceVariables(i, returnValues[i], insBefore); insAfter = Hlp_replaceVariables(i, returnValues[i], insAfter); }; } catch(ex) { GM_log('User script error: ' + ex); }; }; Hlp_insertText(insBefore, insAfter, editorId); }, true); }; // insert text into specified editor function Hlp_insertText(startTag, endTag, editorId) { if (!editorId) { return; }; var textareaElement = document.getElementById(editorId).getElementsByTagName('textarea')[0]; textareaElement.focus(); // save current scrolling position var scrollTop = textareaElement.scrollTop; var scrollLeft = textareaElement.scrollLeft; var start = textareaElement.selectionStart; var end = textareaElement.selectionEnd; var insText = textareaElement.value.substring(start, end); textareaElement.value = textareaElement.value.substr(0, start) + startTag + insText + endTag + textareaElement.value.substr(end); var pos; if (insText.length == 0) { pos = start + startTag.length; } else { pos = start + startTag.length + insText.length + endTag.length; }; textareaElement.selectionStart = pos; textareaElement.selectionEnd = pos; // reset scrolling textareaElement.scrollTop = scrollTop; textareaElement.scrollLeft = scrollLeft; }; /** * Part VI: set event listener for quick editors */ var vbMod_quickEditEventHandler = function() { if (document.getElementById('vB_Editor_QE_' + editorMod.editorElement.editCounter + '_controls')) { editorMod.findEditor(true); editorMod.processButtons(true); // increase counter for next quick editor ++editorMod.editorElement.editCounter; }; }; document.addEventListener('DOMSubtreeModified', function() { // Workaround for GreaseMonkey bug setTimeout(vbMod_quickEditEventHandler, 0); }, true); /** * Part VIII: initialization */ i18n.setLocale('de-DE'); editorMod.processButtons();
Kommentar