Benutzer:Schnark/js/watchlist++.js
aus Wikipedia, der freien Enzyklopädie
< Benutzer:Schnark | js
Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.
- Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
- Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
- Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
- Opera: Strg+F5
//Dokumentation unter [[Benutzer:Schnark/js/watchlist++]] <nowiki>
/*global mediaWiki, OO*/
/*global CSS*/
(function ($, mw) {
"use strict";
var l10n, FLAGS,
hasOwn = Object.prototype.hasOwnProperty;
//jscs:disable maximumLineLength
l10n = {
en: {
'colon-separator': ': ',
'comma-separator': ', ',
'rc-change-size-new': '$1 {{PLURAL:$1|byte|bytes}} after change',
'pipe-separator': ' | ',
'parentheses': '($1)',
'pagetitle': '$1 - {{SITENAME}}',
'watchlist++': 'Watchlist++',
'watchlist++-mode-classical': 'Classical',
'watchlist++-mode-watchlist++': 'Watchlist++',
'watchlist++-flags-new-letter': 'N',
'watchlist++-flags-minor-letter': 'M',
'watchlist++-flags-bot-letter': 'B',
'watchlist++-flags-anon-letter': 'A',
'watchlist++-flags-log-letter': 'L',
'watchlist++-flags-wikidata-letter': 'D',
'watchlist++-flags-category-letter': 'K',
'watchlist++-tags-letter': 'T',
'watchlist++-flags-new-title': 'new page',
'watchlist++-flags-minor-title': 'minor edit',
'watchlist++-flags-bot-title': 'bot edit',
'watchlist++-flags-anon-title': 'IP edit',
'watchlist++-flags-log-title': 'log action',
'watchlist++-flags-wikidata-title': 'Wikidata',
'watchlist++-flags-category-title': 'category change',
'watchlist++-tags-title': '{{PLURAL:$1|tag|tags}}: $2',
'watchlist++-number-editors': 'by $1 users',
'watchlist++-by': 'by $1',
'watchlist++-changes': '$1 {{PLURAL:$1|change|changes}}',
'watchlist++-change': 'change',
'watchlist++-change-block': 'block',
'watchlist++-change-block-unblock': 'unblock',
'watchlist++-change-delete': 'deletion',
'watchlist++-change-delete-restore': 'undeletion',
'watchlist++-change-import': 'import',
'watchlist++-change-massmessage': 'mass message',
'watchlist++-change-move': 'move',
'watchlist++-change-newusers': 'signup',
'watchlist++-change-protect': 'protection',
'watchlist++-change-protect-unprotect': 'unprotection',
'watchlist++-change-renameuser': 'rename',
'watchlist++-change-rights': 'user right change',
'watchlist++-change-supress': 'supress',
'watchlist++-change-upload': 'upload',
'watchlist++-log-comment': '$1: $2',
'watchlist++-log-protect': '($1)',
'watchlist++-log-delete-revision': 'deleted revision',
'watchlist++-log-rights-add': '+ $1',
'watchlist++-log-rights-remove': '− $1',
'watchlist++-log-upload-overwrite': 'reupload',
'watchlist++-changes-read': '(+$1)',
'watchlist++-mark-symbol': '\u279C',
'watchlist++-read-symbol': '\u2713',
'watchlist++-unread-symbol': '×',
'watchlist++-read-until': 'mark as read up to this change',
'watchlist++-read-all': 'mark as read',
'watchlist++-unread-until': 'mark as unread from this change on',
'watchlist++-unread-all': 'mark as unread',
'watchlist++-rules-head': '{{PLURAL:$1|One rule|$1 rules}}',
'watchlist++-rules-head-expand': 'Show rules',
'watchlist++-rules-head-collapse': 'Hide rules',
'watchlist++-rules-add': 'new rule',
'watchlist++-rules-export-import': 'export/import',
'watchlist++-rules-delete-all': 'restore default rules',
'watchlist++-rules-bookmarklet': 'backup for Watchlist++',
'watchlist++-rules-bookmarklet-title': 'Install this link as bookmarklet to get a backup of your current rules',
'watchlist++-rules-delete-all-confirm': 'Are you sure you want to delete all rules and reset the default rules instead?',
'watchlist++-rules-delete': 'delete',
'watchlist++-rules-delete-confirm': 'Are you sure you want to delete the rule "$1"?',
'watchlist++-rules-edit': 'edit',
'watchlist++-export-import-rules': 'These are the current rules encoded as JSON. You can edit them as you like. Please note that changes will be applied only after reloading the page.',
'watchlist++-json-error': 'Your JSON is not valid: $1',
'watchlist++-storage-error': 'A storage error occured, changes to the rules could not be saved.',
'watchlist++-filter': '$1 $2 <code>$3</code>',
'watchlist++-filter-ns': 'The namespace',
'watchlist++-filter-title': 'The title',
'watchlist++-filter-user': 'The user',
'watchlist++-filter-diff': 'The size change',
'watchlist++-filter-flags': 'The value for the flags',
'watchlist++-filter-timestamp': 'The timestamp',
'watchlist++-filter-comment': 'The edit comment',
'watchlist++-filter-logtype': 'The log action',
'watchlist++-filter-tags': 'The change tag',
'watchlist++-type-is': 'is',
'watchlist++-type-is-not': 'is not',
'watchlist++-type-match': 'matches',
'watchlist++-type-match-not': 'doesn\'t match',
'watchlist++-type-and': 'contains at least the bits of',
'watchlist++-type-and-not': 'doesn\'t contain all the bits of',
'watchlist++-type-or': 'contains at most the bits of',
'watchlist++-type-or-not': 'dosn\'t contain only the bits of',
'watchlist++-type-gt': 'is greater than',
'watchlist++-type-gt-not': 'is less than or equal to',
'watchlist++-type-ge': 'is greater than or equal to',
'watchlist++-type-ge-not': 'is less than',
'watchlist++-rule-read-mark': 'Mark all changes as $1 and with tag no. $2, for which all these rules are met:',
'watchlist++-rule-read': 'Mark all changes as $1, for which all these rules are met:',
'watchlist++-rule-mark': 'Mark all with tag no. $1, for which all these rules are met:',
'watchlist++-rules-read': 'read',
'watchlist++-rules-unread': 'unread',
'watchlist++-rules-desc': '$1 ($2)',
'watchlist++-hhmm': '$1:$2',
'watchlist++-ddmm': '$1. $2.',
'watchlist++-toggle-read': 'Toggle read changes',
'watchlist++-add-older': 'Load older changes',
'watchlist++-add-older-title': '',
'watchlist++-add-newer': 'Load newer changes',
'watchlist++-add-newer-title': '',
'watchlist++-reset-notification': 'Mark all changes as read',
'watchlist++-rules-desc-mark-own-pages': 'Highlight changes to own pages by other users',
'watchlist++-rules-desc-hide-bots': 'Mark bot changes as read',
'watchlist++-new-rule-title': 'Create new rule',
'watchlist++-new-rule-text': 'Create a new rule by selecting how it should mark changes and which criterions must be met to apply it.',
'watchlist++-edit-rule-title': 'Edit rule',
'watchlist++-edit-rule-text': 'Edit the rule by selecting how it should mark changes and which criterions must be met to apply it.',
'watchlist++-new-rule-desc': 'Description: ',
'watchlist++-new-rule-mark': '(tag)',
'watchlist++-new-rule-read': '(read/unread)',
'watchlist++-new-rule-type': '(new criterion)',
'watchlist++-rule-mark-1': 'red',
'watchlist++-rule-mark-2': 'orange',
'watchlist++-rule-mark-3': 'yellow',
'watchlist++-rule-mark-4': 'green',
'watchlist++-rule-mark-5': 'blue',
'watchlist++-rule-mark-6': 'pink',
'watchlist++-rule-mark-7': 'violet',
'watchlist++-new-rule-help': 'Help',
'watchlist++-new-rule-save': 'Save',
'watchlist++-new-rule-cancel': 'Cancel',
'watchlist++-new-rule-help-done': 'Back',
'watchlist++-new-rule-error-no-action': 'Missing action for rule!',
'watchlist++-new-rule-error-no-filter': 'Missing filters for rule!',
'watchlist++-new-rule-error-no-number': 'Missing number for bitwise comparison!',
'watchlist++-new-rule-error-no-re': 'Invalid regular expression!',
'watchlist++-new-rule-help-text': '<ul><li>Namespace: numerical value ($1)</li><li>Title: page title (without namespace)</li><li>User: name of user</li><li>Size change: number, can be negative</li><li>Flags: numerical value from these constants: $2</li><li>Timestamp: in format yyyy-mm-ddThh:mm:ssZ</li><li>Edit comment: as source code</li><li>Log action: internal name</li><li>Change tag: internal names, separated by pipes (<code>|</code>)</li></ul>'
},
de: {
'rc-change-size-new': '$1 {{PLURAL:$1|Byte|Byte}} nach der Änderung',
'pagetitle': '$1 – {{SITENAME}}',
'watchlist++': 'Beobachtungsliste++',
'watchlist++-mode-classical': 'Klassisch',
'watchlist++-mode-watchlist++': 'Beobachtungsliste++',
'watchlist++-flags-minor-letter': 'k',
'watchlist++-flags-category-letter': 'K',
'watchlist++-flags-new-title': 'Neuanlage',
'watchlist++-flags-minor-title': 'Kleine Änderung',
'watchlist++-flags-bot-title': 'Bot-Änderung',
'watchlist++-flags-anon-title': 'IP-Änderung',
'watchlist++-flags-log-title': 'Logaktion',
'watchlist++-flags-category-title': 'Kategorieänderung',
'watchlist++-tags-title': '{{PLURAL:$1|Tag|Tags}}: $2',
'watchlist++-number-editors': 'von $1 Benutzern',
'watchlist++-by': 'von $1',
'watchlist++-changes': '$1 {{PLURAL:$1|Änderung|Änderungen}}',
'watchlist++-change': 'Änderung',
'watchlist++-change-block': 'Sperrung',
'watchlist++-change-block-unblock': 'Entsperrung',
'watchlist++-change-delete': 'Löschung',
'watchlist++-change-delete-restore': 'Wiederherstellung',
'watchlist++-change-import': 'Import',
'watchlist++-change-newusers': 'Kontoerstellung',
'watchlist++-change-massmessage': 'Massennachricht',
'watchlist++-change-move': 'Verschiebung',
'watchlist++-change-protect': 'Seitenschutz',
'watchlist++-change-protect-unprotect': 'Seitenfreigabe',
'watchlist++-change-renameuser': 'Umbenennung',
'watchlist++-change-rights': 'Benutzerrechteänderung',
'watchlist++-change-supress': 'Verstecken',
'watchlist++-change-upload': 'Upload',
'watchlist++-log-delete-revision': 'Versionslöschung',
'watchlist++-log-upload-overwrite': 'neue Dateiversion',
'watchlist++-read-until': 'bis hier als gelesen markieren',
'watchlist++-read-all': 'als gelesen markieren',
'watchlist++-unread-until': 'ab hier als ungelesen markieren',
'watchlist++-unread-all': 'als ungelesen markieren',
'watchlist++-rules-head': '{{PLURAL:$1|Eine Regel|$1 Regeln}}',
'watchlist++-rules-head-expand': 'Regeln zeigen',
'watchlist++-rules-head-collapse': 'Regeln verbergen',
'watchlist++-rules-add': 'neue Regel',
'watchlist++-rules-export-import': 'Export/Import',
'watchlist++-rules-delete-all': 'Standardregeln wiederherstellen',
'watchlist++-rules-bookmarklet': 'Sicherungskopie für Beobachtungsliste++',
'watchlist++-rules-bookmarklet-title': 'Installiere diesen Link als Bookmarklet um eine Sicherungskopie deiner aktuellen Regeln zu sichern',
'watchlist++-rules-delete-all-confirm': 'Sollen wirklich alle Regeln gelöscht werden und stattdessen wieder die Standardregeln verwendet werden?',
'watchlist++-rules-delete': 'löschen',
'watchlist++-rules-delete-confirm': 'Soll die Regel „$1“ wirklich gelöscht werden?',
'watchlist++-rules-edit': 'bearbeiten',
'watchlist++-export-import-rules': 'Folgende Daten im JSON-Format repräsentieren die aktuellen Markierungsregeln. Du kannst sie nach Belieben ändern. Beachte, dass Änderungen erst beim nächsten Mal wirksam werden.',
'watchlist++-json-error': 'Die JSON-Daten sind nicht korrekt: $1',
'watchlist++-storage-error': 'Ein Speicherfehler trat auf, Änderungen an den Regeln konnten nicht gespeichert werden.',
'watchlist++-filter-ns': 'Der Namensraum',
'watchlist++-filter-title': 'Der Seitentitel',
'watchlist++-filter-user': 'Der Benutzer',
'watchlist++-filter-diff': 'Die Größenänderung',
'watchlist++-filter-flags': 'Der Wert für die Flags',
'watchlist++-filter-timestamp': 'Der Zeitstempel',
'watchlist++-filter-comment': 'Der Bearbeitungskommentar',
'watchlist++-filter-logtype': 'Die Logaktion',
'watchlist++-filter-tags': 'Die Bearbeitungsmarkierung',
'watchlist++-type-is': 'ist',
'watchlist++-type-is-not': 'ist nicht',
'watchlist++-type-match': 'passt auf',
'watchlist++-type-match-not': 'passt nicht auf',
'watchlist++-type-and': 'enthält mindestens die Bits von',
'watchlist++-type-and-not': 'enthält nicht alle Bits von',
'watchlist++-type-or': 'enthält höchstens die Bits von',
'watchlist++-type-or-not': 'enthält nicht nur die Bits von',
'watchlist++-type-gt': 'ist größer als',
'watchlist++-type-gt-not': 'ist kleiner oder gleich',
'watchlist++-type-ge': 'ist größer oder gleich',
'watchlist++-type-ge-not': 'ist kleiner als',
'watchlist++-rule-read-mark': 'Markiere alle Änderungen als $1 und mit Kennzeichnung Nr. $2, auf die alle folgenden Regeln zutreffen:',
'watchlist++-rule-read': 'Markiere alle Änderungen als $1, auf die alle folgenden Regeln zutreffen:',
'watchlist++-rule-mark': 'Markiere alle Änderungen mit Kennzeichnung Nr. $1, auf die alle folgenden Regeln zutreffen:',
'watchlist++-rules-read': 'gelesen',
'watchlist++-rules-unread': 'ungelesen',
'watchlist++-toggle-read': 'Gelesene Änderungen ein-/ausblenden',
'watchlist++-add-older': 'Ältere',
'watchlist++-add-older-title': 'Ältere Änderungen laden',
'watchlist++-add-newer': 'Neuere',
'watchlist++-add-newer-title': 'Neuere Änderungen laden',
'watchlist++-reset-notification': 'Alle Änderungen als gelesen markieren',
'watchlist++-rules-desc-mark-own-pages': 'Änderungen an eigenen Seiten durch andere Benutzer markieren',
'watchlist++-rules-desc-hide-bots': 'Bot-Änderungen als gelesen markieren',
'watchlist++-new-rule-title': 'Neue Filterregel erstellen',
'watchlist++-new-rule-text': 'Erstelle eine neue Filterregel, indem du auswählst, welche Markierungen sie vornehmen soll und welche Kriterien alle erfüllt sein müssen, damit sie angewendet wird.',
'watchlist++-edit-rule-title': 'Filterregel bearbeiten',
'watchlist++-edit-rule-text': 'Bearbeite die Filterregel, indem du auswählst, welche Markierungen sie vornehmen soll und welche Kriterien alle erfüllt sein müssen, damit sie angewendet wird.',
'watchlist++-new-rule-desc': 'Beschreibung: ',
'watchlist++-new-rule-mark': '(Markierung)',
'watchlist++-new-rule-read': '(gelesen/ungelesen)',
'watchlist++-new-rule-type': '(neues Kriterium)',
'watchlist++-rule-mark-1': 'rot',
'watchlist++-rule-mark-2': 'orange',
'watchlist++-rule-mark-3': 'gelb',
'watchlist++-rule-mark-4': 'grün',
'watchlist++-rule-mark-5': 'blau',
'watchlist++-rule-mark-6': 'rosa',
'watchlist++-rule-mark-7': 'violett',
'watchlist++-new-rule-help': 'Hilfe',
'watchlist++-new-rule-save': 'Speichern',
'watchlist++-new-rule-cancel': 'Abbrechen',
'watchlist++-new-rule-help-done': 'Zurück',
'watchlist++-new-rule-error-no-action': 'Fehlende Aktion für Regel!',
'watchlist++-new-rule-error-no-filter': 'Fehlende Filterkriterien für Regel!',
'watchlist++-new-rule-error-no-number': 'Keine Zahl bei Bit-Vergleich!',
'watchlist++-new-rule-error-no-re': 'Ungültiger regulärer Ausdruck!',
'watchlist++-new-rule-help-text': '<ul><li>Namensraum: numerischer Wert ($1)</li><li>Titel: Seitentitel (ohne Namensraum)</li><li>Benutzer: Benutzername</li><li>Größenänderung: Zahl, eventuell negativ</li><li>Flags: numerischer Wert aus folgenden Konstanten: $2</li><li>Zeitstempel: im Format yyyy-mm-ddThh:mm:ssZ</li><li>Bearbeitungskommentar: als Quelltext</li><li>Logaktion: interne Bezeichnung</li><li>Bearbeitungsmarkierung: interne Bezeichnungen, getrennt durch senkrechte Striche (<code>|</code>)</li></ul>'
},
'de-ch': {
'watchlist++-rules-delete-confirm': 'Soll die Regel «$1» wirklich gelöscht werden?',
'watchlist++-filter-diff': 'Die Grössenänderung',
'watchlist++-type-gt': 'ist grösser als',
'watchlist++-type-ge': 'ist grösser oder gleich',
'watchlist++-new-rule-help-text': '<ul><li>Namensraum: numerischer Wert ($1)</li><li>Titel: Seitentitel (ohne Namensraum)</li><li>Benutzer: Benutzername</li><li>Grössenänderung: Zahl, eventuell negativ</li><li>Flags: numerischer Wert aus folgenden Konstanten: $2</li><li>Zeitstempel: im Format yyyy-mm-ddThh:mm:ssZ</li><li>Bearbeitungskommentar: als Quelltext</li><li>Logaktion: interne Bezeichnung</li><li>Bearbeitungsmarkierung: interne Bezeichnungen, getrennt durch senkrechte Striche (<code>|</code>)</li></ul>'
},
'de-formal': {
'watchlist++-rules-bookmarklet-title': 'Installieren Sie diesen Link als Bookmarklet um eine Sicherungskopie Ihrer aktuellen Regeln zu sichern',
'watchlist++-export-import-rules': 'Folgende Daten im JSON-Format repräsentieren die aktuellen Markierungsregeln. Sie können sie nach Belieben ändern. Beachten Sie, dass Änderungen erst beim nächsten Mal wirksam werden.',
'watchlist++-new-rule-text': 'Erstellen Sie eine neue Filterregel, indem Sie auswählen, welche Markierungen sie vornehmen soll und welche Kriterien alle erfüllt sein müssen, damit sie angewendet wird.',
'watchlist++-edit-rule-text': 'Bearbeiten Sie die Filterregel, indem Sie auswählen, welche Markierungen sie vornehmen soll und welche Kriterien alle erfüllt sein müssen, damit sie angewendet wird.'
}
};
//jscs:enable maximumLineLength
FLAGS = {
NEW: 1,
MINOR: 2,
BOT: 4,
ANON: 8,
LOG: 16,
WIKIDATA: 32,
CATEGORY: 64
};
function initL10N (l10n, keep) {
var i, chain = mw.language.getFallbackLanguageChain();
keep = $.grep(mw.messages.get(keep), function (val) {
return val !== null;
});
for (i = chain.length - 1; i >= 0; i--) {
if (chain[i] in l10n) {
mw.messages.set(l10n[chain[i]]);
}
}
mw.messages.set(keep);
}
function getTitleFromPagename (page) {
var pos = page.indexOf(':');
if (pos > -1) {
if (hasOwn.call(mw.config.get('wgNamespaceIds'), page.slice(0, pos).replace(/ /g, '_').toLowerCase())) {
return page.slice(pos + 1);
}
}
return page;
}
function fixWikidataComment (comment) {
//Wikidata provides completely broken comments,
//and developers don't seem to care to fix it.
//So we try to fix at least some of the worst things.
return comment
.replace(/<a href="\/wiki\/[^#]+#wb[^"]+" title="[^"]+">[^<]+<\/a>/g, '') //broken autosummary, remove
.replace(
//actually, *all* links use the wrong base,
//but for known namespaces even these are broken, too, so it's not easy to fix them
/<a href="\/w\/index.php\?title=(Property:P\d+|Q\d+)&action=edit&redlink=1" class="new" title="[^"]+">/g,
'<a class="external" href="//www.wikidata.org/wiki/$1">'
)
.replace(/>wb([a-z\-]+):([^:]+):/g, function (all, one, two) {
//tons of possible messages, so just use the codes
return '>' + one + ' (' + two.replace(/\|+/g, mw.msg('comma-separator')) + '):';
});
}
function getCSS1 () {
var css = '', i, markers = [
'',
'#d33', //red
'#ff6d22', //orange (from RC filters)
'#fc3', //yellow
'#00af89', //green
'#36c', //blue
'#e6d', //pink (not in any palette)
'#a033c0' //violet (not in any palette)
];
css += '.td-marker { font-weight: bold; font-size: 200%; }';
css += '.td-marker > * { visibility: hidden; }';
for (i = 1; i < markers.length; i++) {
css += '.mark-' + i + ' .td-marker > * { visibility: visible; color: ' + markers[i] + '; }';
}
css += '.changes-block.collapsed .td-collapse span::before { content: "\u25B6"; color: #36c; cursor: pointer; }';
css += '.changes-block.expanded .td-collapse span::before { content: "\u25BC"; color: #36c; cursor: pointer; }';
css += '.changes-single .td-collapse span::before { content: "\u25B7"; color: #a2a9b1; }';
css += '#changes-table .changes-line.collapsed { display:none; }';
css += '.changes-line .td-title > * { visibility: hidden; }';
css += '.changes-line .td-title.title-change > * { visibility: visible; font-style: italic; }';
css += '.td-flags {font-family: monospace, monospace; }';
css += '.td-flags abbr { font-weight: bold; }';
css += '.change-read .td-read .read { display: none; }';
css += '.change-unread .td-read .unread { display: none; }';
css += '#changes-table { line-height: 1.5em; }';
css += '#changes-table table { border-spacing: 0; }';
css += '#changes-table td { white-space: nowrap; padding: 1px 3px; }';
css += '.td-title, .td-user, .td-comment { overflow: hidden; text-overflow: ellipsis; }';
css += '.td-title:hover, .td-user:hover, .td-comment:hover {' +
//background-color: #fff; wird ohnehin von übernächster Anweisung überschrieben
'white-space: normal !important; word-wrap: break-word; overflow: visible; position: relative; z-index: 1; }';
css += '.td-timestamp, .td-diff { text-align: right; }';
css += '#changes-table tr:hover { background-color: #eaf3ff; }';
css += '#rules-head { padding: 0 0.5em; }';
css += '.collapsed ol { display: none; }';
css += '.schnark-rule-widget { border-collapse: collapse; margin-top: 1em; }';
css += '.schnark-rule-widget td { padding-left: 0; padding-right: 0.5em; }';
return css;
}
function getCSS2 () {
var css = '';
css += '.change-unread .td-change, .change-unread .td-user { font-weight: bold; }';
css += '#changes-table .td-change-read { font-weight: normal; display: inline !important; }';
//inline um in fixWidth() CSS3 zu überschreiben
return css;
}
function getCSS3 () {
return '#changes-table .change-read, #changes-table .td-change-read { display: none; }';
}
function getCSS4 () {
return ' ' + //Leerzeichen, um jscs nicht zu verwirren und Zeilenumbruch zu ermöglichen
'#changes-table table {' +
'display: grid;' +
'grid-template-columns:' +
'max-content ' + //marker
'max-content ' + //collapse
'minmax(15em,1.5fr) ' + //title
'max-content ' + //change
'minmax(10em,1fr) ' + //user
'max-content ' + //flags
'max-content ' + //diff
'minmax(5em,1fr) ' + //comment
'max-content ' + //timestamp
'max-content;' + //read
'width: 100%;' +
'}' +
//Subgrid wäre logischer, bietet aber letztlich keine echten Vorteile.
//Nötig dazu wäre:
//* obige Regel von table zu tbody verschieben
//* tr { display: grid; grid-template-columns: subgrid; grid-column: 1/11; }
'#changes-table thead,' +
'#changes-table tbody,' +
'#changes-table tfoot,' +
'#changes-table tr {' +
'display: contents;' +
'}' +
'#changes-table th,' +
'#changes-table td {' +
'display: block;' +
'width: 100%;' +
'box-sizing: border-box;' +
'}' +
//oben ist die Farbe am <tr>, aber dort geht sie wegen display: contents verloren
'#changes-table tr:hover td {' +
'background-color: #eaf3ff;' +
'}';
}
//rules & filters
function getDefaultRules () {
return [{
desc: mw.msg('watchlist++-rules-desc-mark-own-pages'),
filter: [{
key: 'user',
type: 'is',
val: mw.config.get('wgUserName'),
not: ''
}, {
key: 'title',
type: 'match',
val: '^' + mw.util.escapeRegExp(mw.config.get('wgUserName')) + '($|/)'
}, {
key: 'ns',
type: 'match',
val: '^2|3$'
}],
mark: 2
}, {
desc: mw.msg('watchlist++-rules-desc-hide-bots'),
filter: [{
key: 'flags',
type: 'and',
val: String(FLAGS.BOT)
}],
read: true
}];
}
function getNotificationBugfixData () {
var data = mw.storage.get('schnark-watchlist++-nbd');
if (data) {
data = JSON.parse(data);
} else {
data = {};
}
return data;
}
function setNotificationBugfixData (data) {
mw.storage.set('schnark-watchlist++-nbd', JSON.stringify(data));
}
function getAllRules () {
var rules = mw.storage.get('schnark-watchlist++-rules');
if (rules) {
rules = JSON.parse(rules);
} else {
rules = getDefaultRules();
}
return rules;
}
function setRules (rules) {
if (!mw.storage.set('schnark-watchlist++-rules', JSON.stringify(rules))) {
window.alert(mw.msg('watchlist++-storage-error'));
}
}
function deleteRules () {
if (!mw.storage.remove('schnark-watchlist++-rules')) {
window.alert(mw.msg('watchlist++-storage-error'));
}
}
function exportImportRules () {
var rules = getAllRules();
rules = JSON.stringify(rules);
rules = window.prompt(mw.msg('watchlist++-export-import-rules'), rules);
if (rules !== null) {
try {
rules = JSON.parse(rules);
} catch (e) {
window.alert(mw.msg('watchlist++-json-error', String(e)));
return;
}
setRules(rules);
return true;
}
}
function addRuleDialog (onUpdate, ruleId) {
var windowManager, ruleDialog;
function getHelpContent () {
var i, ns = [], flags = [];
for (i in mw.config.get('wgFormattedNamespaces')) {
if (!isNaN(i) && i > 0) {
ns.push(mw.config.get('wgFormattedNamespaces')[i] + mw.msg('colon-separator') + i);
}
}
for (i in FLAGS) {
if (hasOwn.call(FLAGS, i)) {
flags.push(FLAGS[i] + mw.msg('colon-separator') + mw.msg('watchlist++-flags-' + i.toLowerCase() + '-title'));
}
}
return mw.msg('watchlist++-new-rule-help-text',
ns.join(mw.msg('comma-separator')), flags.join(mw.msg('comma-separator')));
}
function RuleLineWidget (config) {
RuleLineWidget.parent.call(this, {$element: $('<tr>')});
this.keyInput = new OO.ui.DropdownWidget({
$overlay: config.$overlay,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: '',
label: mw.msg('watchlist++-new-rule-type')
}),
new OO.ui.MenuOptionWidget({
data: 'ns',
label: mw.msg('watchlist++-filter-ns')
}),
new OO.ui.MenuOptionWidget({
data: 'title',
label: mw.msg('watchlist++-filter-title')
}),
new OO.ui.MenuOptionWidget({
data: 'user',
label: mw.msg('watchlist++-filter-user')
}),
new OO.ui.MenuOptionWidget({
data: 'diff',
label: mw.msg('watchlist++-filter-diff')
}),
new OO.ui.MenuOptionWidget({
data: 'flags',
label: mw.msg('watchlist++-filter-flags')
}),
new OO.ui.MenuOptionWidget({
data: 'timestamp',
label: mw.msg('watchlist++-filter-timestamp')
}),
new OO.ui.MenuOptionWidget({
data: 'comment',
label: mw.msg('watchlist++-filter-comment')
}),
new OO.ui.MenuOptionWidget({
data: 'logtype',
label: mw.msg('watchlist++-filter-logtype')
}),
new OO.ui.MenuOptionWidget({
data: 'tags',
label: mw.msg('watchlist++-filter-tags')
})
]
}
});
this.keyInput.getMenu().selectItemByData(config.data ? config.data[0] : '');
this.typeInput = new OO.ui.DropdownWidget({
$overlay: config.$overlay,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: 'is',
label: mw.msg('watchlist++-type-is')
}),
new OO.ui.MenuOptionWidget({
data: 'is-not',
label: mw.msg('watchlist++-type-is-not')
}),
new OO.ui.MenuOptionWidget({
data: 'match',
label: mw.msg('watchlist++-type-match')
}),
new OO.ui.MenuOptionWidget({
data: 'match-not',
label: mw.msg('watchlist++-type-match-not')
}),
new OO.ui.MenuOptionWidget({
data: 'and',
label: mw.msg('watchlist++-type-and')
}),
new OO.ui.MenuOptionWidget({
data: 'and-not',
label: mw.msg('watchlist++-type-and-not')
}),
new OO.ui.MenuOptionWidget({
data: 'or',
label: mw.msg('watchlist++-type-or')
}),
new OO.ui.MenuOptionWidget({
data: 'or-not',
label: mw.msg('watchlist++-type-or-not')
}),
new OO.ui.MenuOptionWidget({
data: 'gt',
label: mw.msg('watchlist++-type-gt')
}),
new OO.ui.MenuOptionWidget({
data: 'gt-not',
label: mw.msg('watchlist++-type-gt-not')
}),
new OO.ui.MenuOptionWidget({
data: 'ge',
label: mw.msg('watchlist++-type-ge')
}),
new OO.ui.MenuOptionWidget({
data: 'ge-not',
label: mw.msg('watchlist++-type-ge-not')
})
]
}
});
this.typeInput.getMenu().selectItemByData(config.data ? config.data[1] : 'is');
this.valInput = new OO.ui.TextInputWidget({
value: config.data ? config.data[2] : ''
});
if (!config.data) {
this.keyInput.getMenu().once('select', function () {
this.typeInput.toggle(true);
this.valInput.toggle(true);
this.emit('activate');
}.bind(this));
this.typeInput.toggle(false);
this.valInput.toggle(false);
}
this.keyInput.getMenu().connect(this, {
select: this.emit.bind(this, 'change')
});
this.typeInput.getMenu().connect(this, {
select: this.emit.bind(this, 'change')
});
this.valInput.connect(this, {
change: this.emit.bind(this, 'change'),
enter: this.emit.bind(this, 'enter')
});
this.keyInput.$element.css('width', '20em');
this.typeInput.$element.css('width', '20em');
this.$element.append([
$('<td>').append(this.keyInput.$element),
$('<td>').append(this.typeInput.$element),
$('<td>').append(this.valInput.$element)
]);
}
OO.inheritClass(RuleLineWidget, OO.ui.Widget);
RuleLineWidget.prototype.getValue = function () {
var key = this.keyInput.getMenu().findSelectedItem().getData();
return key ? [
key,
this.typeInput.getMenu().findSelectedItem().getData(),
this.valInput.getValue()
] : false;
};
function RuleWidget (config) {
var i;
RuleWidget.parent.call(this, {$element: $('<table>').addClass('schnark-rule-widget')});
this.$overlay = config.$overlay;
this.lines = [];
if (config.data) {
for (i = 0; i < config.data.length; i++) {
this.addLine(config.data[i]);
}
}
this.addLine();
}
OO.inheritClass(RuleWidget, OO.ui.Widget);
RuleWidget.prototype.addLine = function (data) {
var line = new RuleLineWidget({$overlay: this.$overlay, data: data});
this.lines.push(line);
this.$element.append(line.$element);
line.on('activate', this.onLineActivate.bind(this));
};
RuleWidget.prototype.onLineActivate = function () {
this.addLine();
this.emit('sizechange');
};
RuleWidget.prototype.getValue = function () {
return this.lines.map(function (line) {
return line.getValue();
}).filter(function (val) {
return !!val;
});
};
function RuleDialog (config) {
RuleDialog.parent.call(this, config);
this.onUpdate = config.onUpdate;
this.ruleId = config.ruleId;
}
OO.inheritClass(RuleDialog, OO.ui.ProcessDialog);
RuleDialog.static.name = 'schnark-watchlist';
RuleDialog.static.size = 'larger';
RuleDialog.static.actions = [
{
action: 'done', modes: 'edit',
label: mw.msg('watchlist++-new-rule-save'),
flags: ['primary', 'progressive']
}, {
action: 'help', modes: 'edit',
label: mw.msg('watchlist++-new-rule-help')
}, {
modes: 'edit',
label: mw.msg('watchlist++-new-rule-cancel'),
flags: ['safe', 'close']
}, {
action: 'back', modes: 'help',
label: mw.msg('watchlist++-new-rule-help-done'),
flags: ['safe', 'back']
}
];
RuleDialog.prototype.initialize = function () {
RuleDialog.parent.prototype.initialize.apply(this, arguments);
this.panel1 = new OO.ui.PanelLayout({$: this.$, padded: true, expanded: false});
this.createForm(this.ruleId !== undefined ? getAllRules()[this.ruleId] : false);
this.panel2 = new OO.ui.PanelLayout({$: this.$, padded: true, expanded: false});
this.panel2.$element.append(getHelpContent());
this.stackLayout = new OO.ui.StackLayout({items: [this.panel1, this.panel2]});
this.$body.append(this.stackLayout.$element);
};
RuleDialog.prototype.getSetupProcess = function () {
return RuleDialog.parent.prototype.getSetupProcess.apply(this, arguments)
.next(function () {
this.actions.setMode('edit');
}, this);
};
RuleDialog.prototype.getActionProcess = function (action) {
var error;
if (action === 'help') {
return new OO.ui.Process(function () {
this.actions.setMode('help');
this.stackLayout.setItem(this.panel2);
this.updateSize();
}, this);
} else if (action === 'back') {
return new OO.ui.Process(function () {
this.actions.setMode('edit');
this.stackLayout.setItem(this.panel1);
this.updateSize();
}, this);
} else if (action === 'done') {
error = this.saveRuleOrReturnError();
if (error) {
return new OO.ui.Process(function () {
return new OO.ui.Error(mw.msg('watchlist++-new-rule-error-' + error), {recoverable: false});
});
} else {
return new OO.ui.Process(function () {
this.close();
}, this);
}
}
return RuleDialog.parent.prototype.getActionProcess.apply(this, arguments);
};
RuleDialog.prototype.onDismissErrorButtonClick = function () {
this.actions.setAbilities({done: true}); //well, kind of semi-recoverable
return RuleDialog.parent.prototype.onDismissErrorButtonClick.apply(this, arguments);
};
RuleDialog.prototype.getBodyHeight = function () {
return this.stackLayout.getCurrentItem().$element.outerHeight(true);
};
RuleDialog.prototype.createForm = function (rule) {
var $element;
$element = (new OO.ui.LabelWidget({
label: mw.msg(rule ? 'watchlist++-edit-rule-text' : 'watchlist++-new-rule-text')
})).$element;
$element.css('margin-bottom', '1em');
this.panel1.$element.append($element);
this.descInput = new OO.ui.TextInputWidget({value: rule ? rule.desc : ''});
this.markInput = new OO.ui.DropdownWidget({
$overlay: this.$overlay,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: '',
label: mw.msg('watchlist++-new-rule-mark')
}),
new OO.ui.MenuOptionWidget({
data: '1',
label: mw.msg('watchlist++-rule-mark-1')
}),
new OO.ui.MenuOptionWidget({
data: '2',
label: mw.msg('watchlist++-rule-mark-2')
}),
new OO.ui.MenuOptionWidget({
data: '3',
label: mw.msg('watchlist++-rule-mark-3')
}),
new OO.ui.MenuOptionWidget({
data: '4',
label: mw.msg('watchlist++-rule-mark-4')
}),
new OO.ui.MenuOptionWidget({
data: '5',
label: mw.msg('watchlist++-rule-mark-5')
}),
new OO.ui.MenuOptionWidget({ //out of order for historical reasons
data: '7',
label: mw.msg('watchlist++-rule-mark-7')
}),
new OO.ui.MenuOptionWidget({
data: '6',
label: mw.msg('watchlist++-rule-mark-6')
})
]
}
});
this.markInput.getMenu().selectItemByData(rule && 'mark' in rule ? String(rule.mark) : '');
this.readInput = new OO.ui.DropdownWidget({
$overlay: this.$overlay,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: '',
label: mw.msg('watchlist++-new-rule-read')
}),
new OO.ui.MenuOptionWidget({
data: '0',
label: mw.msg('watchlist++-rules-unread')
}),
new OO.ui.MenuOptionWidget({
data: '1',
label: mw.msg('watchlist++-rules-read')
})
]
}
});
this.readInput.getMenu().selectItemByData(rule && 'read' in rule ? (rule.read ? '1' : '0') : '');
this.filterInput = new RuleWidget({
$overlay: this.$overlay,
data: rule ? rule.filter.map(function (filter) {
return [filter.key, filter.type + ('not' in filter ? '-not' : ''), filter.val];
}) : false
});
this.filterInput.connect(this, {sizechange: 'updateSize'});
this.markInput.$element.css('width', '10em');
this.readInput.$element.css('width', '20em');
$element = (new OO.ui.HorizontalLayout({items: [
this.markInput,
this.readInput
]})).$element;
$element.css('margin', '0.5em 0');
this.panel1.$element.append([
(new OO.ui.FieldLayout(this.descInput, {
label: mw.msg('watchlist++-new-rule-desc'),
align: 'top'
})).$element,
$element,
this.filterInput.$element
]);
};
RuleDialog.prototype.saveRuleOrReturnError = function () {
var desc, mark, read, filters, rule, rules, i;
desc = this.descInput.getValue() || '';
mark = this.markInput.getMenu().findSelectedItem().getData();
read = this.readInput.getMenu().findSelectedItem().getData();
filters = this.filterInput.getValue().map(function (data) {
var filter = {key: data[0], type: data[1].replace(/-not$/, ''), val: data[2]};
if (/-not$/.test(data[1])) {
filter.not = '';
}
return filter;
});
rule = {desc: desc, filter: filters};
if (mark) {
rule.mark = Number(mark);
}
if (read) {
rule.read = (read === '1');
}
if (!mark && !read) {
return 'no-action';
}
if (filters.length === 0) {
return 'no-filter';
}
for (i = 0; i < filters.length; i++) {
switch (filters[i].type) {
case 'and':
case 'or':
if (isNaN(filters[i].val)) {
return 'no-number';
}
break;
case 'match':
try {
/*jshint nonew: false*/
new RegExp(filters[i].val);
} catch (e) {
return 'no-re';
}
}
}
rules = getAllRules();
if (this.ruleId !== undefined) {
rules[this.ruleId] = rule;
} else {
rules.push(rule);
}
setRules(rules);
if (this.onUpdate) {
this.onUpdate();
}
};
windowManager = new OO.ui.WindowManager();
$('body').append(windowManager.$element);
ruleDialog = new RuleDialog({
onUpdate: onUpdate,
ruleId: ruleId
});
windowManager.addWindows([ruleDialog]);
windowManager.openWindow(ruleDialog, {
title: mw.msg(ruleId === undefined ? 'watchlist++-new-rule-title' : 'watchlist++-edit-rule-title')
});
}
function testFilter (filter, data) {
/*jshint bitwise: false*/
var v = data[filter.key], ret;
switch (filter.type) {
case 'is':
ret = String(v) === filter.val;
break;
case 'match':
try {
ret = (new RegExp(filter.val)).test(String(v));
} catch (e) {
ret = false;
}
break;
case 'and':
ret = ((Number(v) & Number(filter.val)) === Number(filter.val));
break;
case 'or':
ret = ((Number(v) | Number(filter.val)) === Number(filter.val));
break;
case 'gt':
ret = (v > filter.val);
break;
case 'ge':
ret = (v >= filter.val);
break;
}
if ('not' in filter) {
ret = !ret;
}
return ret;
}
function testFilters (filters, data) {
var i;
for (i = 0; i < filters.length; i++) {
if (!testFilter(filters[i], data)) {
return false;
}
}
return true;
}
function applyRules (rules, data) {
var i, read, mark, desc;
for (i = 0; i < rules.length; i++) {
if (testFilters(rules[i].filter, data)) {
if ('read' in rules[i]) {
if (read === undefined || read === true) {
read = rules[i].read;
}
}
if ('mark' in rules[i]) {
if (mark === undefined || mark > rules[i].mark) {
mark = rules[i].mark;
desc = rules[i].desc;
}
}
}
}
return {read: read, mark: mark, desc: desc};
}
function Watchlist () {
this.changes = [];
this.pages = [];
this.uuids = 1;
this.queryContinue = {
older: false,
newer: false
};
}
Watchlist.compare = function (a, b) {
var aa = a.getPrimarySortkey(), bb = b.getPrimarySortkey();
if (aa < bb) {
return 1;
}
if (aa > bb) {
return -1;
}
aa = a.getSecondarySortkey();
bb = b.getSecondarySortkey();
if (aa < bb) {
return 1;
}
if (aa > bb) {
return -1;
}
return 0;
};
Watchlist.prototype.getUUID = function (type) {
return (this.uuids++) * (type === 'page' ? -1 : 1);
};
Watchlist.prototype.getFromUUID = function (uuid) {
var i, search = uuid < 0 ? this.pages : this.changes;
for (i = 0; i < search.length; i++) {
if (search[i].uuid === uuid) {
return search[i];
}
}
};
Watchlist.prototype.canGetOlder = function () {
return this.queryContinue.older !== true;
};
Watchlist.prototype.canResetAll = function () {
return this.changes.some(function (change) {
return !change.read;
});
};
Watchlist.prototype.getApiQuery = function (cont) {
var data = {
action: 'query',
list: 'watchlist',
wlallrev: true,
wllimit: 500,
wlprop: 'ids|title|flags|user|userid|comment|parsedcomment|timestamp|sizes|notificationtimestamp|loginfo|tags',
wltype: 'edit|new|log|external|categorize',
format: 'json',
formatversion: 2
};
if (cont && this.queryContinue[cont]) {
if (cont === 'older' && this.queryContinue.older === true) {
return false;
}
$.extend(data, this.queryContinue[cont]);
}
return data;
};
Watchlist.prototype.updateContinue = function (json, cont) {
var d;
if (cont !== 'newer') {
if (json['continue']) {
this.queryContinue.older = json['continue'];
} else {
this.queryContinue.older = true;
}
}
if (cont !== 'older') {
if (json.query && json.query.watchlist && json.query.watchlist[0]) {
d = new Date(json.query.watchlist[0].timestamp);
if (!isNaN(d.valueOf())) {
d.setTime(d.getTime() + 1000);
this.queryContinue.newer = {wlend: d.toISOString().replace(/\.0*Z$/, 'Z')};
}
}
}
};
Watchlist.prototype.runApi = function (callback, cont) {
var data = this.getApiQuery(cont);
if (!data) {
callback(0);
return;
}
$.getJSON(mw.util.wikiScript('api'), data).then(function (json) {
if (json) {
this.updateContinue(json, cont);
}
if (json && json.query && json.query.watchlist && json.query.watchlist.length) {
this.fromApi(json.query.watchlist);
callback(json.query.watchlist.length);
} else {
callback(0);
}
}.bind(this));
};
Watchlist.prototype.fromApi = function (data) {
var i, change;
for (i = 0; i < data.length; i++) {
change = (new Change(this.getUUID('change'))).fromApi(data[i]);
this.changes.push(change);
if (change.isMoveCreate()) {
change = (new Change(this.getUUID('change'))).fromMove(change);
this.changes.push(change);
}
}
this.changes.sort(Watchlist.compare);
this.groupChanges();
this.applyRules();
};
Watchlist.prototype.groupChanges = function () {
var i, name, oldName, groups = {}, moves = {};
for (i = 0; i < this.changes.length; i++) {
name = this.changes[i].getPagename();
if (hasOwn.call(moves, name)) {
name = moves[name];
}
if (!hasOwn.call(groups, name)) {
groups[name] = [];
}
groups[name].push(this.changes[i]);
oldName = this.changes[i].getOldPagename();
if (oldName) {
moves[oldName] = name;
}
}
this.pages = [];
for (name in groups) {
if (hasOwn.call(groups, name)) {
this.pages.push(new Page(groups[name], this));
}
}
this.pages.sort(Watchlist.compare);
};
Watchlist.prototype.render = function () {
var i, html = [];
for (i = 0; i < this.pages.length; i++) {
html.push(this.pages[i].render());
}
return '<table>' + html.join('') + '</table>';
};
Watchlist.prototype.applyRules = function () {
var i, rules = getAllRules();
for (i = 0; i < this.pages.length; i++) {
this.pages[i].applyRules(rules);
}
};
Watchlist.prototype.markAllAsRead = function () {
var i;
for (i = 0; i < this.pages.length; i++) {
this.pages[i].markAllAsRead();
}
this.resetNotificationtimestamps();
};
Watchlist.prototype.getEmptyTsForPage = function (page) {
//HACK verhält sich beinahe so wie '' als notificationtimestamp
return page.changes[0].timestamp.replace(/Z$/, 'z');
};
Watchlist.prototype.resetNotificationtimestamps = function () {
$.post(mw.util.wikiScript('api'), {
action: 'setnotificationtimestamp',
entirewatchlist: '',
token: mw.user.tokens.get('csrfToken'),
format: 'json',
formatversion: 2
});
var nbData = {}, i, page;
for (i = 0; i < this.pages.length; i++) {
page = this.pages[i];
if (!page.changes[0].canTrustNotification()) {
nbData[page.getPagename()] = this.getEmptyTsForPage(page);
}
}
setNotificationBugfixData(nbData);
};
Watchlist.prototype.resetNotificationtimestamp = function (title, timestamp) {
var data = {
action: 'setnotificationtimestamp',
titles: title,
token: mw.user.tokens.get('csrfToken'),
format: 'json',
formatversion: 2
};
if (timestamp) {
data.timestamp = timestamp;
}
$.post(mw.util.wikiScript('api'), data);
};
Watchlist.prototype.getBugfixNotification = function (pagename) {
return getNotificationBugfixData()[pagename];
};
Watchlist.prototype.setBugfixNotification = function (pagename, ts) {
var nbData = getNotificationBugfixData();
if (ts) {
nbData[pagename] = ts;
} else {
delete nbData[pagename];
}
setNotificationBugfixData(nbData);
};
function Page (changes, watchlist) {
var i;
this.changes = changes;
this.uuid = watchlist.getUUID('page');
this.watchlist = watchlist;
for (i = 0; i < this.changes.length; i++) {
this.changes[i].page = this;
}
}
Page.prototype.getPagename = function () {
return this.changes[0].getPagename();
};
Page.prototype.getPrimarySortkey = function () {
return this.changes[0].getPrimarySortkey();
};
Page.prototype.getSecondarySortkey = function () {
return this.changes[0].getSecondarySortkey();
};
Page.prototype.getRelevantChanges = function () {
var changes = [], i;
if (this.readcount < this.changes.length) {
for (i = 0; i < this.changes.length; i++) {
if (!this.changes[i].read) {
changes.push(this.changes[i]);
}
}
return (new Change(this.uuid)).fromChanges(changes, this.readcount, false);
}
return (new Change(this.uuid)).fromChanges(this.changes, 0, true);
};
Page.prototype.render = function () {
var html = [], i;
if (this.changes.length === 1) {
return this.changes[0].render({type: 'single'});
}
html.push(this.getRelevantChanges().render({type: 'block'}));
for (i = 0; i < this.changes.length; i++) {
html.push(this.changes[i].render({
type: 'line',
titleChange: (i > 0) && (this.changes[i].getPagename() !== this.changes[i - 1].getPagename())
}));
}
return html.join('');
};
Page.prototype.getIndexesByTimestamp = function (ts) {
var i, i0, i1;
if (!ts) {
i0 = -1;
} else {
for (i = this.changes.length - 1; i >= 0; i--) {
if (this.changes[i].timestamp >= ts) {
break;
}
}
i0 = i;
}
for (i = i0 + 1; i < this.changes.length; i++) {
if (this.changes[i].canTrustNotification()) {
break;
}
}
i1 = i;
//i0: Index of oldes definitely unread change (-1 if no such change)
//i1: Index of newes definitely read change (.length if no such change)
return [i0, i1];
};
Page.prototype.getNotification = function () {
var notification = this.changes[0].notification,
indexes = this.getIndexesByTimestamp(notification);
if (indexes[0] + 1 === indexes[1]) {
return notification;
}
return this.watchlist.getBugfixNotification(this.getPagename()) || this.changes[indexes[1] - 1].timestamp;
};
Page.prototype.setNotification = function (ts) {
var indexes = this.getIndexesByTimestamp(ts);
this.watchlist.resetNotificationtimestamp(this.getPagename(), ts);
this.watchlist.setBugfixNotification(this.getPagename(), indexes[0] + 1 !== indexes[1] ?
(ts || this.watchlist.getEmptyTsForPage(this)) : false);
};
Page.prototype.applyRules = function (rules) {
var i, read, notification = this.getNotification();
this.readcount = 0;
for (i = 0; i < this.changes.length; i++) {
read = this.changes[i].applyRules(rules, notification);
if (read) {
this.readcount++;
}
}
};
Page.prototype.markAllAsRead = function () {
var i;
for (i = 0; i < this.changes.length; i++) {
this.changes[i].read = true;
}
};
Page.prototype.markAsReadUntil = function (change) {
var i, ts = '';
if (!change) {
change = this.changes[0];
}
for (i = this.changes.length - 1; i >= 0; i--) {
this.changes[i].markAsRead();
if (this.changes[i] === change) {
break;
}
}
if (i <= 0) {
$('#uuid-' + this.uuid).removeClass('change-unread').addClass('change-read');
} else {
ts = this.changes[i - 1].timestamp;
}
this.setNotification(ts);
};
Page.prototype.markAsUnreadFrom = function (change) {
var i, ts;
if (!change) {
change = this.changes[this.changes.length - 1];
}
for (i = 0; i < this.changes.length; i++) {
this.changes[i].markAsUnread();
if (this.changes[i] === change) {
break;
}
}
$('#uuid-' + this.uuid).removeClass('change-read').addClass('change-unread');
ts = this.changes[i].timestamp;
this.setNotification(ts);
};
function Change (uuid) {
this.uuid = uuid;
}
Change.prototype.fromApi = function (data) {
/*jshint bitwise: false*/
/*jshint camelcase: false*///API liefert old_revid etc.
//jscs:disable requireCamelCaseOrUpperCaseIdentifiers
var flags;
this.ns = data.ns;
this.title = getTitleFromPagename(data.title);
this.pagename = data.title;
switch (data.type) {
case 'edit':
flags = 0;
this.revid = data.revid;
this.oldrevid = data.old_revid;
break;
case 'external':
flags = FLAGS.WIKIDATA;
break;
case 'log':
flags = FLAGS.LOG;
break;
case 'new':
flags = FLAGS.NEW;
break;
case 'categorize':
flags = FLAGS.CATEGORY;
this.revid = data.revid;
this.oldrevid = data.old_revid;
break;
}
this.user = data.user;
if (flags & FLAGS.WIKIDATA) {
if (mw.util.isIPAddress(this.user)) {
flags += FLAGS.ANON;
}
} else {
if (data.anon) {
flags += FLAGS.ANON;
}
}
if (data.minor) {
flags += FLAGS.MINOR;
}
if (data.bot) {
flags += FLAGS.BOT;
}
/*if (data['new']) {
flags += FLAGS.NEW;
}*/
this.timestamp = data.timestamp;
this.flags = flags;
this.comment = data.comment || '';
this.parsedcomment = data.parsedcomment || '';
this.oldsize = data.oldlen || 0;
this.newsize = data.newlen || 0;
this.diff = this.newsize - this.oldsize;
this.logtype = data.logtype || '';
this.logaction = data.logaction || '';
this.logparams = data.logparams || {};
this.tags = data.tags || [];
this.notification = data.notificationtimestamp;
if (data.logparams && data.logtype === 'move') {
this.oldns = this.ns;
this.oldtitle = this.title;
this.oldpagename = this.pagename;
this.ns = data.logparams.target_ns;
this.title = getTitleFromPagename(data.logparams.target_title);
this.pagename = data.logparams.target_title;
}
if (this.flags & FLAGS.WIKIDATA) {
this.parsedcomment = fixWikidataComment(this.parsedcomment);
}
//jscs:enable requireCamelCaseOrUpperCaseIdentifiers
return this;
};
Change.prototype.fromMove = function (change) {
/*jshint bitwise: false*/
this.ns = change.oldns;
this.title = change.oldtitle;
this.pagename = change.oldpagename;
this.user = change.user;
this.timestamp = change.timestamp;
//jscs:disable disallowImplicitTypeConversion, das ist kein ~-1, also darf ich das
this.flags = (change.flags & ~FLAGS.LOG) | FLAGS.NEW;
//jscs:enable disallowImplicitTypeConversion
this.comment = change.comment;
this.parsedcomment = change.parsedcomment;
this.oldsize = 0;
this.newsize = change.pagename.length + 14; //FIXME
this.diff = this.newsize;
this.logtype = '';
this.logaction = '';
this.logparams = {};
this.tags = change.tags;
this.notification = change.notification;
return this;
};
Change.prototype.fromChanges = function (changes, additional, allread) {
/*jshint bitwise: false*/
var users = [], comments = [], isnew = false, flags, tags, mark, desc = [], firstLast;
function getFirstLast (changes) {
var i, lastReal, firstReal;
if (changes.length === 1) {
return [changes[0], changes[0]];
}
for (i = 0; i < changes.length; i++) {
if (changes[i].isRealEdit()) {
lastReal = i;
break;
}
}
if (lastReal === undefined) {
return false;
}
for (i = changes.length - 1; i >= 0; i--) {
if (changes[i].isRealEdit()) {
firstReal = i;
break;
}
}
return [changes[firstReal], changes[lastReal]];
}
changes.forEach(function (change, i) {
if (users.indexOf(change.user) === -1) {
users.push(change.user);
}
if (comments.indexOf(change.parsedcomment) === -1) {
comments.push(change.parsedcomment);
}
if (i === 0) {
flags = change.flags;
tags = change.tags;
} else {
flags = flags & change.flags;
tags = tags.filter(function (tag) {
return change.tags.indexOf(tag) > -1;
});
}
isnew = isnew || !!(change.flags & FLAGS.NEW);
mark = (mark === undefined ?
change.mark :
(change.mark === undefined ? mark : Math.min(mark, change.mark))
);
if (change.desc && desc.indexOf(change.desc) === -1) {
desc.push(change.desc);
}
});
if (isnew) {
flags = flags | FLAGS.NEW;
}
firstLast = getFirstLast(changes);
if (firstLast) {
this.revid = firstLast[1].revid;
this.oldrevid = firstLast[0].oldrevid;
}
this.ns = changes[0].ns;
this.title = changes[0].title;
this.pagename = changes[0].pagename;
this.user = users;
this.timestamp = changes[0].timestamp;
this.flags = flags;
this.comment = comments.length === 1 ? changes[0].comment : '';
this.parsedcomment = comments.length === 1 ? comments[0] : '';
this.newsize = firstLast ? firstLast[1].newsize : 0;
this.oldsize = firstLast ? firstLast[0].oldsize : 0;
this.diff = this.newsize - this.oldsize;
this.logtype = '';
this.logaction = '';
this.logparams = {};
this.tags = tags;
this.count = changes.length;
this.readcount = additional;
this.read = allread;
this.mark = mark;
this.desc = desc.length ? desc.join(mw.msg('comma-separator')) : '';
return this;
};
Change.prototype.isRealEdit = function () {
/*jshint bitwise: false*/
return !(this.flags & (FLAGS.CATEGORY | FLAGS.WIKIDATA | FLAGS.LOG));
};
Change.prototype.canTrustNotification = function () {
/*jshint bitwise: false*/
return !(this.flags & (FLAGS.CATEGORY | FLAGS.WIKIDATA));
};
Change.prototype.getOldPagename = function () {
return this.oldpagename || false;
};
Change.prototype.isMoveCreate = function () {
return this.getOldPagename() && this.logparams.suppressredirect !== '';
};
Change.prototype.getPagename = function () {
return this.pagename;
};
Change.prototype.getPrimarySortkey = function () {
return this.timestamp;
};
Change.prototype.getSecondarySortkey = function () {
return this.pagename;
};
Change.prototype.getData = function () {
return {
ns: this.ns,
title: this.title,
user: this.user,
timestamp: this.timestamp,
flags: this.flags,
comment: this.comment,
diff: this.diff,
logtype: this.logtype,
tags: this.tags.join('|')
};
};
Change.prototype.renderTags = function () {
var l = this.tags.length;
return l ?
mw.html.element('abbr', {
title: mw.msg('watchlist++-tags-title', l, this.tags.join(mw.msg('comma-separator')))
}, mw.msg('watchlist++-tags-letter')) :
' ';
};
Change.prototype.renderFlag = function (and, msg, empty) {
/*jshint bitwise: false*/
return (this.flags & and) ?
mw.html.element('abbr', {
title: mw.msg('watchlist++-flags-' + msg + '-title')
}, mw.msg('watchlist++-flags-' + msg + '-letter')) :
(empty ? ' ' : false);
};
Change.prototype.renderFlags = function () {
return this.renderTags() +
(
this.renderFlag(FLAGS.CATEGORY, 'category') ||
this.renderFlag(FLAGS.WIKIDATA, 'wikidata') ||
this.renderFlag(FLAGS.LOG, 'log') ||
' '
) +
this.renderFlag(FLAGS.ANON, 'anon', true) +
this.renderFlag(FLAGS.BOT, 'bot', true) +
this.renderFlag(FLAGS.MINOR, 'minor', true) +
this.renderFlag(FLAGS.NEW, 'new', true);
};
Change.prototype.renderEditor = function () {
var editor = this.user, link;
if (Array.isArray(editor)) {
if (editor.length === 1) {
editor = editor[0];
} else {
return mw.html.element('span',
{title: editor.join(mw.msg('comma-separator'))},
mw.msg('watchlist++-number-editors', editor.length));
}
}
link = mw.html.element('a', {href: mw.util.getUrl('Special:Contributions/' + editor),
target: '_blank', rel: 'noopener'}, editor);
return '<span>' + mw.msg('watchlist++-by', link) + '</span>';
};
Change.prototype.renderTitle = function () {
return mw.html.element('a', {href: mw.util.getUrl(this.pagename), target: '_blank', rel: 'noopener'}, this.pagename);
};
Change.prototype.renderChangeUrl = function () {
/*jshint bitwise: false*/
if (this.flags & FLAGS.NEW) {
return false;
}
if (this.flags & FLAGS.LOG) {
return false;
}
if (this.flags & FLAGS.WIKIDATA) {
//TODO Wikidata liefert keinerlei Hinweise über die API um welche Änderung es wirklich geht
return 'https://www.wikidata.org/wiki/Special:ItemByTitle/' + mw.config.get('wgDBname') + '/' +
this.pagename + '?action=history';
}
if (!this.revid) { //Gruppe ohne richtige Bearbeitungen
return false;
}
return mw.util.wikiScript() + '?' + (this.oldrevid > 0 ?
'diff=' + this.revid + '&oldid=' + this.oldrevid : 'diff=prev&oldid=' + this.revid);
};
Change.prototype.renderChangeType = function () {
var key = 'watchlist++-change-' + this.logtype + '-' + this.logaction;
if (mw.messages.exists(key)) {
return mw.msg(key);
}
key = 'watchlist++-change-' + this.logtype;
if (mw.messages.exists(key)) {
return mw.msg(key);
}
if (this.logtype) {
mw.log.warn('No message for ' + this.logtype);
}
return mw.msg('watchlist++-change');
};
Change.prototype.renderChangeTooltip = function () {
if (this.logtype === 'block' && this.logaction !== 'unblock') {
return this.logparams.duration;
}
};
Change.prototype.renderChange = function () {
var href, title, tooltip = false, additional, html;
if (this.count) {
title = mw.msg('watchlist++-changes', this.count);
} else {
title = this.renderChangeType();
tooltip = this.renderChangeTooltip() || false;
}
if (this.readcount) {
additional = mw.html.element('span', {'class': 'td-change-read'},
mw.msg('watchlist++-changes-read', this.readcount));
}
href = this.renderChangeUrl();
if (href) {
html = mw.html.element('a', {href: href, title: tooltip, target: '_blank', rel: 'noopener'}, title);
} else {
html = title;
if (tooltip) {
html = mw.html.element('span', {title: tooltip}, html);
}
}
if (additional) {
html += ' ' + additional;
}
return html;
};
Change.prototype.renderDiff = function () {
if (!this.isRealEdit()) {
return '';
}
var cssClass, sign = '';
if (this.diff < 0) {
cssClass = 'mw-plusminus-neg';
} else if (this.diff > 0) {
sign = '+';
cssClass = 'mw-plusminus-pos';
} else {
cssClass = 'mw-plusminus-null';
}
return mw.html.element('span',
{'class': cssClass, dir: 'ltr', title: mw.msg('rc-change-size-new', mw.language.convertNumber(this.newsize))},
/*mw.msg('rc-change-size',*/ sign + mw.language.convertNumber(this.diff)/*)*/
);
};
Change.prototype.renderTimestamp = function () {
function pad (n) {
return n < 10 ? '0' + String(n) : String(n);
}
var d = new Date(this.timestamp), now = new Date(), timestamp;
if (isNaN(d.valueOf())) {
return '';
}
if (now.getFullYear() === d.getFullYear() && now.getMonth() === d.getMonth() && now.getDate() === d.getDate()) {
timestamp = mw.msg('watchlist++-hhmm', d.getHours(), pad(d.getMinutes()));
} else {
timestamp = mw.msg('watchlist++-ddmm', d.getDate(), d.getMonth() + 1);
}
return mw.html.element('a', {
href: mw.util.getUrl(this.pagename, {action: 'history'}),
target: '_blank', rel: 'noopener',
title: d.toLocaleString()
}, new mw.html.Raw(timestamp));
};
Change.prototype.renderComment = function () {
var additional, comment;
switch (this.logtype + '/' + this.logaction) {
//TODO mehr?
case 'block/block':
case 'block/reblock':
//TODO mehr Informationen
break;
case 'delete/revision':
additional = mw.msg('watchlist++-log-delete-revision');
break;
case 'protect/modify':
case 'protect/protect':
additional = mw.msg('watchlist++-log-protect', this.logparams.description);
break;
case 'rights/rights':
additional = [
this.logparams.oldgroups.filter(function (group) {
return this.logparams.newgroups.indexOf(group) === -1;
}, this).join('comma-separator'),
this.logparams.newgroups.filter(function (group) {
return this.logparams.oldgroups.indexOf(group) === -1;
}, this).join('comma-separator')
];
if (additional[0]) {
additional[0] = mw.msg('watchlist++-log-rights-remove', additional[0]);
}
if (additional[1]) {
additional[1] = mw.msg('watchlist++-log-rights-add', additional[1]);
}
additional = additional.filter(function (msg) {
return !!msg;
}).join('comma-separator');
break;
case 'upload/overwrite':
case 'upload/revert':
additional = mw.msg('watchlist++-log-upload-overwrite');
break;
}
comment = '<span class="comment">' +
this.parsedcomment.replace(/<a /g, '<a target="_blank" rel="noopener" ') +
'</span>';
if (additional) {
return mw.msg('watchlist++-log-comment', additional, comment);
}
return comment;
};
Change.prototype.render = function (options) {
var classes = [];
switch (options.type) {
case 'block':
classes.push('changes-block');
classes.push('collapsed');
break;
case 'line':
classes.push('changes-line');
classes.push('collapsed');
break;
case 'single':
classes.push('changes-single');
}
if (this.read) {
classes.push('change-read');
} else {
classes.push('change-unread');
}
if (this.mark) {
classes.push('mark-' + this.mark);
}
return mw.html.element('tr', {id: 'uuid-' + this.uuid, 'class': classes.join(' ')}, new mw.html.Raw([
mw.html.element('td', {'class': 'td-marker', title: this.desc || ''}, new mw.html.Raw(
mw.html.element('span', {}, mw.msg('watchlist++-mark-symbol')))
),
mw.html.element('td', {'class': 'td-collapse'}, new mw.html.Raw('<span></span>')),
mw.html.element('td', {'class': 'td-title' + (options.titleChange ? ' title-change' : '')},
new mw.html.Raw(this.renderTitle())
),
mw.html.element('td', {'class': 'td-change'}, new mw.html.Raw(this.renderChange())),
mw.html.element('td', {'class': 'td-user'}, new mw.html.Raw(this.renderEditor())),
mw.html.element('td', {'class': 'td-flags'}, new mw.html.Raw(this.renderFlags())),
mw.html.element('td', {'class': 'td-diff'}, new mw.html.Raw(this.renderDiff())),
mw.html.element('td', {'class': 'td-comment'}, new mw.html.Raw(this.renderComment())),
mw.html.element('td', {'class': 'td-timestamp'}, new mw.html.Raw(this.renderTimestamp())),
mw.html.element('td', {'class': 'td-read'}, new mw.html.Raw(
mw.html.element('a', {href: '#',
'class': 'read',
title: mw.msg(options.type === 'line' ? 'watchlist++-read-until' : 'watchlist++-read-all')},
mw.msg('watchlist++-read-symbol')
) +
mw.html.element('a', {href: '#',
'class': 'unread',
title: mw.msg(options.type === 'line' ? 'watchlist++-unread-until' : 'watchlist++-unread-all')},
mw.msg('watchlist++-unread-symbol')
)
))
].join('')));
};
Change.prototype.markAsRead = function () {
this.read = true;
$('#uuid-' + this.uuid).removeClass('change-unread').addClass('change-read');
};
Change.prototype.markAsUnread = function () {
this.read = false;
$('#uuid-' + this.uuid).removeClass('change-read').addClass('change-unread');
};
Change.prototype.markAsReadUntil = function () {
this.page.markAsReadUntil(this);
};
Change.prototype.markAsUnreadFrom = function () {
this.page.markAsUnreadFrom(this);
};
Change.prototype.applyRules = function (rules, notification) {
if (this.rulesApplied) {
return this.read;
}
var ret = applyRules(rules, this.getData());
if (ret.mark !== undefined) {
this.mark = ret.mark;
this.desc = ret.desc;
}
if (!notification || notification > this.timestamp) {
this.read = true;
} else if (ret.read !== undefined) {
this.read = ret.read;
} else {
this.read = false;
}
this.rulesApplied = true;
return this.read;
};
function Table (watchlist) {
this.watchlist = watchlist;
}
Table.prototype.show = function (html) {
this.updateOlderButton();
this.updateResetButton();
if (!html) {
$('#changes-table').show();
return;
}
$('#changes-table').html(html).show();
this.fixWidth();
this.addEventHandlers2();
};
Table.prototype.addEventHandlers1 = function () {
var updateRules = this.showRules.bind(this);
$('#rules-add').on('click', function (e) {
e.preventDefault();
addRuleDialog(updateRules);
});
$('#rules-export-import').on('click', function (e) {
e.preventDefault();
if (exportImportRules()) {
updateRules();
}
});
$('#rules-delete-all').on('click', function (e) {
e.preventDefault();
if (window.confirm(mw.msg('watchlist++-rules-delete-all-confirm'))) {
deleteRules();
updateRules();
}
});
$('#rules-list').on('click', '.rules-edit', function (e) {
e.preventDefault();
addRuleDialog(updateRules, $('#rules-list > li').index($(this).parent('li')));
});
$('#rules-list').on('click', '.rules-delete', function (e) {
var $li, i, rules;
$li = $(this).parent('li');
i = $('#rules-list > li').index($li);
rules = getAllRules();
e.preventDefault();
if (window.confirm(mw.msg('watchlist++-rules-delete-confirm', rules[i].desc))) {
rules.splice(i, 1);
setRules(rules);
updateRules();
}
});
this.headButton.on('click', function () {
if ($('#rules-container.collapsed').length) {
this.headButton.setIcon('collapse');
this.headButton.setTitle(mw.msg('watchlist++-rules-head-collapse'));
} else {
this.headButton.setIcon('expand');
this.headButton.setTitle(mw.msg('watchlist++-rules-head-expand'));
}
$('#rules-container').toggleClass('collapsed expanded');
}.bind(this));
this.toggleReadButton.on('click', function () {
this.s1.disabled = !this.s1.disabled;
this.s2.disabled = !this.s2.disabled;
}.bind(this));
this.addOlderButton.on('click', function () {
this.showWatchlistContinue('older');
}.bind(this));
this.addNewerButton.on('click', function () {
this.showWatchlistContinue('newer');
}.bind(this));
this.resetNotificationButton.on('click', function () {
$('#changes-table tr').removeClass('change-unread').addClass('change-read');
this.watchlist.markAllAsRead();
this.updateResetButton();
}.bind(this));
};
Table.prototype.addEventHandlers2 = function () {
var watchlist = this.watchlist, updateButton;
function read (el) {
watchlist.getFromUUID(
Number($(el).closest('tr').attr('id').replace(/uuid-/, ''))
).markAsReadUntil();
}
function unread (el) {
watchlist.getFromUUID(
Number($(el).closest('tr').attr('id').replace(/uuid-/, ''))
).markAsUnreadFrom();
}
updateButton = this.updateResetButton.bind(this);
$('.td-title a, .td-change a').on('click', function () {
read(this);
updateButton();
});
$('.td-read .read').on('click', function (e) {
read(this);
updateButton();
e.preventDefault();
});
$('.td-read .unread').on('click', function (e) {
unread(this);
updateButton();
e.preventDefault();
});
$('.changes-block .td-collapse').on('click', function () {
$(this).parent('tr').nextUntil('.changes-block, .changes-single').addBack().toggleClass('collapsed expanded');
updateButton();
});
};
Table.prototype.fixWidth = function () {
if (isCompatibleGrid()) {
return;
}
function calculateMaxWidth ($sel) {
var maxWidth = 0;
$sel.each(function () {
var width = $(this).width();
if (width > maxWidth) {
maxWidth = width;
}
});
return maxWidth;
}
var padding = 25, s = mw.util.addCSS('#changes-table tr { display: table-row !important; }' + getCSS2()),
$cols = {
marker: $('.td-marker'),
collapse: $('.td-collapse'),
title: $('.td-title'),
change: $('.td-change'),
user: $('.td-user'),
flags: $('.td-flags'),
diff: $('.td-diff'),
comment: $('.td-comment'),
timestamp: $('.td-timestamp'),
read: $('.td-read')
}, maxWidths = {
marker: calculateMaxWidth($cols.marker),
collapse: calculateMaxWidth($cols.collapse),
title: calculateMaxWidth($cols.title),
change: calculateMaxWidth($cols.change),
user: calculateMaxWidth($cols.user),
flags: calculateMaxWidth($cols.flags),
diff: calculateMaxWidth($cols.diff),
comment: calculateMaxWidth($cols.comment),
timestamp: calculateMaxWidth($cols.timestamp),
read: calculateMaxWidth($cols.read)
}, minWidths = {
marker: maxWidths.marker,
collapse: maxWidths.collapse,
title: Math.min(maxWidths.title, maxWidths.change * 1.75),
change: maxWidths.change,
user: Math.min(maxWidths.user, maxWidths.change * 1.25),
flags: maxWidths.flags,
diff: maxWidths.diff,
comment: Math.min(maxWidths.comment, maxWidths.change * 1.25),
timestamp: maxWidths.timestamp,
read: maxWidths.read
}, availWidth = $('#changes-table').width() - padding, minWidth = 0, maxWidth = 0, i;
$(s.ownerNode).remove();
for (i in $cols) {
if (hasOwn.call($cols, i)) {
minWidth += minWidths[i];
maxWidth += maxWidths[i];
}
}
$('#changes-table table').width(availWidth/* + padding*/);
if (maxWidth <= availWidth) {
minWidths = maxWidths;
} else if (minWidth >= availWidth) {
$('#changes-table table').width(minWidth/* + padding*/);
} else if (maxWidth > minWidth) {
i = (availWidth - minWidth) / (maxWidth - minWidth);
minWidths.title += i * (maxWidths.title - minWidths.title);
minWidths.user += i * (maxWidths.user - minWidths.user);
minWidths.comment += i * (maxWidths.comment - minWidths.comment);
}
for (i in $cols) {
if (hasOwn.call($cols, i)) {
$cols[i].width(minWidths[i]);
}
}
$('#changes-table table').css('table-layout', 'fixed');
};
Table.prototype.showRules = function () {
var rules = getAllRules(), i, bookmarklet = 'javascript', rulesHtml = [];
function formatFilter (filter) {
return mw.msg('watchlist++-filter',
mw.msg('watchlist++-filter-' + filter.key),
mw.msg('watchlist++-type-' + filter.type + ('not' in filter ? '-not' : '')),
filter.val
);
}
function formatRule (rule, link) {
var filters = [], i, html;
for (i = 0; i < rule.filter.length; i++) {
filters.push('<li>' + formatFilter(rule.filter[i]) + '</li>');
}
html = link ? mw.msg('watchlist++-rules-desc', mw.html.escape(rule.desc), link) : mw.html.escape(rule.desc);
html += '<br>';
if ('read' in rule && 'mark' in rule) {
html += mw.msg('watchlist++-rule-read-mark', rule.read ? mw.msg('watchlist++-rules-read') :
mw.msg('watchlist++-rules-unread'), rule.mark);
} else if ('read' in rule) {
html += mw.msg('watchlist++-rule-read', rule.read ? mw.msg('watchlist++-rules-read') :
mw.msg('watchlist++-rules-unread'));
} else {
html += mw.msg('watchlist++-rule-mark', rule.mark);
}
return html + '<ul>' + filters.join('') + '</ul>';
}
this.headButton.setLabel(mw.msg('watchlist++-rules-head', rules.length));
bookmarklet += ':';
bookmarklet += 'mw.libs.restoreWatchlistRules(' + JSON.stringify(rules) + ')';
$('#rules-bookmarklet').attr('href', bookmarklet);
for (i = 0; i < rules.length; i++) {
rulesHtml.push('<li>' + formatRule(
rules[i],
mw.html.element('a', {href: '#', 'class': 'rules-edit'}, mw.msg('watchlist++-rules-edit')) +
mw.msg('pipe-separator') +
mw.html.element('a', {href: '#', 'class': 'rules-delete'}, mw.msg('watchlist++-rules-delete'))
) + '</li>');
}
$('#rules-list').html(rulesHtml.join(''));
};
Table.prototype.buildInterface = function ($container, title) {
var html;
function Spinner () {
OO.ui.Element.apply(this, arguments);
OO.ui.mixin.PendingElement.apply(this, arguments);
}
OO.inheritClass(Spinner, OO.ui.Element);
OO.mixinClass(Spinner, OO.ui.mixin.PendingElement);
Spinner.prototype.pushPending = function () {
OO.ui.mixin.PendingElement.prototype.pushPending.apply(this, arguments);
this.$element.css('height', this.isPending() ? '1em' : '');
};
Spinner.prototype.popPending = function () {
OO.ui.mixin.PendingElement.prototype.popPending.apply(this, arguments);
this.$element.css('height', this.isPending() ? '1em' : '');
};
if (title) {
$('#firstHeading').text(mw.msg('watchlist++'));
}
mw.util.addCSS(getCSS1() + (isCompatibleGrid() ? getCSS4() : ''));
this.s1 = mw.util.addCSS(getCSS2());
this.s2 = mw.util.addCSS(getCSS3());
this.s1.disabled = true;
html = mw.html.element('div', {id: 'rules-container', 'class': 'collapsed'}, new mw.html.Raw(
mw.html.element('h4', {id: 'rules-head'}, '') +
mw.msg('parentheses', [
mw.html.element('a', {href: '#', id: 'rules-add'}, mw.msg('watchlist++-rules-add')),
mw.html.element('a', {href: '#', id: 'rules-export-import'}, mw.msg('watchlist++-rules-export-import')),
mw.html.element('a', {id: 'rules-bookmarklet', title: mw.msg('watchlist++-rules-bookmarklet-title')},
mw.msg('watchlist++-rules-bookmarklet')),
mw.html.element('a', {href: '#', id: 'rules-delete-all'}, mw.msg('watchlist++-rules-delete-all'))
].join(mw.msg('pipe-separator'))) +
'<ol id="rules-list"></ol>'
));
html += mw.html.element('div', {id: 'button-container'}, '');
html += mw.html.element('div', {id: 'spinner-container'}, '');
html += mw.html.element('div', {id: 'changes-table'}, '');
$container.html(html);
this.headButton = new OO.ui.ButtonWidget({
icon: 'expand',
title: mw.msg('watchlist++-rules-head-expand'),
framed: false
});
$('#rules-head').append(this.headButton.$element);
this.toggleReadButton = new OO.ui.ButtonWidget({
label: mw.msg('watchlist++-toggle-read')
});
this.addOlderButton = new OO.ui.ButtonWidget({
label: mw.msg('watchlist++-add-older'),
title: mw.msg('watchlist++-add-older-title')
});
this.addNewerButton = new OO.ui.ButtonWidget({
label: mw.msg('watchlist++-add-newer'),
title: mw.msg('watchlist++-add-newer-title')
});
this.resetNotificationButton = new OO.ui.ButtonWidget({
label: mw.msg('watchlist++-reset-notification')
});
$('#button-container').append(new OO.ui.HorizontalLayout({
items: [
this.toggleReadButton,
new OO.ui.ButtonGroupWidget({
items: [this.addOlderButton, this.addNewerButton]
}),
this.resetNotificationButton
]
}).$element);
this.spinner = new Spinner();
$('#spinner-container').append(this.spinner.$element);
this.showRules();
this.addEventHandlers1();
};
Table.prototype.updateOlderButton = function () {
this.addOlderButton.setDisabled(!this.watchlist.canGetOlder());
};
Table.prototype.updateResetButton = function () {
this.resetNotificationButton.setDisabled(!this.watchlist.canResetAll());
};
Table.prototype.showWatchlistInitial = function () {
this.spinner.pushPending();
this.watchlist.runApi(function () {
$(function () {
this.show(this.watchlist.render());
this.spinner.popPending();
}.bind(this));
}.bind(this));
};
Table.prototype.showWatchlistContinue = function (cont) {
this.spinner.pushPending();
this.watchlist.runApi(function (c) {
if (c) {
this.show(this.watchlist.render());
} else {
this.show();
}
this.spinner.popPending();
}.bind(this), cont);
};
function addWatchlistLink () {
if (!$('.rcfilters-head').length) {
$('.mw-watchlist-toollinks a').last().after(
mw.msg('pipe-separator') +
mw.html.element('a', {href: mw.util.getUrl('Special:Watchlist++')}, mw.msg('watchlist++'))
);
} else {
mw.util.addPortletLink('p-cactions', mw.util.getUrl('Special:Watchlist++'), mw.msg('watchlist++'));
}
}
function buildWrapper () {
var mode = mw.user.options.get('userjs-schnark-watchlistPP-mode'),
$containerClassical, $containerWatchlistPP;
if (['classical', 'watchlist++'].indexOf(mode) === -1) {
mode = 'classical';
}
$containerClassical = $('#mw-content-text');
$containerWatchlistPP = $('<div>');
$containerClassical.after($containerWatchlistPP);
if (mode === 'classical') {
$containerWatchlistPP.addClass('oo-ui-element-hidden');
} else {
$containerClassical.addClass('oo-ui-element-hidden');
}
buildSwitcher({classical: $containerClassical, 'watchlist++': $containerWatchlistPP}, mode);
return $containerWatchlistPP;
}
function buildSwitcher (containers, mode) {
var $currentContainer = containers[mode];
mw.loader.using(['oojs-ui-widgets', 'mediawiki.api']).then(function () {
var switcher;
switcher = new OO.ui.ButtonSelectWidget({
items: [
new OO.ui.ButtonOptionWidget({data: 'classical', label: mw.msg('watchlist++-mode-classical')}),
new OO.ui.ButtonOptionWidget({data: 'watchlist++', label: mw.msg('watchlist++-mode-watchlist++')})
]
});
switcher.selectItemByData(mode);
switcher.on('select', function (item) {
var mode = item.getData();
$currentContainer.addClass('oo-ui-element-hidden');
$currentContainer = containers[mode];
$currentContainer.removeClass('oo-ui-element-hidden');
(new mw.Api()).saveOption('userjs-schnark-watchlistPP-mode', mode);
});
$('.mw-indicators').eq(0).empty().append(switcher.$element);
});
}
function buildWatchlist ($container, title) {
mw.loader.using([
'mediawiki.util', 'mediawiki.storage', 'user.options',
'mediawiki.jqueryMsg', 'mediawiki.special.changeslist',
'oojs-ui.styles.icons-movement', 'oojs-ui-widgets', 'oojs-ui-windows'
]).then(function () {
var watchlist = new Watchlist(), table = new Table(watchlist);
if (title) {
document.title = mw.msg('pagetitle', mw.msg('watchlist++'));
}
table.buildInterface($container, title);
table.showWatchlistInitial();
});
}
function isCompatibleGrid () {
return window.CSS && CSS.supports &&
CSS.supports('display', 'grid') &&
CSS.supports('display', 'contents');
}
if (
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' ||
(mw.config.get('wgNamespaceNumber') === -1 && mw.config.get('wgTitle') === 'Watchlist++')
) {
$.when(
mw.loader.using(['user.options', 'mediawiki.util', 'mediawiki.language', 'oojs-ui-core']),
$.ready
).then(function () {
initL10N(l10n, ['colon-separator', 'comma-separator',
'rc-change-size-new', 'pipe-separator', 'parentheses', 'pagetitle']);
if (mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist') {
if (mw.user.options.get('userjs-schnark-watchlistPP-integrate')) {
buildWatchlist(buildWrapper(), false);
} else {
addWatchlistLink();
}
} else {
buildWatchlist($('#mw-content-text'), true);
}
});
}
mw.libs.restoreWatchlistRules = setRules;
})(jQuery, mediaWiki);
//</nowiki>