Wikipedista:Dvorapa/tools.js
Vzhled
Poznámka: Po uložení musíte vyprázdnit mezipaměť vašeho prohlížeče, jinak změny neuvidíte.
/* <onlyinclude> __NOEDITSECTION__
'''Mikroudělátka''' k vložení do uživatelského skriptu (na stránce [[Speciální:Moje stránka/common.js|common.js]]). Pokud chcete využít některé z následujících mikroudělátek, '''následujte návod [[#Instalace]]'''.
Většina mikroudělátek pochází od [[Wikipedista:Mormegil|Mormegila]] ([[Wikipedista:Mormegil/tools.js.help|zdroj]]), některá bohužel již nejsou funkční, postupně ale budou vylepšována a opravována.
</onlyinclude>
****************************************************************************
****************************************************************************
** **
** Dokumentaci si můžete přečíst na [[Wikipedista:Dvorapa/tools.js/doc]]. **
** **
****************************************************************************
****************************************************************************
<onlyinclude>
== Instalace ==
Pokud chcete jakékoliv z následujících mikroudělátek používat, musíte nejprve na začátek [[Speciální:Moje stránka/common.js|svého souboru ''common.js'']] vložit odkaz na '''tento soubor'''. K tomu slouží následující řádek kódu:
<pre>
importScript("Wikipedista:Dvorapa/tools.js");
</pre>
Dále si tam vložte řádky podle toho, která mikroudělátka chcete použít (viz „Použití“ u každého mikroudělátka):
__TOC__
</onlyinclude><pre> */
// TODO: nahradit následující funkce mw.* funkcemi
// najít element s nadpisem stránky
function findFirstHeading() {
var eContent = document.getElementById('content');
if (eContent === null) return null;
var elems = eContent.getElementsByTagName("h1");
if (elems.length === 0) return null;
return elems[0];
}
// zjistit nadpis stránky
function getPagetitle() {
var eHeading = findFirstHeading();
if (eHeading === null) return "";
var title = eHeading.innerHTML;
// TODO: vyřešit stránky, kde se nečte článek (editace, přesouvání, …)
return title;
}
// získat (enkódované) lokální URL nějaké stránky (obdoba localurl:)
function getLocalURL(text) {
text = text.substring(0, 1).toUpperCase() + text.substring(1);
return "/wiki/" + encodeURIComponent(text.replace(/ /g, "_")).replace(/%2f/gi, "/");
}
// Appends a new tab.
// Převzato z [[commons:MediaWiki:Extra-tabs.js]].
function appendTab(url, name) {
var na = document.createElement('a');
na.setAttribute('href', url);
var txt = document.createTextNode(name);
na.appendChild(txt);
var li = document.createElement('li');
li.appendChild(na);
// Grab the element we want to append the tab and append the tab to it.
var c1 = document.getElementById(mw.config.get('skin') === 'vector' ? 'p-cactions' : 'column-one');
if (!c1) return;
var tabs = c1.getElementsByTagName('div')[0].getElementsByTagName('ul')[0];
tabs.appendChild(li);
}
/* </pre><onlyinclude>
== Tlačítka shrnutí ==
; Funkce
: Pod editační okno přidá panel s tlačítky pro vložení často používaných shrnutí editace (převzato částečně z udělátka na [[:sk:MediaWiki:Gadget-edit-summaries.js|skwiki]] a převážně z udělátka na [[:he:מדיה_ויקי:Gadget-Summarieslist.js|hewiki]].
; Použití
:Základní:
jQuery(function($) { addSummaryToolbar(); });
:Vlastní:
jQuery(function($) {
addSummaryToolbar(
["vlastní", {"title": "ahoj", "content": "Vloží ahoj"}, {"title": "svět", "content": "Vloží svět"},
"další", {"title": "ahoj světe", "content": "Vloží ahoj světe"}],
["diskuse", {"title": "re", "content": "Vloží re"}],
["ahoj", "re"]
);
});
; Parametry
:* První parametr obsahuje nadpisy a seznamy dvojic {"title": "shrnutí", "content": "popisek"} oddělené čárkou.
:* Druhý parametr může být buď prázdný řetězec, nebo nadpisy a seznamy dvojic pro diskusní stránky.
:* Třetí parametr může být buď prázdný řetězec, nebo seznam shrnutí, při jejichž vložení se automaticky zaškrtne tlačítko malé editace. Při vložení jiného než zde uvedeného shrnutí se znovu samo odškrtne.
</onlyinclude><pre> */
function addSummaryToolbar(main, talk, minor) {
var installed = false;
function installSummary($summaryBox, smallArea) {
if (installed) return;
installed = true;
function addSummary() {
var summary = $(this).data('summary');
var text = summary.title;
var sum = $summaryBox,
curr = sum.val();
var comma = curr.length > 0 && curr.charAt(curr.length - 2) != "/";
sum.val(curr + (comma ? ', ' : '') + text).trigger('input');
$("#wpMinoredit").prop('checked', summary.minor || $.inArray(text, minorSummaries) + 1);
}
var summaries = window.summaries || [];
if (main === ("" || undefined)) {
summaries = summaries.concat([
"malé",
{"title": "překlepy", "content": "Oprava překlepů"},
{"title": "odkazy", "content": "Úprava odkazů na jiné články Wikipedie"},
{"title": "kategorie", "content": "Úprava kategorií"},
{"title": "externí odkazy", "content": "Úprava externích odkazů"},
"běžné",
{"title": "pravopis", "content": "Oprava pravopisu"},
{"title": "gramatika", "content": "Oprava gramatiky"},
{"title": "typografie", "content": "Oprava typografie"},
{"title": "formulace", "content": "Úprava formulace"},
{"title": "aktualizace", "content": "Aktualizace údajů"},
{"title": "rozšíření", "content": "Rozšíření článku"},
"technické",
{"title": "obrázek", "content": "Přidání, změna, smazání obrázku" },
{"title": "infobox", "content": "Vložení/úprava infoboxu"},
{"title": "šablona", "content": "Vložení šablony"}
]);
} else {
summaries = summaries.concat(main);
}
if (mw.config.get('wgNamespaceNumber') % 2 == 1 || mw.config.get('wgNamespaceNumber') == 4) {
if (talk === ("" || undefined)) {
summaries = summaries.concat([
"diskuse",
{"title": "odpověď", "content": "Odpověď"},
{"title": "návrh", "content": "Návrh"},
{"title": "dotaz", "content": "Dotaz"}
]);
} else {
summaries = summaries.concat(talk);
}
}
var minorSummaries = [];
if (minor === ("" || undefined)) {
minorSummaries = ["překlepy", "odkazy", "kategorie", "externí odkazy"];
} else {
minorSummaries = minor;
}
var div = $("<div>", {
id: "summariesList"
})
.css({
width: (smallArea ? "100%" : "65%"),
padding: "2px",
fontSize: "85%",
lineHeight: "18px"
});
for (var i = 0; i < summaries.length; i++) {
var summary = summaries[i];
var summaryButton = $('<span>').css({
marginLeft: '0.4em',
whiteSpace: 'nowrap'
}).data({
summary: summary
});
if (typeof summary == "string") {
div.append(smallArea ? '<br>' : ' '); //allow text wrap here
summaryButton.text(summary + ':');
} else {
summaryButton.html(' ' + summary.title + ' ')
.addClass('clickable-edit-summary')
.attr('title', summary.content || '')
.css({
'background-color': '#f9f9f9',
border: 'dotted 1px #708090',
cursor: 'pointer'
})
.click(addSummary);
if (smallArea) div.append(' ');
}
div.append(summaryButton);
}
$summaryBox.after(div);
}
if ($.inArray(mw.config.get('wgAction'), ['edit', 'submit']) + 1) {
installSummary($('.editOptions #wpSummary'), false);
}
mw.hook('ve.saveDialog.stateChanged').add(function() {
var target = ve.init.target;
var $summaryBox = target.saveDialog.$body.find('.ve-ui-mwSaveDialog-summary textarea');
installSummary($summaryBox, true);
});
}
/* </pre><onlyinclude>
== Odkazy na oblíbené stránky ==
; Funkce
: Do horní lišty přidá odkazy na oblíbené stránky.
; Použití
:Základní:
jQuery(function($) { addLinktoolbar([["Portál:Historie", "Portál"], ["Wikipedie:Pod lípou", "Pod lípou"]]); });
:Pokročilé:
jQuery(function($) { addLinktoolbar([["Wikipedie:Žádost o práva správce", "RfA"], ["Wikipedie:Hlasování o smazání", "VfD"]], "Speciální:Poslední změny", "Pracovní", "p-tb", "t-upload"); });
; Parametry
:* První parametr obsahuje seznam dvojic ["odkaz na stránku", "zobrazený text odkazu"] oddělených čárkou. Pokud je místo dvojice prázdná položka "", vloží se na její místo oddělovač.
:* Druhý parametr může být buď prázdný řetězec, nebo název stránky, na které se pouze mají tyto odkazy zobrazit.
:* Třetí parametr může být buď prázdný řetězec, nebo nadpis lišty
:* Čtvrtý parametr může být buď prázdný řetězec, nebo ID jiného prvku, na začátek kterého se mají odkazy vložit.
:* Pátý parametr může být buď prázdný řetězec, nebo ID jiného prvku, před který se mají odkazy vložit.
</onlyinclude><pre> */
function addLinktoolbar(items, page, caption, parent, next) {
if (page !== ("" || undefined) && getPagetitle() != page) return;
if (parent === ("" || undefined)) parent = 'p-personal';
if (next === ("" || undefined)) next = 'pt-preferences';
if (caption !== ("" || undefined)) {
mw.util.addPortletLink(
parent,
getPagetitle(),
caption,
'pt-caption',
caption,
null,
'#' + next
);
}
for (var i = 0; i < items.length; i++) {
mw.util.addPortletLink(
parent,
getLocalURL(items[i][0]),
items[i][1],
'pt-' + items[i][1],
items[i][1],
null,
'#' + next
);
}
}
/* </pre><onlyinclude>
== Oblíbená interwiki ==
; Funkce
: Přeuspořádá interwiki odkazy tak, aby vybrané jazyky byly navrchu a případně buď ještě zvýrazní vybrané jazyky nebo skryje odkazy na ostatní jazyky.
; Použití
jQuery(function($) { reorderInterwiki(['en', 'de', 'sk'], false); });
; Parametry
:* První parametr obsahuje seznam jazykových kódů cizojazyčných Wikipedií oddělených čárkou.
:* Druhý parametr obsahuje <code>true</code> pokud se mají zobrazovat ''pouze'' vybrané jazyky nebo <code>false</code> pokud se mají zobrazovat i ostatní a vybrané se mají jen zobrazovat navrchu a tučně.
</onlyinclude><pre> */
function reorderInterwiki(priorityLanguages, removeOthers) {
langBox = document.getElementById('p-lang');
if (!langBox) return;
langList = langBox.getElementsByTagName('ul');
if (!langList) return;
langList = langList[0];
langItems = langList.getElementsByTagName('li');
priorityList = [];
for (var l = 0; l < priorityLanguages.length; l++) {
var reLanguageMatch = new RegExp('(^|\\s)interwiki-' + priorityLanguages[l] + '(\\s|$)');
for (var i = 0; i < langItems.length; i++) {
var item = langItems[i];
if (reLanguageMatch.test(item.className)) {
langList.removeChild(item);
priorityList.push(item);
break;
}
}
}
if (removeOthers) {
while (langList.hasChildNodes()) {
langList.removeChild(langList.childNodes[0]);
}
}
if (langList.hasChildNodes()) {
var firstNode = langList.childNodes[0];
for (var i = 0; i < priorityList.length; i++) {
langList.insertBefore(priorityList[i], firstNode);
priorityList[i].style.fontWeight = 'bold';
}
} else {
for (var i = 0; i < priorityList.length; i++) {
langList.appendChild(priorityList[i]);
}
}
}
/* </pre><onlyinclude>
== Dodatečné záložky ==
'''{{Námitka}} Sloučit s ''Odkazy na oblíbené stránky'''''
; Funkce
: Přidá některé užitečné záložky: U anonymů odkaz na WHOIS, u neexistujících a zamčených stránek odkaz na log
; Použití
jQuery(function($) { addAdditionalTabs(); });
</onlyinclude><pre> */
// TODO: zkontrolovat funkčnost
function addAdditionalTabs() {
var title = mw.config.get('wgPageName');
var username = null;
var addWhois = false,
addLog = false;
var ns = mw.config.get('wgCanonicalNamespace');
if (ns === 'User' || ns === 'User_talk') {
// uživatelská stránka nebo diskuse (příp. podstránka)
username = title;
addWhois = true;
addLog = true;
} else if (ns === 'Special' && mw.config.get('wgCanonicalSpecialPageName') == 'Contributions') {
// příspěvky uživatele
for (var i = 0; i < document.forms.length; ++i) {
var form = document.forms[i];
if (form.elements['target']) username = form.elements['target'].value;
if (username) break;
}
if (!username) return;
username = 'User:' + username;
addWhois = true;
} else if (ns !== 'Special') {
// normální, nespeciální stránka
if (!document.getElementById('ca-history') || !document.getElementById('ca-edit')) {
// neexistující nebo zamčená stránka – přidat odkaz na log
appendTab('/w/index.php?title=Special:Log&page=' + title, "Log");
}
return;
} else return;
username = username.substring(username.indexOf(':') + 1);
username = username.replace(/\/.*$/, '');
if (addLog) appendTab('/w/index.php?title=Special:Log&page=User:' + username, "Log");
if (username.match(/([0-9]{1,3}\.){3}[0-9]{1,3}/)) {
// anonymní uživatel
if (addWhois) appendTab("http://toolserver.org/~chm/whois.php?ip=" + username, "Whois");
}
}
/* </pre><onlyinclude>
== Odkaz na index ==
'''{{Námitka}} Sloučit s ''Odkazy na oblíbené stránky'''''
; Funkce
: Na každou stránku přidá odkaz do stránky [[Speciální:Všechny stránky]] zobrazující okolí aktuální stránky.
; Použití
jQuery(function($) { addIndexLink(); });
</onlyinclude><pre> */
// TODO: zkontrolovat funkčnost
function addIndexLink() {
if (mw.config.get('wgCanonicalNamespace') === "Special") return;
var pageName = mw.config.get('wgPageName');
appendTab(getLocalURL('Special:Allpages/' + pageName.substring(0, pageName.length - 1)), 'Index');
}
/* </pre><onlyinclude>
== Odkazy na jiné Wikipedie při chybějícím interwiki ==
'''{{Zamítnuto}} Pravděpodobně nefunkční'''
; Funkce
: U stránky bez interwiki zobrazí odkazy na stejně pojmenované stránky na vybraných cizojazyčných Wikipediích.
; Použití
jQuery(function($) { showDefaultInterwiki(['cs', 'en']); });
; Parametry
: Buď seznam jazykových kódů cizojazyčných Wikipedií oddělených čárkou, nebo <code>null</code> pro nějaké výchozí
</onlyinclude><pre> */
// TODO: zkontrolovat funkčnost
function ShowDefaultInterwiki(langs) {
if (!langs || langs.length == 0) {
langs = ['cs', 'en', 'de', 'sk', 'pl', 'fr'];
}
// stránky, které už interwiki mají
var langBox = document.getElementById('p-lang');
if (!langBox || document.getElementById('wbc-linkToItem-link')) return;
// bez editací, historie atd., s výjimkou situace, kdy stránka dosud ani neexistuje (a ošetřit NS MediaWiki)
if (!document.getElementById('t-cite') && document.getElementById('ca-history') && mw.config.get('wgNamespaceNumber') !== 8) return;
if (!langBox) {
langBox = document.createElement('div');
langBox.id = 'p-lang';
langBox.className = mw.config.get('skin') === 'vector' ? 'portal' : 'portlet';
var caption = document.createElement('h5');
caption.appendChild(document.createTextNode('totéž jinde'));
langBox.appendChild(caption);
}
var langBody = document.createElement('div');
langBody.className = mw.config.get('skin') === 'vector' ? 'body' : 'pBody';
langBox.appendChild(langBody);
var langList = document.createElement('ul');
langBody.appendChild(langList);
for (var i = 0; i < langs.length; ++i) {
var lang = langs[i];
if (lang == mw.config.get('wgContentLanguage')) continue;
var item = document.createElement('li');
item.className = 'interwiki-' + lang;
langList.appendChild(item);
var link = document.createElement('a');
var ns = mw.config.get('wgCanonicalNamespace');
if (ns.length > 0) ns += ':';
var title = mw.config.get('wgTitle');
if (ns === 'Special') title = mw.config.get('wgCanonicalSpecialPageName');
link.href = '//' + lang + '.wikipedia.org' + getLocalURL(ns + title);
link.appendChild(document.createTextNode(lang));
item.appendChild(link);
}
var columnOne = document.getElementById(mw.config.get('skin') === 'vector' ? 'mw-panel' : 'column-one');
columnOne.appendChild(langBox);
}
/* </pre><onlyinclude>
== Chytré sledované stránky ==
; Autor
: [[:en:User:UncleDouggie]]
; Funkce
: Ke sledovaným stránkám přidá další možnosti úpravy
; Použití
jQuery(function($) { smartWatchlist(); });
; Parametry
: Viz komentáře v kódu
</onlyinclude><pre> */
// TODO: zkontrolovat funkčnost
/** Smart watchlist
*
* Provides ability to selectively hide and/or highlight changes in a user's watchlist display.
* Author: [[User:UncleDouggie]]
*
*/
// Extend jQuery to add a simple color picker optimized for our use
function smartWatchlist() {
// works on any display element
$.fn.swlActivateColorPicker = function(callback) {
if (this.length > 0 && !$colorPalette) {
constructPalette();
}
return this.each(function() {
attachColorPicker(this, callback);
});
};
$.fn.swlDeactivateColorPicker = function() {
return this.each(function() {
deattachColorPicker(this);
});
};
// set background color of elements using the palette within this class
$.fn.swlSetColor = function(paletteIndex) {
return this.each(function() {
setColor(this, paletteIndex);
});
};
var colorPickerOwner;
var $colorPalette = null;
var paletteVisible = false;
var onChangeCallback = null; // should be able to vary for each color picker using a subclosure (not today)
var constructPalette = function() {
$colorPalette = $("<div />")
.css({
width: '97px',
position: 'absolute',
border: '1px solid #0000bf',
'background-color': '#f2f2f2',
padding: '1px'
});
// add each color swatch to the pallete
$.each(colors, function(i) {
$("<div> </div>").attr("flag", i)
.css({
height: '12px',
width: '12px',
border: '1px solid #000',
margin: '1px',
float: 'left',
cursor: 'pointer',
'line-height': '12px',
'background-color': "#" + this
})
.bind("click", function() {
changeColor($(this).attr("flag"), $(this).css("background-color"))
})
.bind("mouseover", function() {
$(this).css("border-color", "#598FEF");
})
.bind("mouseout", function() {
$(this).css("border-color", "#000");
})
.appendTo($colorPalette);
});
$("body").append($colorPalette);
$colorPalette.hide();
};
var attachColorPicker = function(element, callback) {
onChangeCallback = callback;
$(element)
.css({
border: '1px solid #303030',
cursor: 'pointer'
})
.bind("click", togglePalette);
};
var deattachColorPicker = function(element) {
if ($colorPalette) {
$(element)
.css({
border: 'none', // should restore previous value
cursor: 'default' // should restore previous value
})
.unbind("click", togglePalette);
hidePalette();
}
};
var setColor = function(element, paletteIndex) {
$(element).css({
'background-color': '#' + colors[paletteIndex]
});
var bright = brightness(colors[paletteIndex]);
if (bright < 128) {
$(element).css("color", "#ffffff"); // white text on dark background
} else {
$(element).css("color", "");
}
};
var checkMouse = function(event) {
// check if the click was on the palette or on the colorPickerOwner
var selectorParent = $(event.target).parents($colorPalette).length;
if (event.target == $colorPalette[0] || event.target == colorPickerOwner || selectorParent > 0) {
return;
}
hidePalette();
};
var togglePalette = function() {
colorPickerOwner = this;
paletteVisible ? hidePalette() : showPalette();
};
var hidePalette = function() {
$(document).unbind("mousedown", checkMouse);
$colorPalette.hide();
paletteVisible = false;
};
var showPalette = function() {
$colorPalette
.css({
top: $(colorPickerOwner).offset().top + ($(colorPickerOwner).outerHeight()),
left: $(colorPickerOwner).offset().left
})
.show();
//bind close event handler
$(document).bind("mousedown", checkMouse);
paletteVisible = true;
};
var changeColor = function(paletteIndex, newColor) {
setColor(colorPickerOwner, paletteIndex);
hidePalette();
if (typeof(onChangeCallback) === "function") {
onChangeCallback.call(colorPickerOwner, paletteIndex);
}
};
var brightness = function(hexColor) {
// returns brightness value from 0 to 255
// algorithm from http://www.w3.org/TR/AERT
var c_r = parseInt(hexColor.substr(0, 2), 16);
var c_g = parseInt(hexColor.substr(2, 2), 16);
var c_b = parseInt(hexColor.substr(4, 2), 16);
return ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
};
var colors = [
'ffffff', 'ffffbd', 'bdffc2', 'bdf7ff', 'b3d6f9', 'ffbdfa',
'feb88a', 'ffff66', 'a3fe8a', '8afcfe', 'c1bdff', 'ff80e9',
'ff7f00', 'ffd733', '39ff33', '33fffd', '0ea7dd', 'cf33ff',
'db0000', 'e0b820', '0edd1f', '0ba7bf', '3377ff', 'a60edd',
'990c00', '997500', '0c9900', '008499', '1a0edd', '800099',
'743436', '737434', '347440', '346674', '1b0099', '743472'
];
}
/** Smart watchlist settings
*
* All settings are grouped together to support save, load, undo, import and export.
* Child objects are read from local storage or created on the fly.
* Structure of the settings object:
*
* settings: {
* controls: {},
* Used for control of the GUI and meta data about the settings object.
* Not subject to undo or import operations, but it is saved, loaded and exported.
*
* userCategories: [ (displayed category names in menu order, 1 based with no gaps)
* 1: {
* key: category key,
* name: category display name
* },
* 2: ...
* ],
* nextCategoryKey: 1 (monotonically increasing key to link page categories with display names)
* rebuildCategoriesOnUndo: "no" or "rebuild" (optimization for undo)
*
* wikiList: [ (in display order when sorted by wiki)
* 0: {
* domain: wiki domain (e.g., "en.wikipedia.org")
* displayName: "English Wikipedia"
* },
* 1: ...
* ],
* wikis: {
* wiki domain 1: {
* watchlistToken: [ // not included for home wiki/account
* 0: { token: tokenID,
* userName: username on remote wiki }
* 1: ...
* ],
* active: boolean,
* expanded: boolen,
* lastLoad: time,
* pages { // contains only pages with settings, not everything on a watchlist
* pageID1: {
* category: category key,
* patrolled: revision ID,
* flag: page flag key,
* hiddenSections: {
* section 1 title: date hidden,
* ...
* }
* hiddenRevs: {
* revID1: date hidden,
* ...
* }
* },
* pageID2: ...
* },
* users {
* username1: {
* flag: user flag key,
* hidden: date hidden
* },
* username2: ...
* }
* },
* wiki domain 2: ...
* }
* }
*/
// create a closure so the methods aren't global but we can still directly reference them
(function() {
// global hooks for event handler callbacks into functions within the closure scope
SmartWatchlist = {
changeDisplayedCategory: function() {
changeDisplayedCategory.apply(this, arguments);
},
changePageCategory: function() {
changePageCategory.apply(this, arguments);
},
hideRev: function() {
hideRev.apply(this, arguments);
},
patrolRev: function() {
patrolRev.apply(this, arguments);
},
hideUser: function() {
hideUser.apply(this, arguments);
},
processOptionCheckbox: function() {
processOptionCheckbox.apply(this, arguments);
},
clearSettings: function() {
clearSettings.apply(this, arguments);
},
undo: function() {
undo.apply(this, arguments);
},
setupCategories: function() {
if (setupCategories) {
setupCategories.apply(this, arguments);
} else {
alert("Category editor did not load. Try reloading the page.");
}
}
};
var settings = {};
var lastSettings = [];
var maxSettingsSize = 2000000;
var maxUndo = 100; // dynamically updated
var maxSortLevels = 4;
// for local storage - use separate settings for each wiki user account
var storageKey = "SmartWatchlist." + mw.config.get('wgUserName');
var storage = null;
var initialize = function() {
// check for local storage availability
try {
if (typeof(localStorage) === "object" && typeof(JSON) === "object") {
storage = localStorage;
}
} catch (e) {} // ignore error in FF 3.6 with dom.storage.enabled=false
readLocalStorage(); // load saved user settings
initSettings();
createSettingsPanel();
// build menu to change the category of a page
var $categoryMenuTemplate = $constructCategoryMenu("no meta")
// no attributes other than onChange allowed so the menu can be rebuilt in setupCategories()!
.attr("onChange", "javascript:SmartWatchlist.changePageCategory(this, value);");
var lastPageID = null;
var rowsProcessed = 0;
// process each displayed change row
$("table.mw-enhanced-rc tr").each(function() {
rowsProcessed++;
var $tr = $(this);
var $td = $tr.find("td:last-child");
var isHeader = false;
// check if this is the header for an expandable list of changes
if ($tr.find(".mw-changeslist-expanded").length > 0) {
isHeader = true;
lastPageID = null; // start of a new page section
}
/* Parse IDs from the second link. The link text can be of the following forms:
1. "n changes" - used on a header row for a collapsable list of changes
2. "cur" - an individual change within a list of changes to the same page
3. "diff" - single change with no header row
4. "talk" - deleted revision. No page ID is present on such a row. */
var $secondLink = $td.find("a:eq(1)"); // get second <a> tag in the cell
var href = $secondLink.attr("href");
var linkText = $secondLink.text();
var pageID = href.replace(/.*&curid=/, "").replace(/&.*/, "");
var revID = href.replace(/.*&oldid=/, "").replace(/&.*/, "");
var user = $td.find(".mw-userlink").text();
// check if we were able to parse the page ID
if (!isNaN(parseInt(pageID))) {
lastPageID = pageID;
}
// check for a deleted revision
else if ($td.find(".history-deleted").length > 0 && lastPageID) {
pageID = lastPageID; // use page ID from the previous row in the same page, if any
}
// unable to determine type of row
else {
pageID = null;
if (console) {
console.log("SmartWatchlist: unable to parse row " + $td.text());
}
}
if (pageID) {
$tr.attr({
pageID: pageID,
wiki: document.domain
});
// check if we were able to parse the rev ID and have an individual change row
if (!isNaN(parseInt(revID)) &&
(linkText == "cur" || linkText == "diff")) {
// add the hide change link
$tr.attr("revID", revID);
var $revLink = $("<a/>", {
href: "javascript:SmartWatchlist.hideRev('" + pageID + "', '" + revID + "');",
title: "Hide this change",
text: "hide change"
});
$td.append($("<span/>")
.addClass("swlRevisionButton")
.append(" [").append($revLink).append("]")
);
// add the patrol prior changes link
var $patrolLink = $("<a/>", {
href: "javascript:SmartWatchlist.patrolRev('" + pageID + "', '" + revID + "');",
title: "Hide previous changes",
text: "patrol"
});
$td.append($("<span/>")
.addClass("swlRevisionButton")
.append(" [").append($patrolLink).append("]")
);
}
// check if this is the top-level row for a page
if (isHeader || linkText == "diff") {
// add the category menu with the current page category pre-selected
$newMenu = $categoryMenuTemplate.clone();
$td.prepend($newMenu);
// add the page attribute to the link to the page to support highlighting specific pages
$td.find("a:eq(0)") // get first <a> tag in the cell
.attr({
pageID: pageID,
wiki: document.domain
})
.addClass("swlPageTitleLink");
}
}
// check if we parsed a user for an individual change row
if (user && !isHeader) {
// mark change row for possible hiding/flagging
$tr.attr("wpUser", user);
if (!$tr.attr("wiki")) {
$tr.attr("wiki", document.domain);
}
// add the hide user link
var $hideUserLink = $("<a/>", {
href: "javascript:SmartWatchlist.hideUser('" + user + "');",
title: "Hide changes by " + user + " on all pages",
text: "hide user"
});
$td.append($("<span/>")
.addClass("swlHideUserButton")
.append(" [").append($hideUserLink).append("]")
);
}
}); // close each()
// set the user attribute for each username link to support highlighting specific users
$(".mw-userlink").each(function() {
var $userLink = $(this);
$userLink.attr({
wiki: document.domain,
wpUser: $userLink.text()
})
.addClass("swlUserLink");
});
initDisplayControls();
// restore last displayed category and apply display settings
changeDisplayedCategory(
selectCategoryMenu($("#swlSettingsPanelCategorySelector"), getSetting("controls", "displayedCategory")));
// check if we were able to do anything
if (rowsProcessed == 0) {
$("#SmartWatchlistOptions")
.append($("<p/>", {
text: 'To use Smart Watchlist, enable "enhanced recent changes" in your user preferences.'
})
.css("color", "#cc00ff")
);
}
};
var initDisplayControls = function() {
// set visibility of buttons and pulldowns shown on each change row
$(".swlOptionCheckbox").each(function() {
$checkbox = $(this);
// restore saved checkbox setting
$checkbox.attr("checked", getSetting("controls", [$checkbox.attr("controlsProperty")]));
// apply checkbox value to buttons
processOptionCheckbox(this);
});
};
// if the desired category exists, pre-select it in the menu
// otherwise, fallback to the default selection
var selectCategoryMenu = function($selector, category) {
// check if page category has been deleted
if (typeof(category) === "undefined") {
$selector.attr("selectedIndex", "0"); // fallback to first option
} else {
// attempt to use set page category
$selector.val(category);
if ($selector.val() == null) {
// desired category not in the menu, fallback to first option
$selector.attr("selectedIndex", "0");
}
}
return $selector.val(); // return actual category selected
};
// called when the displayed category menu setting is changed
var changeDisplayedCategory = function(category) {
setSetting("controls", "displayedCategory", category);
applySettings();
writeLocalStorage();
};
// called when the category for a page is changed
var changePageCategory = function(td, category) {
var $tr = $(td.parentNode.parentNode);
var pageID = $tr.attr("pageID");
var wiki = $tr.attr("wiki");
// convert category to a number if possible
if (typeof(category) === "string") {
var intCategory = parseInt(category);
if (!isNaN(intCategory)) {
category = intCategory;
}
}
// update category selection menus for all other instances of the page
$('tr[wiki="' + document.domain + '"][pageID="' + pageID + '"] select').val(category);
// update settings
snapshotSettings("change page category");
if (category == "uncategorized") {
deleteSetting("wikis", document.domain, "pages", pageID, "category")
} else {
setSetting("wikis", document.domain, "pages", pageID, "category", category);
}
writeLocalStorage();
// hide the page immediately if auto refresh
applySettings();
};
// callback for "hide change"
var hideRev = function(pageID, revID) {
var mode = getSetting("controls", "displayedCategory");
// hide the rows unless displaying everything currently
if (mode != "all+") {
var $tr = $('tr[wiki="' + document.domain + '"][revID="' + revID + '"]'); // retrieve individual change row
hideElements($tr);
suppressHeaders();
}
// update settings
snapshotSettings("hide change");
if (mode == "hide") {
deleteSetting("wikis", document.domain, "pages", pageID, "hiddenRevs", revID); // unhide
} else {
setSetting("wikis", document.domain, "pages", pageID, "hiddenRevs", revID, new Date()); // hide
}
writeLocalStorage();
};
// callback for "patrol"
var patrolRev = function(pageID, revID) {
var mode = getSetting("controls", "displayedCategory");
// hide the rows unless displaying everything currently
if (mode != "all+") {
var $tr = $('tr[wiki="' + document.domain + '"][pageID="' + pageID + '"]').filter(function() { // filter all rows for the page
var rowRevID = $(this).attr("revID");
return (rowRevID <= revID);
});
hideElements($tr);
suppressHeaders();
}
// update settings
snapshotSettings("patrol action");
setSetting("wikis", document.domain, "pages", pageID, "patrolled", revID);
writeLocalStorage();
};
// callback for "hide user"
var hideUser = function(user) {
var mode = getSetting("controls", "displayedCategory");
// hide the rows unless displaying everything currently
if (mode != "all+") {
var $tr = $('tr[wiki="' + document.domain + '"][wpUser="' + user + '"]'); // retrieve all changes by user
hideElements($tr);
suppressHeaders();
}
// update settings
snapshotSettings("hide user");
if (mode == "hide") {
deleteSetting("wikis", document.domain, "users", user, "hide"); // unhide
} else {
setSetting("wikis", document.domain, "users", user, "hide", new Date()); // hide
}
writeLocalStorage();
};
// toggle the state of a given class of user interface elements
var processOptionCheckbox = function(checkbox) {
var $checkbox = $(checkbox);
var $elements = $("." + $checkbox.attr("controlledClass"));
if (checkbox.checked) {
if ($checkbox.hasClass("swlColorPickerControl")) {
$elements
.attr("onClick", "javascript:return false;") // disable links so color picker can activate
.swlActivateColorPicker(setFlag);
} else {
$elements.show();
}
} else {
if ($checkbox.hasClass("swlColorPickerControl")) {
$elements
.attr("onClick", "") // re-enable links
.swlDeactivateColorPicker();
} else {
$elements.hide();
}
}
setSetting("controls", $checkbox.attr("controlsProperty"), checkbox.checked);
writeLocalStorage();
};
// callback from the color picker to flag a user or page
var setFlag = function(flag) {
$this = $(this); // element to be flagged
var $tr = $this.parents("tr[wiki]");
var wiki = $tr.attr("wiki");
var idLabel;
var settingPath;
var $idElement;
if ($this.hasClass("swlUserLink")) {
idLabel = "wpUser";
$idElement = $this;
settingPath = "users";
} else {
idLabel = "pageID";
$idElement = $tr;
settingPath = "pages";
}
var id = $idElement.attr(idLabel);
if (typeof(id) === "string") {
snapshotSettings("highlight");
// update the color on all other instances of the element
$('a[wiki="' + wiki + '"][' + idLabel + '="' + id + '"]').swlSetColor(flag);
// update settings
flag = parseInt(flag);
if (!isNaN(flag) && flag > 0) {
setSetting("wikis", wiki, settingPath, id, "flag", flag);
} else {
deleteSetting("wikis", wiki, settingPath, id, "flag");
}
writeLocalStorage();
}
};
// hide header rows that don't have any displayed changes
var suppressHeaders = function() {
// process all change list tables (page headers + changes)
var $tables = $("table.mw-enhanced-rc");
$tables.each(function(index) {
var $table = $(this);
// check if this is a header table with a following table
if ($table.filter(":has(.mw-changeslist-expanded)").length > 0 &&
index + 1 < $tables.length) {
// check if the following table has visible changes
var $visibleRows = $tables.filter(":eq(" + (index + 1) + ")")
.find("tr")
.not(".swlHidden");
if ($visibleRows.length == 0) {
hideElements($table);
}
}
});
};
// hide a set of jQuery elements and apply our own class
// to support header suppression and later unhiding
var hideElements = function($elements) {
$elements.hide();
$elements.addClass("swlHidden");
};
// reinitialize displayed content using current settings
var applySettings = function() {
var displayedCategory = getSetting("controls", "displayedCategory");
// show all changes, including heading tables
$(".swlHidden").each(function() {
var $element = $(this);
$element.show()
$element.removeClass("swlHidden");
});
if (displayedCategory != "all+" && displayedCategory != "hide") { // XXX should showing these be a new option?
// hide changes by set users
$('tr[wiki="' + document.domain + '"][wpUser]').each(function() {
var $tr = $(this);
if (getSetting("wikis", document.domain, "users", $tr.attr("wpUser"), "hide")) {
hideElements($tr);
}
});
}
// process each change row
$('tr[wiki="' + document.domain + '"][pageID]').each(function() {
var $tr = $(this);
var pageID = $tr.attr("pageID");
var revID = $tr.attr("revID");
var pageCategory = getSetting("wikis", document.domain, "pages", pageID, "category");
var pageFlag = getSetting("wikis", document.domain, "pages", pageID, "flag");
// check if there is a page category menu on the row
var $select = $tr.find('select');
if ($select.length == 1) {
// select proper item in the menu
var newCategoryKey = selectCategoryMenu($select, pageCategory);
// reset page category if the current category has been deleted
if (pageCategory && pageCategory != newCategoryKey) {
deleteSetting("wikis", document.domain, "pages", pageID, "category");
pageCategory = newCategoryKey;
}
}
// check if change should be hidden
// XXX should we show changes by hidden users when in "hidden" display mode? Maybe a new option.
var visible;
if (displayedCategory == "all+") {
visible = true;
} else if (revID &&
(getSetting("wikis", document.domain, "pages", pageID, "hiddenRevs", revID) || // specific revision is hidden
getSetting("wikis", document.domain, "pages", pageID, "patrolled") >= revID // revision has been patrolled
)) {
visible = false;
}
// check if page is hidden
else if (pageCategory == "hide" && displayedCategory != "hide") {
visible = false;
} else if (displayedCategory == "all") {
visible = true;
}
// check for no category
else if (displayedCategory == "uncategorized") {
if (pageCategory) {
visible = false;
} else {
visible = true;
}
}
// check if page is flagged
else if (displayedCategory == "flag" && typeof(pageFlag) !== "undefined") {
visible = true;
}
// check for selected category
else if (pageCategory && displayedCategory == pageCategory) {
visible = true;
} else {
visible = false;
}
if (!visible) {
hideElements($tr);
}
});
// hide changes to unknown pages if not displaying all pages
if (displayedCategory != "all+" && displayedCategory != "all" && displayedCategory != "uncategorized") {
hideElements($("table.mw-enhanced-rc tr").not('[pageID]'));
}
// decorate user links
$(".mw-userlink").each(function() {
var $userLink = $(this);
var user = $userLink.attr("wpUser");
var flag = getSetting("wikis", document.domain, "users", user, "flag");
if (typeof(flag) == "number") {
$userLink.swlSetColor(flag);
} else {
$userLink.swlSetColor(0);
}
});
// decorate page titles
$('a[pageID]').each(function() {
var $pageTitleLink = $(this);
var flag = getSetting("wikis", document.domain, "pages", [$pageTitleLink.attr("pageID")], "flag");
if (typeof(flag) == "number") {
$pageTitleLink.swlSetColor(flag);
} else {
$pageTitleLink.swlSetColor(0);
}
});
suppressHeaders();
};
// add smart watchlist settings panel below the standard watchlist options panel
var createSettingsPanel = function() {
// construct panel column 1
var $column1 = $("<td />").attr("valign", "top")
.append(
$("<input>", {
type: "checkbox",
"class": "swlOptionCheckbox",
controlledClass: "swlRevisionButton",
controlsProperty: "showRevisionButtons",
onClick: "javascript:SmartWatchlist.processOptionCheckbox(this);"
})
)
.append("Enable hide/patrol change buttons")
.append("<br />")
.append(
$("<input>", {
type: "checkbox",
"class": "swlOptionCheckbox",
controlledClass: "swlHideUserButton",
controlsProperty: "showUserButtons",
onClick: "javascript:SmartWatchlist.processOptionCheckbox(this);"
})
)
.append("Enable hide user buttons")
.append("<br />")
.append(
$("<input>", {
type: "checkbox",
"class": "swlOptionCheckbox swlColorPickerControl",
controlledClass: "swlUserLink",
controlsProperty: "showUserColorPickers",
onClick: "javascript:SmartWatchlist.processOptionCheckbox(this);"
})
)
.append("Assign user highlight colors")
.append("<br />")
.append(
$("<input>", {
type: "checkbox",
"class": "swlOptionCheckbox swlColorPickerControl",
controlledClass: "swlPageTitleLink",
controlsProperty: "showPageColorPickers",
onClick: "javascript:SmartWatchlist.processOptionCheckbox(this);"
})
)
.append("Assign page highlight colors")
.append("<br />")
.append(
$("<input>", {
type: "checkbox",
"class": "swlOptionCheckbox",
controlledClass: "swlPageCategoryMenu",
controlsProperty: "showPageCategoryButtons",
onClick: "javascript:SmartWatchlist.processOptionCheckbox(this);"
})
)
.append("Assign page categories");
// construct panel column 2
var $column2 = $("<div />")
.attr("style", "padding-left: 25pt;")
.append(
$("<div />").attr("align", "center")
.append(
$("<input />", {
type: "button",
onClick: "javascript:SmartWatchlist.clearSettings();",
title: "Reset all page and user settings and remove all custom categories",
value: "Clear settings"
})
)
.append(" ")
.append(
$("<input />", {
type: "button",
onClick: "javascript:SmartWatchlist.setupCategories();",
title: "Create, change and delete custom category names",
value: "Setup categories"
})
)
.append(" ")
.append(
$("<input />", {
type: "button",
id: "swlUndoButton",
onClick: "javascript:SmartWatchlist.undo();",
title: "Nothing to undo",
disabled: "disabled",
value: "Undo"
})
)
.append("<p />")
.append("Display pages in: ")
.append(
$constructCategoryMenu("meta")
// no attributes other than onChange allowed so the menu can be rebuild in setupCategories()!
.attr("onChange", "javascript:SmartWatchlist.changeDisplayedCategory(value);")
)
);
$sortPanel = $("<div />").attr("align", "right")
.append("Sort order: ");
for (var i = 0; i < maxSortLevels; i++) {
$sortPanel
.append($constructSortMenu().attr("selectedIndex", i))
.append("<br />");
if (i == 0) {
$sortPanel.append("(not yet) ");
}
}
// construct panel column 3
var $column3 = $("<div />")
.attr("style", "padding-left: 25pt;")
.append($sortPanel);
// construct main settings panel
$("#mw-watchlist-options")
.after(
$("<fieldset />", {
id: "SmartWatchlistOptions"
})
.append(
$("<legend />", {
text: "Smart watchlist settings"
})
)
.append(
$("<table />")
.append(
$("<tr />")
.append($column1)
.append(
$("<td />", {
valign: "top"
})
.append($column2)
)
.append(
$("<td />", {
valign: "top"
})
.append($column3)
)
)
)
);
if (!storage) {
$("#SmartWatchlistOptions")
.append(
$("<p />", {
text: "Your browser does not support saving settings to local storage. " +
"Items hidden or highlighted will not be retained after reloading the page."
})
.css("color", "red")
);
}
};
// construct a page category menu
var $constructCategoryMenu = function(metaOptionString) {
var $selector =
$("<select />", {
"class": "namespaceselector swlCategoryMenu",
withMeta: metaOptionString // flag so the menu can be rebuilt in setupCategories()
});
if (metaOptionString == "meta") {
// for updating the displayed category selection
$selector.attr("id", "swlSettingsPanelCategorySelector");
} else {
// for hiding/showing page category menus
$selector.addClass("swlPageCategoryMenu");
}
// create default category, must be first in the menu!!!
var categories = [{
value: "uncategorized",
text: "uncategorized"
}];
// add user categories, if any
var userCategories = getSetting("userCategories");
if (typeof(userCategories) === "object") {
for (var i = 0; i < userCategories.length && userCategories[i]; i++) {
var key = userCategories[i].key;
if (typeof(key) !== "number") {
alert("Smart watchlist user category definitions are corrupt. You will need to clear your settings. Sorry.");
break;
} else {
categories.push({
value: userCategories[i].key,
text: userCategories[i].name
})
}
}
}
// add special categories to settings menu
if (metaOptionString == "meta") {
categories.push({
value: "all",
text: "all except hidden"
}, {
value: "flag",
text: "highlighted"
});
}
categories.push({
value: "hide",
text: "hidden"
});
if (metaOptionString == "meta") {
categories.push({
value: "all+",
text: "everything"
});
}
// construct all <option> elements
for (var i in categories) {
$selector.append($("<option />", categories[i]));
}
return $selector;
};
// construct a page category menu
var $constructSortMenu = function() {
var $selector =
$("<select />", {
"class": "namespaceselector swlSortMenu"
});
var sortCriteria = [{
value: "wiki",
text: "Wiki"
}, {
value: "title",
text: "Title"
}, {
value: "timeDec",
text: "Time (newest first)"
}, {
value: "timeInc",
text: "Time (oldest first)"
}, {
value: "risk",
text: "Vandalism risk"
}, {
value: "namespace",
text: "Namespace"
}, {
value: "flagPage",
text: "Highlighted pages"
}, {
value: "flagUser",
text: "Highlighted users"
}];
// construct all <option> elements
for (var i in sortCriteria) {
$selector.append($("<option />", sortCriteria[i]));
}
return $selector;
};
// save settings for later undo
var snapshotSettings = function(currentAction, rebuildOption) {
if (typeof(rebuildOption) === "undefined") {
rebuildOption = "no";
}
setSetting("rebuildCategoriesOnUndo", rebuildOption);
var settingsClone = $.extend(true, {}, settings);
lastSettings.push(settingsClone);
while (lastSettings.length > maxUndo) {
lastSettings.shift();
}
if (currentAction) {
currentAction = "Undo " + currentAction;
} else {
currentAction = "Undo last change";
}
setSetting("undoAction", currentAction);
$("#swlUndoButton")
.attr("disabled", "")
.attr("title", currentAction);
};
// restore previous settings
var undo = function() {
if (lastSettings.length > 0) {
var currentControls = settings.controls;
settings = lastSettings.pop();
settings.controls = currentControls; // controls aren't subject to undo
// only rebuild menus when needed because it takes several seconds
if (getSetting("rebuildCategoriesOnUndo") == "rebuild") {
rebuildCategoryMenus(); // also updates display and local storage
} else {
writeLocalStorage();
applySettings();
}
var lastAction = getSetting("undoAction");
if (!lastAction) {
lastAction = "";
}
$("#swlUndoButton").attr("title", lastAction);
if (lastSettings.length == 0) {
$("#swlUndoButton")
.attr("disabled", "disabled")
.attr("title", "Nothing to undo");
}
}
};
// for use after a change to the category settings
var rebuildCategoryMenus = function() {
// rebuild existing category menus
$('.swlCategoryMenu').each(function() {
var $newMenu = $constructCategoryMenu($(this).attr('withMeta'));
$newMenu.attr("onChange", $(this).attr("onChange")); // retain old menu action
this.parentNode.replaceChild($newMenu.get(0), this);
});
// update menu selections and save settings
changeDisplayedCategory(
selectCategoryMenu($("#swlSettingsPanelCategorySelector"), getSetting("controls", "displayedCategory")));
initDisplayControls();
};
// read from local storage to current in-work settings during initialization
var readLocalStorage = function() {
if (storage) {
var storedString = storage.getItem(storageKey);
if (storedString) {
try {
settings = JSON.parse(storedString);
} catch (e) {
alert("Smart watchlist: error loading stored settings!");
settings = {};
}
}
// delete all obsolete local storage keys from prior versions and bugs
// this can eventually go away
var obsoleteKeys = [
"undefinedmarkedUsers",
"undefinedmarkedPages",
"undefinedpatrolledRevs",
"undefinedhiddenRevs",
"undefinedGUI",
"SmartWatchlist.flaggedPages",
"SmartWatchlist.flaggedUsers",
"SmartWatchlist.hiddenPages",
"SmartWatchlist.hiddenUsers",
"SmartWatchlist.markedUsers",
"SmartWatchlist.markedPages",
"SmartWatchlist.patrolledRevs",
"SmartWatchlist.hiddenRevs",
"SmartWatchlist.GUI",
"SmartWatchlist." + mw.config.get("wgUserName") + ".markedUsers",
"SmartWatchlist." + mw.config.get("wgUserName") + ".markedPages",
"SmartWatchlist." + mw.config.get("wgUserName") + ".patrolledRevs",
"SmartWatchlist." + mw.config.get("wgUserName") + ".userFlag",
"SmartWatchlist." + mw.config.get("wgUserName") + ".pageCategory",
"SmartWatchlist." + mw.config.get("wgUserName") + ".pageFlag",
"SmartWatchlist." + mw.config.get("wgUserName") + ".patrolledRevision",
"SmartWatchlist." + mw.config.get("wgUserName") + ".hiddenRevs",
"SmartWatchlist." + mw.config.get("wgUserName") + ".GUI",
"length"
];
for (var i in obsoleteKeys) {
if (typeof(storage.getItem(obsoleteKeys[i])) !== "undefined") {
storage.removeItem(obsoleteKeys[i]);
}
}
}
};
// update local storage to current in-work settings
var writeLocalStorage = function() {
if (storage) {
var storeString = JSON.stringify(settings);
var size = storeString.length;
if (size > maxSettingsSize) {
storeString = "";
alert("Smart watchlist: new settings are too large to be saved (" + size + " bytes)!")
return;
}
var lastSaveString = storage.getItem(storageKey);
try {
storage.setItem(storageKey, storeString);
} catch (e) {
storeString = "";
alert("Smart watchlist: error saving new settings!");
// revert to previously saved settings that seemed to work
storage.setItem(storageKey, lastSaveString);
}
maxUndo = Math.floor(maxSettingsSize / size) + 2;
}
};
// erase all saved settings
var clearSettings = function() {
snapshotSettings("clear settings", "rebuild");
var currentControls = settings.controls;
settings = {};
settings.controls = currentControls; // controls aren't subject to clearing
initSettings();
rebuildCategoryMenus(); // also updates display and local storage
};
// lookup a setting path passed as a series of arguments
// returns undefined if no setting exists
var getSetting = function() {
var obj = settings;
for (var index in arguments) {
if (typeof(obj) !== "object") {
return undefined; // part of path is missing
}
obj = obj[arguments[index]];
}
return obj;
};
// set the value of a setting path passed as a series of argument strings
// creates intermediate objects as needed
// number arguments reference arrays and string arguments reference associative array properties
// the last argument is the value to be set (can be any type)
var setSetting = function() {
if (arguments.length < 2) {
throw "setSetting: insufficient arguments";
}
var obj = settings;
for (var index = 0; index < arguments.length - 2; index++) {
var nextObj = obj[arguments[index]];
if (typeof(nextObj) !== "object") {
if (typeof(arguments[index + 1]) === "number") {
nextObj = obj[arguments[index]] = [];
} else {
nextObj = obj[arguments[index]] = {};
}
}
obj = nextObj;
}
obj[arguments[arguments.length - 2]] = arguments[arguments.length - 1];
};
// delete a setting path passed as a series of argument strings if the entire path exists
var deleteSetting = function() {
if (arguments.length < 1) {
throw "deleteSetting: insufficient arguments";
}
var obj = settings;
for (var index = 0; index < arguments.length - 1; index++) {
// check if we hit a snag and still have more arguments to go
if (typeof(obj) !== "object") {
return;
}
obj = obj[arguments[index]];
}
if (typeof(obj) === "object") {
delete obj[arguments[index]];
}
};
var initSettings = function() {
// check if home domain already exists
if (!getSetting("wikis", document.domain)) {
setSetting("wikis", document.domain, "active", true);
var wikiNumber = 0;
var wikiList = getSetting("wikiList");
if (wikiList) {
wikiNumber = wikiList.length;
}
setSetting("wikiList", wikiNumber, {
domain: document.domain,
displayName: document.domain
});
}
if (!settings.nextCategoryKey) {
settings.nextCategoryKey = 1;
}
};
// dialog windows
var setupCategories = null;
mw.loader.using(['jquery.ui.dialog', 'jquery.ui.sortable'], function() {
setupCategories = function() {
// construct a category name row for editing
var addCategory = function(key, name) {
$editTable.append(
$('<tr />')
.append(
$('<td />').append($('<span />').addClass('ui-icon ui-icon-arrowthick-2-n-s'))
)
.append(
$('<td />').append(
$('<input />', {
type: 'text',
size: '20',
categoryKey: key,
value: name
})
)
)
);
};
// jQuery UI sortable() seems to only like <ul> top-level elements
var $editTable = $('<ul />').sortable({
axis: 'y'
});
for (var i in settings.userCategories) {
addCategory(settings.userCategories[i].key,
settings.userCategories[i].name);
}
if (!getSetting('userCategories', 0)) {
addCategory(settings.nextCategoryKey++, ''); // pre-add first category if needed
}
var $interface = $('<div />')
.css({
'position': 'relative',
'margin-top': '0.4em'
})
.append(
$('<ul />')
.append($('<li />', {
text: "Renamed categories retain current pages."
}))
.append($('<li />', {
text: "Dragging lines changes the order in category menus."
}))
.append($('<li />', {
text: "To delete a category, blank its name."
}))
.append($('<li />', {
text: "Pages in deleted categories revert to uncategorized."
}))
)
.append($('<br />'))
.append($editTable)
.append($('<br />'))
.dialog({
width: 400,
autoOpen: false,
title: 'Custom category setup',
modal: true,
buttons: {
'Save': function() {
$(this).dialog('close');
snapshotSettings('category setup', 'rebuild');
// replace category names in saved settings
deleteSetting('userCategories');
var index = 0;
$editTable.find('input').each(function() {
var name = $.trim(this.value);
if (name.length > 0) { // skip blank categories
// convert category key back into a number
var key = $(this).attr('categoryKey');
if (typeof(key) === "string") {
var intKey = parseInt(key);
if (!isNaN(intKey)) {
setSetting('userCategories', index++, {
key: intKey,
name: name
});
}
}
}
});
rebuildCategoryMenus();
},
'Add category': function() {
addCategory(settings.nextCategoryKey++, '');
},
'Cancel': function() {
$(this).dialog('close');
}
}
});
$interface.dialog('open');
}
});
// activate only on the watchlist page
if (mw.config.get("wgNamespaceNumber") == -1 && mw.config.get("wgTitle") == "Watchlist") {
$(document).ready(initialize);
};
})();
/* </pre> */