MediaWiki:Gadget-recentchanges.js

Aus Stupidedia, der sinnfreien Enzyklopädie!
Wechseln zu: Navigation, Suche

Wichtig: Nach dem Speichern musst Du deinem Browser sagen, dass er die neue Version laden soll: Mozilla/Firefox: Strg-Shift-R, IE: Strg-F5, Safari: Cmd-Shift-R, Konqueror: F5.

/**
 * Lilsis – autokratisches RC-Überwachungsprogramm
 * Autor: [[Benutzer:Phorgo]]
 * Lizenz: GNU FDL
 *
 * Verwendete Dateien:
 * [[Datei:Lilsis Logo.svg]]
 * [[Datei:Lilsis Button Toggle.svg]]
 * [[Datei:Lilsis Button Close.svg]]
 * [[Datei:Lilsis Button Open.svg]]
 * [[Datei:Lilsis Button Refresh.svg]]
 * [[Datei:Lilsis Button Stop.svg]]
 * [[Datei:Lilsis Button Options.svg]]
 * [[Datei:Lilsis Status Loading.svg]]
 * [[Datei:Lilsis Status Error.svg]]
 * [[Datei:Lilsis Status Null.svg]]
 * [[Datei:Lilsis Status Stopped.svg]]
 *
 * <nowiki>
 */

// Hintergrundeigenschaften
gRC.changes = {};
gRC.watchlist = [];
gRC.linkboxMoved = false; // Um Schwierigkeiten mit der Boxenhöhenberechnung aus dem Weg zu gehen
gRC.defaultOptions = { // Standardoptionen
    rclimit: 6, // Anzahl der zu ladenden Änderungen
    timeFormat: '$H:$I:$S', // Zeitformat für die erste Spalte
    interval: 10, // Intervall in Sekunden
    placeholderRows: 6, // Anzahl der Platzhalterzeilen
    slideSpeed: 'normal', // Klappgeschwindigkeit der Box
    diffLimit: 50000, // Maximale Größe der anzuzeigenden Versionsunterschiede, um Browserschwierigkeiten bei Mammutänderungen zu vermeiden
    automoveLinkbox: true, // Linkbox hoch- und runterschieben
    resizable: true // Größer und kleiner ziehen
};
// Bots können ihresgleichen sehen
if ( global.groups.bot === false ) {
    gRC.defaultOptions.rcshow = '!bot';
}
gRC.tempOptions = {}; // Temporäre Optionen
gRC.stopped = false;
gRC.requestTime = 0; // Wird benötigt, damit verspätete, inaktuelle Requests ignoriert werden.
gRC.errorLog = [];
gRC.windowZIndex = 0; // Basis-Höhenlage der Fenster, damit wir sie später ordnen können, ohne dass sich ihr z-index beständig erhöht.
gRC.windowIDs = [];

// Konfigurationseigenschaften
gRC.userOptions = $.parseJSON( mw.user.options.get( 'userjs-recentchanges' ) ) || {}; // Benutzeroptionen
gRC.cookieName = 'gRC_show';
gRC.pageLinks = [];
gRC.userLinks = [];
gRC.modules = {};

// Grafiken
gRC.toggleButton = '/images/thumb/a/a0/Lilsis_Button_Toggle.svg/16px-Lilsis_Button_Toggle.svg.png';
gRC.closeButton = '/images/thumb/f/fd/Lilsis_Button_Close.svg/16px-Lilsis_Button_Close.svg.png';
gRC.openButton = '/images/thumb/e/e3/Lilsis_Button_Open.svg/32px-Lilsis_Button_Open.svg.png';
gRC.refreshButton = '/images/thumb/f/f2/Lilsis_Button_Refresh.svg/16px-Lilsis_Button_Refresh.svg.png';
gRC.stopButton = '/images/thumb/c/c8/Lilsis_Button_Stop.svg/16px-Lilsis_Button_Stop.svg.png';
gRC.optionButton = '/images/thumb/2/2d/Lilsis_Button_Options.svg/16px-Lilsis_Button_Options.svg.png';
gRC.statusLoading = '/images/thumb/9/9a/Lilsis_Status_Loading.svg/16px-Lilsis_Status_Loading.svg.png';
gRC.statusError = '/images/thumb/4/4e/Lilsis_Status_Error.svg/16px-Lilsis_Status_Error.svg.png';
gRC.statusNull = '/images/thumb/a/ab/Lilsis_Status_Null.svg/16px-Lilsis_Status_Null.svg.png';
gRC.statusStopped = '/images/thumb/4/4b/Lilsis_Status_Stopped.svg/16px-Lilsis_Status_Stopped.svg.png';

/**
 * Initiierung und Containeraufbau
 */

gRC.init = function() {
    gRC.createTempOptions();
    $( '<div id="gRC" />' ).appendTo( 'body' );
    gRC.buildContainer();
    gRC.buildHeader();
    gRC.buildBody();
    gRC.buildTable();
    gRC.buildDummies();
    gRC.buildTab();
    gRC.getWatchlist();
    // Cookie setzen, falls nötig
    if ( getCookie( gRC.cookieName ) === null ) {
        setCookie( gRC.cookieName, '1' );
    }
    if ( getCookie( gRC.cookieName ) === '0' ) {
        gRC.hide();
    }
    else {
        gRC.show( true );
    }
}

// Benutzeroptionen überschreiben Standardoptionen, temporäre Optionen überschreiben Benutzeroptionen. Das ist die Ordnung der Dinge.
gRC.createTempOptions = function() {
    gRC.tempOptions = {};
    // Standardoptionen in die temporäre Abfrage übernehmen
    for ( i in gRC.defaultOptions ) {
        gRC.tempOptions[i] = gRC.defaultOptions[i];
    }
    // Benutzeroptionen übernehmen
    // null entfernt die jeweilige Option.
    for ( i in gRC.userOptions ) {
        gRC.tempOptions[i] = gRC.userOptions[i];
        if ( gRC.tempOptions[i] === false || gRC.tempOptions[i] === null ) {
            delete gRC.tempOptions[i];
        }
    }
}

gRC.buildContainer = function() {
    var container = $( '<div id="gRC-container" />' );
    container.appendTo( '#gRC' );
    if ( gRC.tempOptions.resizable ) {
        container.resizable( { alsoResize: '#gRC-body > div', handles: 'n', resize: function( event, ui ) {
            ui.element.css( 'top', '' );
            if ( gRC.tempOptions.automoveLinkbox !== undefined ) {
                $( '#gPlb_box' ).animate( { bottom: ui.element.height() }, 0 );
                gRC.linkboxMoved = true;
            }
        } } );
    }
}

gRC.buildHeader = function() {
    // Titel
    var title = $( '<div id="gRC-title" />' );
    title.html( '<img src="/images/thumb/a/a1/Lilsis_Logo.svg/16px-Lilsis_Logo.svg.png" alt="Logo" /> <span>Lilsis – autokratisches RC-Überwachungsprogramm<span class="time"></span></span>' );
    // Buttons
    var buttons = $( '<div id="gRC-buttons" />' );
    // Status
    var image = $( '<img alt="Alles in Ordnung!" id="gRC-status" title="Alles in Ordnung!" />' );
    image.attr( 'src', gRC.statusNull );
    buttons.append( image );
    // Refresh
    var image = $( '<img alt="Aktualisieren" id="gRC-refreshButton" class="gRC-button" title="Aktualisieren" />' );
    image.attr( 'src', gRC.refreshButton );
    buttons.append( image );
    // Refresh
    var image = $( '<img alt="Anhalten" id="gRC-stopButton" class="gRC-button" title="Anhalten" />' );
    image.attr( 'src', gRC.stopButton );
    buttons.append( image );
    // Optionen
    var image = $( '<img alt="Optionen" id="gRC-optionButton" class="gRC-button" title="Optionen" />' );
    image.attr( 'src', gRC.optionButton );
    buttons.append( image );
    // Schließen
    var image = $( '<img alt="Schließen" id="gRC-closeButton" class="gRC-button" title="Schließen" />' );
    image.attr( 'src', gRC.closeButton );
    buttons.append( image );
    // Header
    var header = $( '<div id="gRC-header" />' );
    header.append( title );
    header.append( buttons );
    header.append( '<div class="visualClear"></div>' ); // Den brauchen wir wegen der float-Elemente
    header.appendTo( '#gRC-container' );
    $( '#gRC-refreshButton' ).click( gRC.refresh );
    $( '#gRC-stopButton' ).click( function() {
        clearTimeout( gRC.timeout );
        gRC.stopped = true;
        $( '#gRC-status' ).attr( 'src', gRC.statusStopped );
        $( '#gRC-status' ).attr( 'alt', 'Box gestoppt' );
        $( '#gRC-status' ).attr( 'title', 'Box gestoppt' );
    } );
    $( '#gRC-optionButton' ).click( function() {
        gRC.openWindow();
    } );
    $( '#gRC-closeButton' ).click( gRC.hide );
}

gRC.buildBody = function() {
    var body = $( '<div id="gRC-body" />' );
    body.append( '<div />' );
    body.appendTo( '#gRC-container' );
}

gRC.buildTable = function() {
    var table = $( '<table id="gRC-table" cellspacing="0" />' );
    // Überhang verstecken, um eventuelle hässliche Scrollbalken zu vermeiden
    $( '#gRC-body' ).css( 'overflow', 'hidden' );
    // Platzhalterzeilen, damit die Tabelle nicht so leer aussieht, bevor die Daten geladen wurden
    table.append( '<tr class="changeRow row-bg0"><td colspan="7">Lade gerade …</td></tr>' );
    for ( var i = 0; i < ( parseInt( gRC.tempOptions.placeholderRows ) - 1 ); i++ ) {
        var bgClass = ( i % 2 === 1 ) ? 'row-bg0' : 'row-bg1';
        table.append( '<tr class="changeRow ' + bgClass + '"><td colspan="7">&nbsp;</td></tr>' );
    }
    table.appendTo( '#gRC-body div' );
}

gRC.buildDummies = function() {
    var dummy = $( '<div class="gRC-dummy" />' );
    dummy.css( 'height', $( '#gRC-container' ).height() );
    dummy.appendTo( '#footer' );
    // Für Vector brauchen wir einen zusätzlichen Dummy in der Sidebar
    if ( skin === 'vector' ) {
        var dummy = $( '<div class="gRC-dummy" />' );
        dummy.css( 'height', $( '#gRC-container' ).height() );
        dummy.appendTo( '#mw-panel' );
    }
}

gRC.buildTab = function() {
    var tab = $( '<div id="gRC-tab" />' );
    var image = $( '<img alt="Öffnen" />' );
    image.attr( 'src', gRC.openButton );
    tab.append( image );
    tab.attr( 'title', 'Öffnen' );
    tab.appendTo( '#gRC' );
    $( '#gRC-tab' ).click( gRC.show );
}

gRC.show = function( startup ) {
    gRC.stopped = false;
    gRC.getData( true );
    setCookie( gRC.cookieName, '1' );
    if ( gRC.tempOptions.slideSpeed.toString().match( /^[0-9\.]+$/ ) !== null ) {
        gRC.tempOptions.slideSpeed = parseFloat( gRC.tempOptions.slideSpeed );
    }
    var speed = ( startup === true ) ? 0 : gRC.tempOptions.slideSpeed;
    // Linkbox nach oben schieben, falls nötig
    var containerHeight = $( '#gRC-container' ).height();
    if ( startup === true ) {
        setHook( 'AfterGadgetExecution', function() {
            if ( $( '#gPlb_box' ).css( 'bottom' ) === '0px' && gRC.tempOptions.automoveLinkbox !== undefined ) {
                $( '#gPlb_box' ).animate( { bottom: containerHeight }, speed );
                gRC.linkboxMoved = true;
            }
        } );
    }
    else {
        if ( $( '#gPlb_box' ).css( 'bottom' ) === '0px' && gRC.tempOptions.automoveLinkbox !== undefined ) {
            $( '#gPlb_box' ).animate( { bottom: containerHeight }, speed );
            gRC.linkboxMoved = true;
        }
    }
    if ( skin === 'vector' && $( '#gRC-tab' ).css( 'left' ) === '0px' ) {
        $( '#mw-panel .gRC-dummy' ).animate( { height: containerHeight }, speed );
    }
    else {
        $( '.gRC-dummy' ).animate( { height: containerHeight }, speed );
    }
    $( '#gRC-container, .gRC-dummy' ).slideDown( speed );
    $( '#gRC-tab' ).slideUp( speed );
}

gRC.hide = function() {
    gRC.stopped = true;
    clearTimeout( gRC.timeout );
    setCookie( gRC.cookieName, '0' );
    // Linkbox wieder nach unten schieben
    if ( gRC.tempOptions.slideSpeed.toString().match( /^[0-9\.]+$/ ) !== null ) {
        gRC.tempOptions.slideSpeed = parseFloat( gRC.tempOptions.slideSpeed );
    }
    if ( gRC.linkboxMoved === true && gRC.tempOptions.automoveLinkbox !== undefined ) {
        $( '#gPlb_box' ).animate( { bottom: 0 }, gRC.tempOptions.slideSpeed );
    }
    if ( skin === 'vector' && $( '#gRC-tab' ).css( 'left' ) === '0px' ) {
        $( '#mw-panel .gRC-dummy' ).animate( { height: $( '#gRC-tab' ).height() }, gRC.tempOptions.slideSpeed );
        $( '#mw-panel .gRC-dummy' ).slideDown( gRC.tempOptions.slideSpeed );
        $( '#footer .gRC-dummy' ).slideUp( gRC.tempOptions.slideSpeed );
    }
    else {
        $( '.gRC-dummy' ).animate( { height: $( '#gRC-tab' ).height() }, gRC.tempOptions.slideSpeed );
        $( '.gRC-dummy' ).slideDown( gRC.tempOptions.slideSpeed );
    }
    $( '#gRC-container' ).slideUp( gRC.tempOptions.slideSpeed );
    $( '#gRC-tab' ).slideDown( gRC.tempOptions.slideSpeed );
}

/**
 * Datenabfrage und Tabellenaufbau
 */

gRC.refresh = function() {
    gRC.stopped = false;
    gRC.getWatchlist();
    gRC.getData( true );
}

gRC.getData = function( refresh ) {
    if ( gRC.stopped === true ) {
        return;
    }
    // Timeout löschen, um doppelte Requests zu vermeiden
    clearTimeout( gRC.timeout );
    gRC.timeout = setTimeout( gRC.getData, parseFloat( gRC.tempOptions.interval ) * 1000 );
    // Das Ladesymbol anzeigen
    if ( gRC.tempOptions.noBlink === undefined ) { // Aber nur, wenn wir das wollen.
        $( '#gRC-status' ).attr( 'src', gRC.statusLoading );
    }
    $( '#gRC-status' ).attr( 'alt', 'Lade gerade …' );
    $( '#gRC-status' ).attr( 'title', 'Lade gerade …' );
    var query = {
        action: 'query',
        list: 'recentchanges',
        rcprop: 'user|userid|comment|parsedcomment|flags|timestamp|title|ids|sizes|redirect|loginfo|tags',
        rctoken: 'patrol'
    };
    if ( global.groups.sysop === true || global.groups.f === true ) {
        query.rcprop += '|patrolled';
        query.rctoken = 'patrol';
    }
    for ( i in gRC.tempOptions ) {
        if ( i.match( /^rc/ ) !== null ) {
            query[i] = gRC.tempOptions[i];
        }
    }
    if ( query.rcshow !== undefined ) {
        if ( query.rcshow.match( /patrolled/ ) !== null && global.groups.sysop === false && global.groups.f === false ) {
            query.rcshow = query.rcshow.replace( /\|?!?patrolled/g, '' );
        }
    }
    var requestTime = time.getTime();
    gRC.requestTime = requestTime;
    api.request( new api.RequestObject( query, 'get' ), gRC.receiveData, [refresh, requestTime] );
}

gRC.receiveData = function( data, zeug, jqxhr ) {
    if ( gRC.stopped === true || zeug[1] < gRC.requestTime ) {
        return;
    }
    // Status bearbeiten
    if ( jqxhr.status !== 200 ) {
        gRC.errorLog.push( { startTime: zeug[1], endTime: time.getTime(), type: 'HTTP', code: jqxhr.status, text: jqxhr.statusText } );
        gRC.modules.errorLog.prototype.refreshLog();
        // Statusicon aktualisieren
        $( '#gRC-status' ).attr( 'src', gRC.statusError );
        $( '#gRC-status' ).attr( 'alt', 'HTTP-Error ' + jqxhr.status.toString() + ': ' + jqxhr.statusText );
        $( '#gRC-status' ).attr( 'title', 'HTTP-Error ' + jqxhr.status.toString() + ': ' + jqxhr.statusText );
        // Tabelleninhalt durch Fehlermeldung ersetzen
        $( '#gRC-table' ).html( '' );
        for ( var i = 0; i < parseInt( gRC.tempOptions.placeholderRows ); i++ ) {
            var bgClass = ( i % 2 === 0 ) ? 'row-bg0' : 'row-bg1';
            $( '#gRC-table' ).append( '<tr class="changeRow ' + bgClass + '"><td colspan="7">&nbsp;</td></tr>' );
        }
        $( '#gRC-table tr:first td' ).attr( 'colspan', '7' );
        $( '#gRC-table tr:first td' ).html( 'HTTP-Error ' + jqxhr.status.toString() + ': ' + jqxhr.statusText );
        gRC.changes = {};
        return;
    }
    // Falls die Daten als String zurückkommen, sollen sie in ein Objekt umgewandelt werden.
    if ( typeof data !== 'object' ) {
        data = $.parseJSON( data );
    }
    // API-Fehlermeldungen examinieren
    if ( data.error !== undefined ) {
        var code = data.error.code;
        var info = data.error.info;
        gRC.errorLog.push( { startTime: zeug[1], endTime: time.getTime(), type: 'API', code: code, text: info } );
        gRC.modules.errorLog.prototype.refreshLog();
        // Statusicon aktualisieren
        $( '#gRC-status' ).attr( 'src', gRC.statusError );
        $( '#gRC-status' ).attr( 'alt', 'API-Error ' + code + ': ' + info );
        $( '#gRC-status' ).attr( 'title', 'API-Error ' + code + ': ' + info );
        // Tabelleninhalt durch Fehlermeldung ersetzen
        $( '#gRC-table' ).html( '' );
        for ( var i = 0; i < parseInt( gRC.tempOptions.placeholderRows ); i++ ) {
            var bgClass = ( i % 2 === 0 ) ? 'row-bg0' : 'row-bg1';
            $( '#gRC-table' ).append( '<tr class="changeRow ' + bgClass + '"><td colspan="7">&nbsp;</td></tr>' );
        }
        $( '#gRC-table tr:first td' ).attr( 'colspan', '7' );
        $( '#gRC-table tr:first td' ).html( 'API-Error ' + code + ': ' + info );
        gRC.changes = {};
        return;
    }
    $( '#gRC-status' ).attr( 'src', gRC.statusNull );
    $( '#gRC-status' ).attr( 'alt', 'Alles in Ordnung!' );
    $( '#gRC-status' ).attr( 'title', 'Alles in Ordnung!' );
    var changes = api.makeArray( data );
    $( '#gRC-title span.time' ).html( ' – Stand: ' + time.getText( '$W, $D.$M.$Y, $H:$I:$S', gRC.requestTime ) );
    // Wenn sich seit dem letzten Request nichts in den LÄ geändert hat, brauchen wir die Tabelle nicht zu aktualisieren, es sei denn, eine Aktualisierung wird erzwungen.
    if ( $.toJSON( changes ) === $.toJSON( gRC.changes ) && zeug[0] !== true ) {
        return;
    }
    gRC.changes = changes;
    $( '#gRC-body' ).attr( 'style', null ); // overflow soll wieder auf die Standardeinstellung zurückgesetzt werden, damit die Scrollbalken notfalls zur Verfügung stehen
    $( '#gRC-table .changeRow' ).remove(); // Bisherige Zeilen entfernen
    $.each( changes, function( i ) {
        $( '#gRC-table' ).append( gRC.createRow( this, i ) );
    } );
    for ( var i = $( '#gRC-table tr' ).length; i < parseInt( gRC.tempOptions.placeholderRows ); i++ ) {
        var bgClass = ( i % 2 === 0 ) ? 'row-bg0' : 'row-bg1';
        $( '#gRC-table' ).append( '<tr class="changeRow ' + bgClass + '"><td colspan="7">&nbsp;</td></tr>' );
    }
}

gRC.createRow = function( change, i ) {
    var row = $( '<tr class="changeRow" />' );
    // Abwechselnd heller und dunkler Hintergrund
    var bgClass = ( i % 2 === 0 ) ? 'row-bg0' : 'row-bg1';
    row.addClass( bgClass );
    if ( change.title === undefined ) {
        row.html( '<td colspan="7">&nbsp;</td>' );
        return row;
    }
    // Klasse für den Namensraum
    row.addClass( 'row-ns-' + change.ns.toString() );
    // Klassen für Flags und Konsorten
    if ( change.patrolled === undefined && ( global.groups.sysop === true || global.groups.f === true ) ) {
        row.addClass( 'row-unpatrolled' );
    }
    if ( change['new'] !== undefined ) {
        row.addClass( 'row-new' );
    }
    if ( change.minor !== undefined ) {
        row.addClass( 'row-minor' );
    }
    if ( change.bot !== undefined ) {
        row.addClass( 'row-bot' );
    }
    if ( change.anon !== undefined ) {
        row.addClass( 'row-anon' );
    }
    if ( change.redirect !== undefined ) {
        row.addClass( 'row-redirect' );
    }
    // Klassen für Logbucheinträge
    if ( change.type === 'log' ) {
        row.addClass( 'row-log' );
        row.addClass( 'row-logtype-' + change.logtype );
        row.addClass( 'row-logaction-' + change.logaction );
    }
    // Klasse für beobachtete Seiten
    if ( $.inArray( change.title, gRC.watchlist ) !== -1 ) {
        row.addClass( 'row-watched' );
    }
    // ID für rcid
    row.attr( 'id', 'rcid-' + change.rcid );
    row.append( gRC.createWindowCol( change ) );
    row.append( gRC.createTimeCol( change ) );
    row.append( gRC.createSizeCol( change ) );
    row.append( gRC.createFlagCol( change ) );
    row.append( gRC.createTitleCol( change ) );
    row.append( gRC.createUserCol( change ) );
    row.append( gRC.createCommentCol( change ) );
    return row;
}

gRC.createWindowCol = function( change ) {
    var text = $( '<a title="Erweitertes Fenster öffnen" />' );
    text.attr( 'href', 'javascript:gRC.openWindow( ' + $.toJSON( change ) + ' );' );
    var plusminus = ( $( '#gRC-window-' + change.rcid.toString() ).length === 0 ) ? '[+]' : '[–]';
    text.html( plusminus );
    // Spalte erstellen und Inhalt einfügen
    var col = $( '<td class="windowCol" />' );
    col.append( text );
    return col;
}

gRC.createTimeCol = function( change ) {
    var text = $( '<span />' );
    text.attr( 'title', time.getText( '$W, $D.$M.$Y, $H:$I:$S', change.timestamp ) );
    text.html( time.getText( gRC.tempOptions.timeFormat.replace( / /g, '&nbsp;' ), change.timestamp ) );
    // Spalte erstellen und Inhalt einfügen
    var col = $( '<td class="timeCol" />' );
    col.append( text );
    return col;
}

gRC.createSizeCol = function( change ) {
    var col = $( '<td class="sizeCol" />' );
    // Logbucheintrag
    if ( change.type === 'log' ) {
        var text = gRC.createLogLink( change );
    }
    // Normale Änderung oder Neuerstellung
    else {
        var text = $( '<span />' );
        var oldlen = change.oldlen;
        var newlen = change.newlen;
        var diff = newlen - oldlen;
        // Seitenlänge gleich geblieben
        if ( diff === 0 ) {
            text.addClass( 'mw-plusminus-null' );
            text.attr( 'title', 'Aktuelle Länge: ' + newlen.toString() + ' Bytes' );
            text.html( '0' );
        }
        // Seitenlänge erhöht
        else if ( diff > 0 ) {
            text.addClass( 'mw-plusminus-pos' );
            text.attr( 'title', 'Aktuelle Länge: ' + newlen.toString() + ' Bytes\nVorherige Länge: ' + oldlen.toString() + ' Bytes' );
            text.html( '+' + diff.toString() );
        }
        // Seitenlänge verringert
        else {
            text.addClass( 'mw-plusminus-neg' );
            text.attr( 'title', 'Aktuelle Länge: ' + newlen.toString() + ' Bytes\nVorherige Länge: ' + oldlen.toString() + ' Bytes' );
            text.html( diff.toString() );
        }
    }
    // Spalte erstellen und Inhalt einfügen
    col.append( text );
    return col;
}

gRC.createFlagCol = function( change ) {
    var col = $( '<td class="flagCol" />' );
    // Unkontrollierte Änderung
    if ( change.patrolled === undefined && ( global.groups.sysop === true || global.groups.f === true ) ) {
        col.append( '<span title="Nicht-kontrollierte Änderung" class="unpatrolled">!</span>' );
    }
    // Seite wurde neu erstellt
    if ( change['new'] !== undefined ) {
        col.append( '<span title="Neue Seite" class="newpage">N</span>' );
    }
    // Als Kleinigkeit markiert
    if ( change.minor !== undefined ) {
        col.append( '<span title="Kleine Änderung" class="minoredit">K</span>' );
    }
    // Wir sind die Bots, Widerstand ist zwecklos!
    if ( change.bot !== undefined ) {
        col.append( '<span title="Änderung durch einen Bot" class="botedit">B</span>' );
    }
    return col;
}

gRC.createTitleCol = function( change ) {
    var col = $( '<td class="titleCol" />' );
    var link = $( '<a class="mw-title" />' );
    var redirect = ( change.redirect !== undefined ) ? '?redirect=no' : ''; // Wenn die Seite ein Redirect ist, soll ?redirect=no an die URL angehängt werden, damit wir nicht weitergeleitet werden.
    link.attr( 'href', mw.util.wikiGetlink( change.title ) + redirect );
    link.attr( 'title', change.title );
    link.html( mw.html.escape( change.title ) );
    if ( $.inArray( change.title, gRC.watchlist ) !== -1 ) {
        link.addClass( '' );
    }
    col.append( link );
    col.append( ' ' );
    var text = $( '<span>(</span>' );
    var links = new Array();
    // Unterschied
    if ( change.revid !== 0 ) {
        links.push( { href: mw.util.wikiGetlink( change.title ) + '?diff={revid}&rcid={rcid}', title: 'Unterschied', text: 'U' } );
    }
    // Versionsgeschichte, wenn's die Seite gibt
    // Löschen für Diktatoren ( T = Tonne )
    if ( change.pageid !== 0 ) {
        links.push( { href: mw.util.wikiGetlink( change.title ) + '?action=history', title: 'Versionsgeschichte', text: 'V' } );
        if ( global.groups.sysop === true ) {
            links.push( { href: mw.util.wikiGetlink( change.title ) + '?action=delete', title: 'Löschen', text: 'T' } );
        }
    }
    // Andernfalls das Logbuch anzeigen
    else {
        links.push( { href: mw.util.wikiGetlink( 'Spezial:Logbuch' ) + '?page={title_urlc}', title: 'Logbuch', text: 'L' } );
    }
    $.merge( links, gRC.pageLinks );
    var linkCodes = new Array();
    $.each( links, function() {
        var href = titleToURL( gRC.replaceHolders( this.href, change ) );
        var title = ( this.title !== undefined ) ? gRC.replaceHolders( this.title, change ) : gRC.replaceHolders( this.href, change );
        var text = gRC.replaceHolders( this.text, change );
        var link = $( '<a />' );
        link.attr( 'href', href );
        link.attr( 'title', title );
        link.html( text );
        linkCodes.push( link.selfHTML() );
    } );
    text.append( linkCodes.join( '|' ) );
    text.append( ')' );
    col.append( text );
    return col;
}

gRC.createUserCol = function( change ) {
    var col = $( '<td class="userCol" />' );
    var user = change.user;
    var link = $( '<a class="mw-userlink" />' );
    // Bearbeiter ist 'ne IP
    if ( change.anon !== undefined ) {
        col.append( '<span class="ip">IP</span> ' );
        link.attr( 'href', mw.util.wikiGetlink( 'Spezial:Beiträge/' + user ) );
        link.attr( 'title', 'Beiträge von ' + user );
    }
    // Bearbeiter ist angemeldet
    else {
        link.attr( 'href', mw.util.wikiGetlink( 'Benutzer:' + user ) );
        link.attr( 'title', 'Benutzerseite von ' + user );
    }
    link.html( mw.html.escape( user ) );
    col.append( link );
    col.append( ' ' );
    var text = $( '<span class="mw-usertoollinks">(</span>' );
    var links = new Array();
    // Diskussionsseite
    links.push( { href: 'Benutzer Diskussion:{user}', title: 'Diskussionsseite von {user}', text: 'D' } );
    // Beiträge, wenn angemeldet
    if ( change.anon === undefined ) {
        links.push( { href: 'Spezial:Beiträge/{user}', title: 'Beiträge von {user}', text: 'B' } );
    }
    // Sperren
    if ( global.groups.sysop === true ) {
        links.push( { href: 'Spezial:Sperren/{user}', title: '{user} sperren', text: 'S' } );
    }
    $.merge( links, gRC.userLinks );
    var linkCodes = new Array();
    $.each( links, function() {
        var href = titleToURL( gRC.replaceHolders( this.href, change ) );
        var title = ( this.title !== undefined ) ? gRC.replaceHolders( this.title, change ) : gRC.replaceHolders( this.href, change );
        var text = gRC.replaceHolders( this.text, change );
        var link = $( '<a />' );
        link.attr( 'href', href );
        link.attr( 'title', title );
        link.html( text );
        linkCodes.push( link.selfHTML() );
    } );
    text.append( linkCodes.join( '|' ) );
    text.append( ')' );
    col.append( text );
    return col;
}

gRC.createCommentCol = function( change ) {
    var col = $( '<td class="commentCol" />' );
    if ( change.tags.length !== 0 ) {
        var text = $( '<span class="mw-tag-markers">[</span>' );
        var markers = new Array();
        $.each( change.tags, function() {
            var tag = this;
            var marker = $( '<span class="mw-tag-marker" />' );
            var tagClass = 'mw-tag-marker-' + tag;
            tagClass = tagClass.replace( /(^[0-9\-])|[\x00-\x20!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~]|\xC2\xA0/g, '_' ); // Blöde Zeichen durch Unterstriche ersetzen
            tagClass = tagClass.replace( /_+/g, '_' ); // Doppelte Unterstriche zu einem zusammenstauchen
            tagClass = tagClass.replace( /_+$/g, '' ); // Unterstriche am Ende entfernen
            marker.addClass( tagClass );
            marker.html( mw.html.escape( tag ) );
            markers.push( marker.selfHTML() );
        } );
        text.append( markers.join( ', ' ) );
        text.append( ']' );
        col.append( text );
        col.append( ' ' );
    }
    var text = $( '<span class="comment" />' );
    text.html( change.parsedcomment );
    col.append( text );
    return col;
}

// Beobachtungsliste abfragen
gRC.getWatchlist = function( cont ) {
    var query = {
        action: 'query',
        list: 'watchlistraw',
        wrlimit: 'max'
    };
    // Fortsetzung der vorigen Abfrage bei Limitüberschreitung
    if ( cont !== undefined ) {
        query.wrcontinue = cont;
    }
    // Andernfalls die temporäre Beobachtungsliste leeren
    else {
        gRC.tempWatchlist = [];
    }
    api.request( new api.RequestObject( query, 'get' ), gRC.setWatchlist );
}

gRC.setWatchlist = function( data ) {
    if ( !data ) {
        return;
    }
    $.each( data.watchlistraw, function() {
        gRC.tempWatchlist.push( this.title );
    } );
    // Falls mehr Seiten auf der Liste stehen, als in einem Request abgefragt werden können, wird gRC.getWatchlist() erneut ausgeführt.
    if ( data['query-continue'] !== undefined ) {
        gRC.getWatchlist( data['query-continue'].watchlistraw.wrcontinue );
    }
    // Andernfalls wird die temporäre Liste übernommen und die Tabelle aktualisiert.
    else {
        gRC.watchlist = gRC.tempWatchlist;
        gRC.getData( true );
    }
}

// Inhalt, href- und title-Attribute in Links setzen
gRC.setLink = function( id, context, href, title, html ) {
    title = ( $.type( title ) === 'string' ) ? title : href;
    href = titleToURL( href );
    $( 'a:eq(' + id.toString() + ')', context ).attr( 'href', href );
    $( 'a:eq(' + id.toString() + ')', context ).attr( 'title', title );
    if ( html !== undefined ) {
        $( 'a:eq(' + id.toString() + ')', context ).html( html );
    }
}

gRC.replaceHolders = function( text, change ) {
    text = text.replace( /\{change\}/gi, $.toJSON( change ) ); // Änderungsobjekt im JSON-Format
    text = text.replace( /\{revid\}/gi, change.revid.toString() ); // Neue Versions-ID
    text = text.replace( /\{oldid\}/gi, change.old_revid.toString() ); // Alte Versions-ID
    text = text.replace( /\{rcid\}/gi, change.rcid.toString() ); // Änderungs-ID
    text = text.replace( /\{ns\}/gi, change.ns.toString() ); // Namensraum-ID
    text = text.replace( /\{title\}/gi, change.title ); // Seitentitel
    text = text.replace( /\{user\}/gi, change.user ); // Benutzername
    text = text.replace( /\{title_url\}/gi, encodeURI( change.title ) ); // Seitentitel url-kodiert
    text = text.replace( /\{user_url\}/gi, encodeURI( change.user ) ); // Benutzername url-kodiert
    text = text.replace( /\{title_urlc\}/gi, encodeURIComponent( change.title ) ); // Seitentitel url-component-kodiert
    text = text.replace( /\{user_urlc\}/gi, encodeURIComponent( change.user ) ); // Benutzername url-component-kodiert
    text = text.replace( /\{token_urlc\}/gi, encodeURIComponent( global.editToken ) ); // Bearbeitungstoken url-component-kodiert
    text = text.replace( /\{title_html\}/gi, mw.html.escape( change.title ) ); // Seitentitel html-maskiert
    text = text.replace( /\{user_html\}/gi, mw.html.escape( change.user ) ); // Benutzername html-maskiert
    text = text.replace( /\{title_js\}/gi, change.title.replace( /\\/g, '\\\\' ).replace( /\'/g, '\\\'' ).replace( /\"/g, '\\"' ) ); // Seitentitel js-maskiert
    text = text.replace( /\{user_js\}/gi, change.user.replace( /\\/g, '\\\\' ).replace( /\'/g, '\\\'' ).replace( /\"/g, '\\"' ) ); // Benutzername js-maskiert
    text = text.replace( /\{token_js\}/gi, global.editToken.replace( /\\/g, '\\\\' ).replace( /\'/g, '\\\'' ).replace( /\"/g, '\\"' ) ); // Bearbeitungstoken js-maskiert
    return text;
}

execute( 'gRC.init' );

Linktipps: Faditiva und 3DPresso