MediaWiki:Common.js: Skillnad mellan sidversioner
Från bevaringsprogram
Ingen redigeringssammanfattning |
Ingen redigeringssammanfattning |
||
Rad 1: | Rad 1: | ||
/* JavaScript som skrivs här körs varje gång en användare laddar en sida. */ | /* JavaScript som skrivs här körs varje gång en användare laddar en sida. */ | ||
/ | //<source lang="javascript"> | ||
if( | /* | ||
Ajax-based simple Category manager. Allows adding/removing/changing categories on a page view. | |||
} else { | Supports multiple category changes, as well as redirect and disambiguation resolution. Also | ||
plugs into the upload form. Search engines to use for the suggestion list are configurable, and | |||
} | can be selected interactively. | ||
Author: [[User:Lupo]], April-May 2010 | |||
License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0) | |||
Choose whichever license of these you like best :-) | |||
*/ | |||
// Globals: | |||
// (inline script on the page): | |||
// wgNamespaceNumber, wgCanonicalSpecialPageName, wgNamespaceIds (optional), wgFormattedNamespaces (optional) | |||
// wgScript, wgServer, wgArticlePath, wgScriptPath, wgAction, wgPageName, wgTitle, wgUserName, wgIsArticle, | |||
// wgArticleId | |||
// ajax.js | |||
// sajax_init_object | |||
// wikibits.js | |||
// addOnloadHook, window.ie6_bugs | |||
if (typeof (HotCat) == 'undefined') { // Guard against double inclusions | |||
// Configuration stuff. | |||
var HotCat = { | |||
// Localize these messages to the main language of your wiki. | |||
messages : | |||
{ cat_removed : 'tog bort [[Kategori:$1]]' | |||
,cat_added : 'lade till [[Kategori:$1]]' | |||
,cat_keychange: 'ändrade sorteringen i [[Kategori:$1]]: ' | |||
,cat_notFound : 'Kategorin "$1" hittades inte' | |||
,cat_exists : 'Kategorin "$1" finns redan, lades inte till.' | |||
,cat_resolved : ' (omdirigeringen [[Kategori:$1]] rättades)' | |||
,uncat_removed: 'tog bort {{uncategorized}}' | |||
,using : ' med [[Wikipedia:Hotcat|Hotcat]]' | |||
,multi_change : '$1 kategorier' | |||
// $1 is replaced by a number | |||
,commit : 'Spara' | |||
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, | |||
// see localization hook below. | |||
,ok : 'OK' | |||
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, | |||
// see localization hook below. | |||
,cancel : 'Avbryt' | |||
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, | |||
// see localization hook below. | |||
,multi_error : 'Kunde inte hämta sidans text från servern. Dina ändringar sparades inte.' | |||
+' Försök igen senare.' | |||
// Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, | |||
// see localization hook below. | |||
} | |||
,category_regexp : '[Cc]ategory|[Kk]ategori' | |||
// Regular sub-expression matching all possible names for the category namespace. Is automatically localized | |||
// correctly if you're running MediaWiki 1.16 or later. Otherwise, set it appropriately, e.g. at the German | |||
// Wikipedia, use '[Cc]ategory|[Kk]ategorie', or at the Chinese Wikipedia, use '[Cc]ategory|分类|分類'. | |||
,category_canonical : 'Kategori' | |||
// The standard category name on your wiki. Is automatically localized correctly if you're running | |||
// MediaWiki 1.16 or later; otherwise, set it to the preferred category name (e.g., "Kategorie"). | |||
,categories : 'Kategorier' | |||
// Plural of category_canonical | |||
,disambig_category : null | |||
// Any category in this category is deemed a disambiguation category; i.e., a category that should not contain | |||
// any items, but that contains links to other categories where stuff should be categorized. If you don't have | |||
// that concept on your wiki, set it to null. | |||
,redir_category : 'Kategoriomdirigering' | |||
// Any category in this category is deemed a (soft) redirect to some other category defined by the first link | |||
// to another category. If your wiki doesn't have soft category redirects, set this to null. | |||
,links : {change: '(±)', remove: '(-)', add: '(+)', restore: '(×)', undo: '(×)'} | |||
// The little modification links displayed after category names. | |||
,addmulti : '<span>+<sup>+</sup></span>' | |||
// The HTML content of the "enter multi-mode" link at the front. | |||
,disable : | |||
function () { // Return true to disable HotCat | |||
return ( wgNamespaceNumber < 0 // Special pages; Special:Upload is handled differently | |||
|| wgNamespaceNumber == 6 // Fil | |||
|| wgNamespaceNumber == 7 // Fildiskussion | |||
|| wgNamespaceNumber == 10 // Mall | |||
|| typeof (wgNamespaceIds) != 'unknown' | |||
&& ( wgNamespaceNumber == wgNamespaceIds['creator'] | |||
|| wgNamespaceNumber == wgNamespaceIds['timedtext'] | |||
) | |||
); | |||
} | |||
,uncat_regexp : /\{\{\s*([Uu]ncat(egori[sz]ed( image)?)?|[Nn]ocat|[Nn]eedscategory)[^}]*\}\}/g | |||
// A regexp matching a templates used to mark uncategorized pages, if your wiki does have that. | |||
// If not, set it to null. | |||
,existsYes : 'http://upload.wikimedia.org/wikipedia/commons/thumb/b/be/P_yes.svg/20px-P_yes.svg.png' | |||
,existsNo : 'http://upload.wikimedia.org/wikipedia/commons/thumb/4/42/P_no.svg/20px-P_no.svg.png' | |||
// The images used for the little indication icon. Should not need changing. | |||
}; | |||
if (wgUserLanguage != wgContentLanguage) importScript ('MediaWiki:Gadget-HotCat.js/' + wgUserLanguage); | |||
// Localization hook to localize HotCat.messages.commit and HotCat.messages.multi_error. For German, the | |||
// file would be "MediaWiki:Gadget-HotCat.js/de", and its contents could be for instance | |||
// | |||
// HotCat.messages.commit = 'Speichern'; | |||
// HotCat.messages.ok = 'OK'; | |||
// HotCat.messages.cancel = 'Abbrechen'; | |||
// HotCat.messages.multi_error = 'Seitentext konnte nicht vom Server geladen werden. Die Änderungen können ' | |||
// +'leider nicht gespeichert werden.'; | |||
// No further changes should be necessary here. However, you should also localize the search engine names. | |||
// Search in this file for "Localize the names here", and localize the names there. | |||
(function () { | |||
// First auto-localize the category names | |||
if (typeof (wgFormattedNamespaces) != 'undefined' && wgFormattedNamespaces['14']) { | |||
function create_regexp_str (name) | |||
{ | |||
if (!name || name.length == 0) return ""; | |||
var initial = name.substr (0, 1); | |||
name = name.substring(1).replace (/[ _]/g, '[ _]').replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1'); | |||
return '[' + initial.toLowerCase () + initial.toUpperCase () + ']' + name; | |||
} | |||
HotCat.category_canonical = wgFormattedNamespaces['14']; | |||
HotCat.category_regexp += '|' + create_regexp_str (HotCat.category_canonical); | |||
for (var cat_name in wgNamespaceIds) { | |||
if ( typeof (cat_name) == 'string' | |||
&& cat_name.toLowerCase () != HotCat.category_canonical.toLowerCase () | |||
&& wgNamespaceIds[cat_name] == 14) | |||
{ | |||
HotCat.category_regexp += '|' + create_regexp_str (cat_name); | |||
} | |||
} | |||
} | |||
// Utility functions. Yes, this duplicates some functionality that also exists in other places, but | |||
// to keep this whole stuff in a single file not depending on any other on-wiki Javascripts, we re-do | |||
// these few operations here. | |||
function bind (func, target) { | |||
var f = func, tgt = target; | |||
return function () { return f.apply (tgt, arguments); }; | |||
} | |||
function make (arg, literal) { | |||
if (!arg) return null; | |||
return literal ? document.createTextNode (arg) : document.createElement (arg); | |||
} | |||
function param (name, uri) { | |||
if (typeof (uri) == 'undefined' || uri === null) uri = document.location.href; | |||
var re = RegExp ('[&?]' + name + '=([^&#]*)'); | |||
var m = re.exec (uri); | |||
if (m && m.length > 1) return decodeURIComponent(m[1]); | |||
return null; | |||
} | |||
function title (href) { | |||
if (!href) return null; | |||
var script = wgScript + '?'; | |||
if (href.indexOf (script) == 0 || href.indexOf (wgServer + script) == 0) { | |||
// href="/w/index.php?title=..." | |||
return param ('title', href); | |||
} else { | |||
// href="/wiki/..." | |||
var prefix = wgArticlePath.replace ('$1', ""); | |||
if (href.indexOf (prefix) != 0) prefix = wgServer + prefix; // Fully expanded URL? | |||
if (href.indexOf (prefix) == 0) | |||
return decodeURIComponent (href.substring (prefix.length)); | |||
} | |||
return null; | |||
} | |||
function hasClass (elem, name) { | |||
return (' ' + elem.className + ' ').indexOf (' ' + name + ' ') >= 0; | |||
} | |||
function capitalize (str) { | |||
if (!str || str.length == 0) return str; | |||
return str.substr(0, 1).toUpperCase() + str.substr (1); | |||
} | |||
// Text modification | |||
var findCatsRE = | |||
new RegExp ('\\[\\[\\s*(?:' + HotCat.category_regexp + ')\\s*:\[^\\]\]+\\]\\]', 'g'); | |||
function replaceByBlanks (match) { | |||
return match.replace(/(\s|\S)/g, ' '); // /./ doesn't match linebreaks. /(\s|\S)/ does. | |||
} | |||
function find_category (wikitext, category, once) { | |||
var cat_name = category.replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1'); | |||
var initial = cat_name.substr (0, 1); | |||
var cat_regex = new RegExp ('\\[\\[\\s*(' + HotCat.category_regexp + ')\\s*:\\s*' | |||
+ (initial == '\\' | |||
? initial | |||
: '[' + initial.toUpperCase() + initial.toLowerCase() + ']') | |||
+ cat_name.substring (1).replace (/[ _]/g, '[ _]') | |||
+ '\\s*(\\|.*?)?\\]\\]', 'g' | |||
); | |||
if (once) return cat_regex.exec (wikitext); | |||
var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks) | |||
.replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks); | |||
var result = []; | |||
var curr_match = null; | |||
while ((curr_match = cat_regex.exec (copiedtext)) != null) { | |||
result.push ({match : curr_match}); | |||
} | |||
result.re = cat_regex; | |||
return result; // An array containing all matches, with positions, in result[i].match | |||
} | |||
function change_category (wikitext, toRemove, toAdd, key) { | |||
function find_insertionpoint (wikitext) { | |||
var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks) | |||
.replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks); | |||
// Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element". | |||
var index = -1; | |||
findCatsRE.lastIndex = 0; | |||
while (findCatsRE.exec(copiedtext) != null) index = findCatsRE.lastIndex; | |||
// We should try to find interwiki links here, but that's for later. | |||
return index; | |||
} | |||
var summary = []; | |||
var nameSpace = HotCat.category_canonical; | |||
var cat_point = -1; // Position of removed category; | |||
if (key) key = '|' + key; | |||
var keyChange = (toRemove && toAdd && toRemove == toAdd && toAdd.length > 0); | |||
if (toRemove && toRemove.length > 0) { | |||
var matches = find_category (wikitext, toRemove); | |||
if (!matches || matches.length == 0) { | |||
return {text: wikitext, 'summary': summary, error: HotCat.messages.cat_notFound.replace ('$1', toRemove)}; | |||
} else { | |||
var before = wikitext.substring (0, matches[0].match.index); | |||
var after = wikitext.substring (matches[0].match.index + matches[0].match[0].length); | |||
if (matches.length > 1) { | |||
// Remove all occurrences in after | |||
matches.re.lastIndex = 0; | |||
after = after.replace (matches.re, ""); | |||
} | |||
if (toAdd) { | |||
nameSpace = matches[0].match[1] || nameSpace; | |||
if (!key) key = matches[0].match[2]; // Remember the category key, if any. | |||
} | |||
// Remove whitespace (properly): strip whitespace, but only up to the next line feed. | |||
// If we then have two linefeeds in a row, remove one. Otherwise, if we have two non- | |||
// whitespace characters, insert a blank. | |||
var i = before.length - 1; | |||
while (i >= 0 && before.charAt (i) != '\n' && before.substr (i, 1).search (/\s/) >= 0) i--; | |||
var j = 0; | |||
while (j < after.length && after.charAt (j) != '\n' && after.substr (j, 1).search (/\s/) >= 0) | |||
j++; | |||
if (i >= 0 && before.charAt (i) == '\n' && j < after.length && after.charAt (j) == '\n') | |||
i--; | |||
if (i >= 0) before = before.substring (0, i+1); else before = ""; | |||
if (j < after.length) after = after.substring (j); else after = ""; | |||
if (before.length > 0 && before.substring (before.length - 1).search (/\S/) >= 0 | |||
&& after.length > 0 && after.substr (0, 1).search (/\S/) >= 0) | |||
before += ' '; | |||
cat_point = before.length; | |||
wikitext = before + after; | |||
if (!keyChange) summary.push (HotCat.messages.cat_removed.replace ('$1', toRemove)); | |||
} | |||
} | |||
if (toAdd && toAdd.length > 0) { | |||
var matches = find_category (wikitext, toAdd); | |||
if (matches && matches.length > 0) { | |||
return {text: wikitext, 'summary': summary, error : HotCat.messages.cat_exists.replace ('$1', toAdd)}; | |||
} else { | |||
if (cat_point < 0) | |||
cat_point = find_insertionpoint (wikitext); | |||
var newcatstring = '\n[[' + nameSpace + ':' + toAdd + (key || "") + ']]'; | |||
if (cat_point >= 0) { | |||
wikitext = wikitext.substring (0, cat_point) + newcatstring + wikitext.substring (cat_point); | |||
} else { | |||
wikitext += newcatstring; | |||
} | |||
if (keyChange) { | |||
summary.push (HotCat.messages.cat_keychange.replace ('$1', toAdd) + '"' + key.substring(1) + '"'); | |||
} else { | |||
summary.push (HotCat.messages.cat_added.replace ('$1', toAdd)); | |||
} | |||
if (HotCat.uncat_regexp) { | |||
var txt = wikitext.replace (HotCat.uncat_regexp, ""); // Remove "uncat" templates | |||
if (txt.length != wikitext.length) { | |||
wikitext = txt; | |||
summary.push (HotCat.messages.uncat_removed); | |||
} | |||
} | |||
} | |||
} | |||
return {text: wikitext, 'summary': summary, error: null}; | |||
} | |||
if (wgAction == 'edit') { | |||
// Legacy code based on URI parameters, can add/remove/change only one single category. Still | |||
// used for single-category changes, and newly to supply a default edit summary for multi category | |||
// changes. | |||
var toRemove = param ('hotcat_removecat'); | |||
var toAdd = param ('hotcat_newcat'); | |||
if (toAdd) { | |||
toAdd = capitalize (toAdd.replace (/_/g, ' ').replace (/^\s+|\s+$/g, "")); | |||
if (toAdd.length == 0) toAdd = null; | |||
} | |||
if (toRemove) { | |||
toRemove = capitalize (toRemove.replace (/_/g, ' ').replace (/^\s+|\s+$/g, "")); | |||
if (toRemove.length == 0) toRemove = null; | |||
} | |||
if (toAdd || toRemove) { | |||
addOnloadHook (function () { | |||
if (!document.editform || !document.editform.wpTextbox1) return; | |||
var comment = param ('hotcat_comment') || ""; | |||
var cat_key = param ('hotcat_sortkey'); | |||
var result = change_category (document.editform.wpTextbox1.value, toRemove, toAdd, cat_key); | |||
var do_commit = !HotCat.noCommit && !result.error && param ('hotcat_nocommit') != '1'; | |||
document.editform.wpTextbox1.value = result.text; | |||
if (result.summary && result.summary.length > 0) | |||
document.editform.wpSummary.value = result.summary.join ('; ') + comment + HotCat.messages.using; | |||
document.editform.wpMinoredit.checked = true ; | |||
if (result.error) alert (result.error); | |||
if (do_commit) { | |||
// Hide the entire edit section so as not to tempt the user into editing... | |||
var content = document.getElementById ('bodyContent') // monobook & vector skin | |||
|| document.getElementById ('mw_contentholder') // modern skin | |||
|| document.getElementById ('article'); // classic skins | |||
if (content) content.style.display = 'none'; | |||
document.editform.submit(); | |||
} | |||
}); | |||
} | |||
return; | |||
} | |||
// The real HotCat UI | |||
function evtKeys (e) { | |||
e = e || window.event || window.Event; // W3C, IE, Netscape | |||
var code = 0; | |||
if (typeof (e.ctrlKey) != 'undefined') { // All modern browsers | |||
// Ctrl-click seems to be overloaded in FF/Mac (it opens a pop-up menu), so treat cmd-click | |||
// as a ctrl-click, too. | |||
if (e.ctrlKey || e.metaKey) code |= 1; | |||
if (e.shiftKey) code |= 2; | |||
} else if (typeof (e.modifiers) != 'undefined') { // Netscape... | |||
if (e.modifiers & (Event.CONTROL_MASK | Event.META_MASK)) code |= 1; | |||
if (e.modifiers & Event.SHIFT_MASK) code |= 2; | |||
} | |||
return code; | |||
} | |||
function evtKill (e) { | |||
e = e || window.event || window.Event; // W3C, IE, Netscape | |||
if (typeof (e.preventDefault) != 'undefined') { | |||
e.preventDefault (); | |||
e.stopPropagation (); | |||
} else | |||
e.cancelBubble = true; | |||
return false; | |||
} | |||
var catLine = null; | |||
var onUpload = false; | |||
var editors = []; | |||
var commitButton = null; | |||
var commitForm = null; | |||
var multiSpan = null; | |||
var pageText = null; | |||
var pageTime = null; | |||
var watchCreate = false; | |||
var watchEdit = false; | |||
var minorEdits = false; | |||
var is_rtl = false; | |||
function setMultiInput () { | |||
if (commitButton || onUpload) return; | |||
commitButton = make ('input'); | |||
commitButton.type = 'button'; | |||
commitButton.value = HotCat.messages.commit; | |||
commitButton.onclick = multiSubmit; | |||
if (multiSpan) { | |||
multiSpan.parentNode.replaceChild (commitButton, multiSpan); | |||
} else { | |||
catLine.appendChild (commitButton); | |||
} | |||
// Get the preferences, so that we can set wpWatchthis correctly later on. Must use Ajax here. | |||
if (wgUserName) { | |||
var request = sajax_init_object (); | |||
request.open | |||
('GET', wgServer + wgScriptPath + '/api.php?format=json&action=query&meta=userinfo&uiprop=options', true); | |||
request.onreadystatechange = | |||
function () { | |||
if (request.readyState != 4) return; | |||
if (request.status == 200 && request.responseText && request.responseText.charAt(0) == '{') { | |||
var json = eval ('(' + request.responseText + ')'); | |||
if (json && json.query && json.query.userinfo && json.query.userinfo.options) { | |||
watchCreate = json.query.userinfo.options.watchcreations == '1'; | |||
watchEdit = json.query.userinfo.options.watchdefault == '1'; | |||
minorEdits = json.query.userinfo.options.minordefault == 1; | |||
} | |||
} | |||
}; | |||
request.setRequestHeader ('Pragma', 'cache=yes'); | |||
request.setRequestHeader ('Cache-Control', 'no-transform'); | |||
request.send (null); | |||
} | |||
} | |||
function currentTimestamp () { | |||
var now = new Date(); | |||
var ts = "" + now.getUTCFullYear(); | |||
function two (s) { return s.substr (s.length - 2); } | |||
ts = ts | |||
+ two ('0' + (now.getUTCMonth() + 1)) | |||
+ two ('0' + now.getUTCDate()) | |||
+ two ('00' + now.getUTCHours()) | |||
+ two ('00' + now.getUTCMinutes()) | |||
+ two ('00' + now.getUTCSeconds()); | |||
return ts; | |||
} | |||
function performChanges () { | |||
// Don't use the edit API or LAPI, it's always bothersome to report back errors like edit | |||
// conflicts. Instead, make one remote call (blocking, because we can't continue anyway if | |||
// it doesn't succeed), getting the page text. Perform the changes on the text, then construct | |||
// a form to submit all this as a diff. | |||
if (pageText === null) { | |||
var request = sajax_init_object (); | |||
var uri = wgServer + wgScriptPath | |||
+ '/api.php?format=json&action=query&titles=' + encodeURIComponent (wgPageName) | |||
+ '&prop=info%7Crevisions&rvprop=content%7Ctimestamp'; | |||
request.open ('GET', uri, false); // Yes, synchronous | |||
request.send (null); | |||
if (request.status == 200 && request.responseText && request.responseText.charAt(0) == '{') { | |||
setPage (eval ('(' + request.responseText + ')')); | |||
} | |||
} | |||
if (pageText === null) { | |||
alert (HotCat.messages.multi_error); | |||
return; | |||
} | |||
// Create a form and submit it | |||
if (!commitForm) { | |||
var formContainer = make ('div'); | |||
formContainer.style.display = 'none'; | |||
document.body.appendChild (formContainer); | |||
formContainer.innerHTML = | |||
'<form method="post" enctype="multipart/form-data" action="' | |||
+ wgScript + '?title=' + encodeURIComponent (wgPageName) | |||
+ '&action=edit">' | |||
+ '<input type="hidden" name="wpTextbox1" />' | |||
+ '<input type="hidden" name="wpSummary" value="" />' | |||
+ '<input type="checkbox" name="wpMinoredit" value="1" />' | |||
+ '<input type="checkbox" name="wpWatchthis" value="1" />' | |||
+ '<input type="hidden" name="wpEdittime" />' | |||
+ '<input type="hidden" name="wpStarttime" />' | |||
+ '<input type="hidden" name="wpDiff" value="wpDiff" />' | |||
+ '</form>'; | |||
commitForm = formContainer.firstChild; | |||
} | |||
var result = { text : pageText }; | |||
var changed = [], added = [], deleted = [], changes = 0; | |||
for (var i=0; i < editors.length; i++) { | |||
if (editors[i].state == CategoryEditor.CHANGED) { | |||
result = change_category ( | |||
result.text | |||
, editors[i].originalCategory | |||
, editors[i].currentCategory | |||
, editors[i].currentKey | |||
); | |||
if (!result.error) { | |||
changes++; | |||
if (!editors[i].originalCategory || editors[i].originalCategory.length == 0) { | |||
added.push (editors[i].currentCategory); | |||
} else { | |||
changed.push ({from : editors[i].originalCategory, to : editors[i].currentCategory}); | |||
} | |||
} | |||
} else if (editors[i].state == CategoryEditor.DELETED) { | |||
result = change_category (result.text, editors[i].originalCategory, null, null); | |||
if (!result.error) { | |||
changes++; | |||
deleted.push (editors[i].originalCategory); | |||
} | |||
} | |||
} | |||
// Fill in the form and submit it | |||
commitForm.wpMinoredit.checked = minorEdits; | |||
commitForm.wpWatchthis.checked = wgArticleId == 0 && watchCreate || watchEdit; | |||
if (wgArticleId > 0) { | |||
if (changes == 1) { | |||
if (result.summary && result.summary.length > 0) | |||
commitForm.wpSummary.value = result.summary.join ('; ') + HotCat.messages.using; | |||
commitForm.wpMinoredit.checked = true; | |||
} else if (changes > 1) { | |||
var summary = []; | |||
if (deleted.length == 1) | |||
summary.push ('-[[' + HotCat.category_canonical + ':' + deleted[0] + ']]'); | |||
else if (deleted.length > 1) | |||
summary.push ('- ' + HotCat.messages.multi_change.replace ('$1', "" + deleted.length)); | |||
if (added.length == 1) | |||
summary.push ('+[[' + HotCat.category_canonical + ':' + added[0] + ']]'); | |||
else if (added.length > 1) | |||
summary.push ('+ ' + HotCat.messages.multi_change.replace ('$1', "" + added.length)); | |||
if (changed.length == 1) { | |||
if (changed[0].from != changed[0].to) { | |||
summary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + ']]→[[' | |||
+ HotCat.category_canonical + ':' + changed[0].to + ']]'); | |||
} else { | |||
summary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + ']]'); | |||
} | |||
} else if (changed.length > 1) { | |||
summary.push ('± ' + HotCat.messages.multi_change.replace ('$1', "" + changed.length)); | |||
} | |||
if (summary.length > 0) | |||
commitForm.wpSummary.value = summary.join ('; ') + HotCat.messages.using; | |||
} | |||
} | |||
commitForm.wpTextbox1.value = result.text; | |||
commitForm.wpStarttime.value = currentTimestamp (); | |||
commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value; | |||
commitForm.submit(); | |||
} | |||
function resolveMulti (toResolve, callback) { | |||
for (var i = 0; i < toResolve.length; i++) { | |||
toResolve[i].dab = null; | |||
toResolve[i].dabInput = toResolve[i].lastInput; | |||
} | |||
if (noSuggestions) { | |||
callback (toResolve); | |||
return; | |||
} | |||
var request = sajax_init_object (); | |||
if (!request) { | |||
noSuggestions = true; | |||
callback (toResolve); | |||
return; | |||
} | |||
var url = wgServer + wgScriptPath + '/api.php'; | |||
// Use %7C instead of |, otherwise Konqueror insists on re-encoding the arguments, resulting in doubly encoded | |||
// category names. (That is a bug in Konqueror. Other browsers don't have this problem.) | |||
var args = 'action=query&prop=info%7Clinks%7Ccategories&plnamespace=14&pllimit=50' | |||
+ '&cllimit=' + (toResolve.length * 10) // Category limit is global, link limit is per page | |||
+ '&format=json&titles='; | |||
for (var i = 0; i < toResolve.length; i++) { | |||
args += encodeURIComponent ('Category:' + toResolve[i].dabInput); | |||
if (i+1 < toResolve.length) args += '%7C'; | |||
} | |||
if (url.length + args.length + 1 > 2000) { // Lowest common denominator: IE has a URI length limit of 2083 | |||
request.open ('POST', url, true); | |||
request.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded'); | |||
} else { | |||
url += '?' + args; args = null; | |||
request.open ('GET', url, true); | |||
} | |||
request.onreadystatechange = | |||
function () { | |||
if (request.readyState != 4) return; | |||
if (request.status != 200) { | |||
callback (toResolve); | |||
return; | |||
} | |||
resolveRedirects (toResolve, eval ('(' + request.responseText + ')')); | |||
callback (toResolve); | |||
}; | |||
request.setRequestHeader ('Pragma', 'cache=yes'); | |||
request.setRequestHeader ('Cache-Control', 'no-transform'); | |||
request.send (args); | |||
} | |||
function resolveOne (page, toResolve) { | |||
var cats = page.categories; | |||
var lks = page.links; | |||
var is_dab = false; | |||
var is_redir = typeof (page.redirect) == 'string'; // Hard redirect? | |||
if (!is_redir && cats && (HotCat.disambig_category || HotCat.redir_category)) { | |||
for (var c = 0; c < cats.length; c++) { | |||
var cat = cats[c]['title']; | |||
// Strip namespace prefix | |||
if (cat) { | |||
cat = cat.substring (cat.indexOf (':') + 1).replace(/_/g, ' '); | |||
if (cat == HotCat.disambig_category) { | |||
is_dab = true; break; | |||
} else if (cat == HotCat.redir_category) { | |||
is_redir = true; break; | |||
} | |||
} | |||
} | |||
} | |||
if (!is_redir && !is_dab) return; | |||
if (!lks || lks.length == 0) return; | |||
var titles = []; | |||
for (var i = 0; i < lks.length; i++) { | |||
if ( lks[i]['ns'] == 14 // Category namespace | |||
&& lks[i]['title'] && lks[i]['title'].length > 0) // Name not empty | |||
{ | |||
// Internal link to existing thingy. Extract the page name and remove the namespace. | |||
var match = lks[i]['title']; | |||
titles.push (match.substring (match.indexOf (':') + 1)); | |||
if (is_redir) break; | |||
} | |||
} | |||
for (var j = 0; j < toResolve.length; j++) { | |||
if (toResolve[j].dabInput != page.title.substring (page.title.indexOf (':') + 1)) continue; | |||
if (titles.length > 1) { | |||
toResolve[j].dab = titles; | |||
} else { | |||
toResolve[j].inputExists = true; // Might actually be wrong... | |||
toResolve[j].icon.src = HotCat.existsYes; | |||
toResolve[j].text.value = | |||
titles[0] + (toResolve[j].currentKey != null ? '|' + toResolve[j].currentKey : ""); | |||
} | |||
} | |||
} | |||
function resolveRedirects (toResolve, params) { | |||
if (!params || !params.query || !params.query.pages) return; | |||
for (var p in params.query.pages) resolveOne (params.query.pages[p], toResolve); | |||
} | |||
function multiSubmit () { | |||
var toResolve = []; | |||
for (var i = 0; i < editors.length; i++) { | |||
if (editors[i].state == CategoryEditor.CHANGE_PENDING || editors[i].state == CategoryEditor.OPEN) | |||
toResolve.push (editors[i]); | |||
} | |||
if (toResolve.length == 0) { | |||
performChanges (); | |||
return; | |||
} | |||
resolveMulti ( | |||
toResolve | |||
, function (resolved) { | |||
var firstDab = null; | |||
var dontChange = false; | |||
for (var i = 0; i < resolved.length; i++) { | |||
if (resolved[i].lastInput != resolved[i].dabInput) { | |||
// We didn't disable all the open editors, but we did asynchronous calls. It is | |||
// theoretically possible that the user changed something... | |||
dontChange = true; | |||
} else { | |||
if (resolved[i].dab) { | |||
if (!firstDab) firstDab = resolved[i]; | |||
} else { | |||
if (resolved[i].acceptCheck(true)) resolved[i].commit(); | |||
} | |||
} | |||
} | |||
if (firstDab) { | |||
CategoryEditor.makeActive (firstDab); | |||
} else if (!dontChange) { | |||
performChanges (); | |||
} | |||
} | |||
); | |||
} | |||
var noSuggestions = false; | |||
var suggestionEngines = { | |||
opensearch : | |||
{ uri : '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=$1' // $1 = search term | |||
,handler : // Function to convert result of uri into an array of category names | |||
function (responseText, queryKey) { | |||
if (responseText.charAt (0) != '[') return null; | |||
var queryResult = eval ('(' + responseText + ')'); | |||
if (queryResult != null && queryResult.length == 2 && queryResult[0] == queryKey) { | |||
var titles = queryResult[1]; | |||
for (var i = 0; i < titles.length; i++) { | |||
titles[i] = titles[i].substring (titles[i].indexOf (':') + 1); // rm namespace | |||
} | |||
return titles; | |||
} | |||
return null; | |||
} | |||
} | |||
,internalsearch : | |||
{ uri : '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1' | |||
,handler : | |||
function (responseText, queryKey) { | |||
if (responseText.charAt (0) != '{') return null; | |||
var queryResult = eval ('(' + responseText + ')'); | |||
if (queryResult && queryResult.query && queryResult.query.allpages) { | |||
var titles = queryResult.query.allpages; | |||
for (var i = 0; i < titles.length; i++) { | |||
titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace | |||
if (titles[i].indexOf (queryKey) != 0) { | |||
titles.splice (i, 1); // Doesn't start with the query key | |||
i--; | |||
} | |||
} | |||
return titles; | |||
} | |||
return null; | |||
} | |||
} | |||
}; | |||
// Localize the names here. | |||
var suggestionConfigs = { | |||
searchindex : {name: 'Search index', engines: ['opensearch'], cache: {}} | |||
,pagelist : {name: 'Page list', engines: ['internalsearch'], cache: {}} | |||
,combined : {name: 'Combined search', engines: ['opensearch', 'internalsearch'], cache: {}} | |||
}; | |||
function CategoryEditor () { this.initialize.apply (this, arguments); }; | |||
CategoryEditor.UNCHANGED = 0; | |||
CategoryEditor.OPEN = 1; // Open, but no input yet | |||
CategoryEditor.CHANGE_PENDING = 2; // Open, some input made | |||
CategoryEditor.CHANGED = 3; | |||
CategoryEditor.DELETED = 4; | |||
CategoryEditor.makeActive = function (toActivate) { | |||
for (var i = 0; i < editors.length; i++) { | |||
if (editors[i] != toActivate) editors[i].inactivate (); | |||
} | |||
toActivate.is_active = true; | |||
if (toActivate.dab) { | |||
toActivate.showSuggestions (toActivate.dab, false, null, true); // do autocompletion, no key, no engine selector | |||
toActivate.dab = null; | |||
} | |||
}; | |||
CategoryEditor.prototype = { | |||
initialize : function (line, span, after, key) { | |||
// If a span is given, 'after' is the category title, otherwise it may be an element after which to | |||
// insert the new span. 'key' is likewise overloaded; if a span is given, it is the category key (if | |||
// known), otherwise it is a boolean indicating whether a bar shall be prepended. | |||
if (!span) { | |||
this.isAddCategory = true; | |||
// Create add span and append to catLinks | |||
this.originalCategory = ""; | |||
this.originalKey = null; | |||
this.originalExists = false; | |||
span = make ('span'); | |||
span.className = 'noprint'; | |||
if (key) { | |||
span.appendChild (make (' | ', true)); | |||
if (after) { | |||
after.parentNode.insertBefore (span, after.nextSibling); | |||
after = after.nextSibling; | |||
} else { | |||
line.appendChild (span); | |||
} | |||
} else if (line.firstChild) { | |||
span.appendChild (make (' ', true)); | |||
line.appendChild (span); | |||
} | |||
this.linkSpan = make ('span'); | |||
this.linkSpan.className = 'noprint'; | |||
var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this); | |||
lk.appendChild (make (HotCat.links.add, true)); | |||
this.linkSpan.appendChild (lk); | |||
span = make ('span'); | |||
span.className = 'noprint'; | |||
if (is_rtl) span.dir = 'rtl'; | |||
span.appendChild (this.linkSpan); | |||
if (after) | |||
after.parentNode.insertBefore (span, after.nextSibling); | |||
else | |||
line.appendChild (span); | |||
this.normalLinks = null; | |||
this.undelLink = null; | |||
this.catLink = null; | |||
} else { | |||
if (is_rtl) span.dir = 'rtl'; | |||
this.isAddCategory = false; | |||
this.catLink = span.firstChild; | |||
this.originalCategory = after; | |||
this.originalKey = (key && key.length > 1) ? key.substr(1) : null; // > 1 because it includes the leading bar | |||
this.originalExists = !hasClass (this.catLink, 'new'); | |||
// Create change and del links | |||
this.makeLinkSpan (); | |||
span.appendChild (this.linkSpan); | |||
} | |||
this.line = line; | |||
this.engine = HotCat.suggestions; | |||
this.span = span; | |||
this.currentCategory = this.originalCategory; | |||
this.currentExists = this.originalExists; | |||
this.currentKey = this.originalKey; | |||
this.state = CategoryEditor.UNCHANGED; | |||
this.lastSavedState = CategoryEditor.UNCHANGED; | |||
this.lastSavedCategory = this.originalCategory; | |||
this.lastSavedKey = this.originalKey; | |||
this.lastSavedExists = this.originalExists; | |||
editors[editors.length] = this; | |||
}, | |||
makeLinkSpan : function () { | |||
this.normalLinks = make ('span'); | |||
var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.remove, this); | |||
lk.appendChild (make (HotCat.links.remove, true)); | |||
this.normalLinks.appendChild (make (' ', true)); | |||
this.normalLinks.appendChild (lk); | |||
lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this); | |||
lk.appendChild (make (HotCat.links.change, true)); | |||
this.normalLinks.appendChild (make (' ', true)); | |||
this.normalLinks.appendChild (lk); | |||
this.linkSpan = make ('span'); | |||
this.linkSpan.className = 'noprint'; | |||
this.linkSpan.appendChild (this.normalLinks); | |||
this.undelLink = make ('span'); | |||
this.undelLink.style.display = 'none'; | |||
lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.restore, this); | |||
lk.appendChild (make (HotCat.links.restore, true)); | |||
this.undelLink.appendChild (make (' ', true)); | |||
this.undelLink.appendChild (lk); | |||
this.linkSpan.appendChild (this.undelLink); | |||
}, | |||
open : function (evt) { | |||
if (this.isAddCategory && !onUpload) { | |||
var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one | |||
} | |||
if (!commitButton && !onUpload) { | |||
for (var i = 0; i < editors.length; i++) { | |||
if (editors[i].state != CategoryEditor.UNCHANGED) { | |||
setMultiInput(); | |||
break; | |||
} | |||
} | |||
} | |||
if (!this.form) { | |||
var form = make ('form'); | |||
form.method = 'POST'; form.onsubmit = bind (this.accept, this); | |||
this.form = form; | |||
var text = make ('input'); text.type = 'text'; text.size = HotCat.editbox_width; | |||
if (!noSuggestions) { | |||
text.onkeyup = | |||
bind ( | |||
function (evt) { | |||
evt = evt || window.event || window.Event; // W3C, IE, Netscape | |||
var key = evt.keyCode || 0; | |||
if (key == 38 || key == 40) { | |||
// In case a browser doesn't generate keypress events for arrow keys... | |||
if (this.keyCount == 0) return this.processKey (evt); | |||
} else { | |||
if (key == 27) this.resetKeySelection (); | |||
// Also do this for ESC as a workaround for Firefox bug 524360 | |||
// https://bugzilla.mozilla.org/show_bug.cgi?id=524360 | |||
var dont_autocomplete = (key == 8 || key == 46 || key == 27); // BS, DEL, ESC | |||
this.state = CategoryEditor.CHANGE_PENDING; | |||
var self = this; | |||
window.setTimeout (function () {self.textchange (dont_autocomplete);}, HotCat.suggest_delay); | |||
} | |||
} | |||
,this | |||
); | |||
text.onkeydown = | |||
bind ( | |||
function (evt) { | |||
evt = evt || window.event || window.Event; // W3C, IE, Netscape | |||
this.lastKey = evt.keyCode || 0; | |||
this.keyCount = 0; | |||
// Handle return explicitly, to override the default form submission to be able to check for ctrl | |||
if (evt.keyCode == 13) this.accept (evt); | |||
} | |||
,this | |||
); | |||
// And handle continued pressing of arrow keys | |||
text.onkeypress = bind (function (evt) {this.keyCount++; return this.processKey (evt);}, this); | |||
} | |||
text.onfocus = bind (function () { CategoryEditor.makeActive (this); }, this); | |||
this.text = text; | |||
this.icon = make ('img'); | |||
var list = null; | |||
if (!noSuggestions) { | |||
list = make ('select'); | |||
list.onclick = bind (function () { if (this.setValueFromList ()) this.textchange (); }, this); | |||
list.ondblclick = bind (function (e) { if (this.setValueFromList ()) this.accept (e); }, this); | |||
list.onchange = bind (function (e) { this.setValueFromList (); this.text.focus(); }, this); | |||
list.onkeyup = | |||
bind ( | |||
function (evt) { | |||
evt = evt || window.event || window.Event; // W3C, IE, Netscape | |||
if (evt.keyCode == 27) { | |||
this.resetKeySelection (); | |||
this.text.focus(); | |||
var self = this; | |||
window.setTimeout (function () {self.textchange (true);}, HotCat.suggest_delay); | |||
} else if (evt.keyCode == 13) { | |||
this.accept (evt); | |||
} | |||
} | |||
,this | |||
); | |||
if (!HotCat.fixed_search) { | |||
var engineSelector = make ('select'); | |||
for (var key in suggestionConfigs) { | |||
var opt = make ('option'); | |||
opt.value = key; | |||
if (key == this.engine) opt.selected = true; | |||
opt.appendChild (make (suggestionConfigs[key].name, true)); | |||
engineSelector.appendChild (opt); | |||
} | |||
engineSelector.onchange = bind ( | |||
function () { | |||
this.engine = this.engineSelector.options[this.engineSelector.selectedIndex].value; | |||
this.textchange (true, true); // Don't autocomplete, force re-display of list | |||
} | |||
,this | |||
); | |||
this.engineSelector = engineSelector; | |||
} | |||
} | |||
this.list = list; | |||
function button_label (id, defaultText) { | |||
var label = null; | |||
if ( onUpload | |||
&& typeof (UFUI) != 'undefined' | |||
&& typeof (UIElements) != 'undefined' | |||
&& typeof (UFUI.getLabel) == 'function') { | |||
try { | |||
label = UFUI.getLabel (id, true); | |||
// Extract the plain text. IE doesn't know that Node.TEXT_NODE == 3 | |||
while (label && label.nodeType != 3) label = label.firstChild; | |||
} catch (ex) { | |||
label = null; | |||
} | |||
} | |||
if (!label || !label.data) return defaultText; | |||
return label.data; | |||
} | |||
var OK = make ('input'); OK.type = 'submit'; | |||
OK.value = button_label ('wpOkUploadLbl', HotCat.messages.ok); | |||
this.ok = OK; | |||
var cancel = make ('input'); cancel.type = 'button'; | |||
cancel.value = button_label ('wpCancelUploadLbl', HotCat.messages.cancel); | |||
cancel.onclick = bind (this.cancel, this); | |||
this.cancelButton = cancel; | |||
if (list) form.appendChild (list); | |||
if (this.engineSelector) form.appendChild (this.engineSelector); | |||
form.appendChild (text); | |||
if (!noSuggestions) form.appendChild (this.icon); | |||
form.appendChild (OK); | |||
form.appendChild (cancel); | |||
form.style.display = 'none'; | |||
this.span.appendChild (form); | |||
} | |||
if (this.list) this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
this.currentCategroy = this.lastSavedCategory; | |||
this.currentExits = this.lastSavedExists; | |||
this.currentKey = this.lastSavedKey; | |||
this.icon.src = this.currentExists ? HotCat.existsYes : HotCat.existsNo; | |||
this.text.value = this.currentCategory + (this.currentKey ? '|' + this.currentKey : ""); | |||
this.originalState = this.state; | |||
this.lastInput = this.currentCategory; | |||
this.inputExists = this.currentExists; | |||
this.state = this.state == CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING; | |||
// Display the form | |||
if (this.catLink) this.catLink.style.display = 'none'; | |||
this.linkSpan.style.display = 'none'; | |||
this.form.style.display = 'inline'; | |||
CategoryEditor.makeActive (this); | |||
// Kill the event before focussing, otherwise IE will kill the onfocus event! | |||
var result = evtKill (evt); | |||
this.text.focus(); | |||
return result; | |||
}, | |||
cancel : function () { | |||
if (this.isAddCategory && !onUpload) { | |||
this.removeEditor(); // We added a new adder when opening | |||
return; | |||
} | |||
// Close, re-display link | |||
if (this.list) this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
this.form.style.display = 'none'; | |||
if (this.catLink) this.catLink.style.display = ""; | |||
this.linkSpan.style.display = ""; | |||
this.state = this.originalState; | |||
this.currentCategory = this.lastSavedCategory; | |||
this.currentKey = this.lastSavedKey; | |||
this.currentExists = this.lastSavedExists; | |||
}, | |||
removeEditor : function () { | |||
var next = this.span.nextSibling; | |||
if (next) next.parentNode.removeChild (next); | |||
this.span.parentNode.removeChild (this.span); | |||
for (var i = 0; i < editors.length; i++) { | |||
if (editors[i] == this) { | |||
editors.splice (i, 1); | |||
break; | |||
} | |||
} | |||
var self = this; | |||
window.setTimeout (function () {delete self;}, 10); | |||
}, | |||
rollback : function (evt) { | |||
this.undoLink.parentNode.removeChild (this.undoLink); | |||
this.undoLink = null; | |||
this.currentCategory = this.originalCategory; | |||
this.currentKey = this.originalKey; | |||
this.currentExists = this.originalExists; | |||
this.lastSavedCategory = this.originalCategory; | |||
this.lastSavedKey = this.originalKey; | |||
this.lastSavedExists = this.originalExists; | |||
this.state = CategoryEditor.UNCHANGED; | |||
if (!this.currentCategory || this.currentCategory.length == 0) { | |||
// It was a newly added category. Remove the whole editor. | |||
this.removeEditor(); | |||
} else { | |||
// Redisplay the link... | |||
this.catLink.removeChild (this.catLink.firstChild); | |||
this.catLink.appendChild (make (this.currentCategory, true)); | |||
this.catLink.href = wgArticlePath.replace ('$1', 'Category:' + this.currentCategory); | |||
this.catLink.title = ""; | |||
this.catLink.className = this.currentExists ? "" : 'new'; | |||
} | |||
return evtKill (evt); | |||
}, | |||
inactivate : function () { | |||
if (this.list) this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
this.is_active = false; | |||
}, | |||
acceptCheck : function (dontCheck) { | |||
this.sanitizeInput (); | |||
var value = this.text.value.split('|'); | |||
var key = null; | |||
if (value.length > 1) key = value[1]; | |||
var v = capitalize (value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, "")); | |||
this.lastInput = v; | |||
if (v.length == 0) { | |||
this.cancel (); | |||
return false; | |||
} | |||
if ( !dontCheck | |||
&& ( ( v == this.lastSavedCategory | |||
&& ( key == this.lastSavedKey | |||
|| key === null && this.lastSavedKey.length == 0 | |||
|| this.lastSavedKey === null && key.length == 0 | |||
) | |||
) | |||
|| wgNamespaceNumber == 14 && v == wgTitle | |||
) | |||
) | |||
{ | |||
this.cancel (); | |||
return false; | |||
} | |||
this.currentCategory = v; | |||
this.currentKey = key; | |||
this.currentExists = this.inputExists; | |||
return true; | |||
}, | |||
accept : function (evt) { | |||
this.noCommit = (evtKeys (evt) & 1) != 0; | |||
var result = evtKill (evt); | |||
if (this.acceptCheck ()) { | |||
var toResolve = [this]; | |||
var original = this.currentCategory; | |||
resolveMulti ( | |||
toResolve | |||
, function (resolved) { | |||
if (resolved[0].dab) { | |||
CategoryEditor.makeActive (resolved[0]); | |||
} else { | |||
if (resolved[0].acceptCheck(true)) { | |||
resolved[0].commit ( | |||
(resolved[0].currentCategory != original) | |||
? HotCat.messages.cat_resolved.replace ('$1', original) | |||
: null | |||
); | |||
} | |||
} | |||
} | |||
); | |||
} | |||
return result; | |||
}, | |||
close : function () { | |||
if (!this.catLink) { | |||
// Create a catLink | |||
this.catLink = make ('a'); | |||
this.catLink.appendChild (make ('foo', true)); | |||
this.catLink.style.display = 'none'; | |||
this.span.insertBefore (this.catLink, this.span.firstChild.nextSibling); | |||
} | |||
this.catLink.removeChild (this.catLink.firstChild); | |||
this.catLink.appendChild (make (this.currentCategory, true)); | |||
this.catLink.href = wgArticlePath.replace ('$1', 'Category:' + this.currentCategory); | |||
this.catLink.title = ""; | |||
this.catLink.className = this.currentExists ? "" : 'new'; | |||
this.lastSavedCategory = this.currentCategory; | |||
this.lastSavedKey = this.currentKey; | |||
this.lastSavedExists = this.currentExists; | |||
// Close form and redisplay category | |||
if (this.list) this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
this.form.style.display = 'none'; | |||
this.catLink.style.display = ""; | |||
if (this.isAddCategory) { | |||
if (onUpload) { | |||
var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one | |||
} | |||
this.isAddCategory = false; | |||
this.linkSpan.parentNode.removeChild (this.linkSpan); | |||
this.makeLinkSpan (); | |||
this.span.appendChild (this.linkSpan); | |||
} | |||
if (!this.undoLink && !onUpload) { | |||
// Append an undo link. | |||
var span = make ('span'); | |||
var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.rollback, this); | |||
lk.appendChild (make (HotCat.links.undo, true)); | |||
span.appendChild (make (' ', true)); | |||
span.appendChild (lk); | |||
this.normalLinks.appendChild (span); | |||
this.undoLink = span; | |||
} | |||
this.linkSpan.style.display = ""; | |||
this.state = CategoryEditor.CHANGED; | |||
}, | |||
commit : function (comment) { | |||
// Check again to catch problem cases after redirect resolution | |||
if ( ( this.currentCategory == this.originalCategory | |||
&& (this.currentKey == this.originalKey | |||
|| this.currentKey === null && this.originalKey.length == 0 | |||
|| this.originalKey === null && this.currentKey.length == 0 | |||
) | |||
) | |||
|| wgNamespaceNumber == 14 && this.currentCategory == wgTitle | |||
) | |||
{ | |||
this.cancel (); | |||
return; | |||
} | |||
if (commitButton || onUpload) { | |||
this.close (); | |||
} else { | |||
if (this.list) this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
// Execute change from this.originalCategory to this.currentCategory|this.currentKey, | |||
var editlk = wgServer + wgScript + '?title=' + encodeURIComponent (wgPageName) | |||
+ '&action=edit'; | |||
var url = editlk + '&hotcat_newcat=' + encodeURIComponent (this.currentCategory); | |||
if (this.currentKey) url += '&hotcat_sortkey=' + encodeURIComponent (this.currentKey); | |||
if (this.originalCategory.length > 0) | |||
url += '&hotcat_removecat=' + encodeURIComponent (this.originalCategory); | |||
if (comment) url = url + '&hotcat_comment=' + encodeURIComponent (comment); | |||
if (this.noCommit || HotCat.no_autocommit) url = url + '&hotcat_nocommit=1'; | |||
document.location = url; | |||
} | |||
}, | |||
remove : function (evt) { | |||
this.doRemove (evtKeys (evt) & 1); | |||
return evtKill (evt); | |||
}, | |||
doRemove : function (noCommit) { | |||
if (this.isAddCategory) { // Empty input on adding a new category | |||
this.cancel (); | |||
return; | |||
} | |||
if (!commitButton && !onUpload) { | |||
for (var i = 0; i < editors.length; i++) { | |||
if (editors[i].state != CategoryEditor.UNCHANGED) { | |||
setMultiInput(); | |||
break; | |||
} | |||
} | |||
} | |||
if (commitButton) { | |||
this.catLink.style.textDecoration = 'line-through'; | |||
this.originalState = this.state; | |||
this.state = CategoryEditor.DELETED; | |||
this.normalLinks.style.display = 'none'; | |||
this.undelLink.style.display = ""; | |||
} else { | |||
if (onUpload) { | |||
// Remove this editor completely | |||
this.removeEditor (); | |||
} else { | |||
// Execute single category deletion. | |||
var editlk = wgServer + wgScript + '?title=' + encodeURIComponent (wgPageName) | |||
+ '&action=edit'; | |||
if (noCommit || HotCat.no_autocommit) editlk += '&hotcat_nocommit=1'; | |||
document.location = | |||
editlk + '&hotcat_removecat=' + encodeURIComponent (this.originalCategory); | |||
} | |||
} | |||
}, | |||
restore : function (evt) { | |||
// Can occur only if we do have a commit button and are not on the upload form | |||
this.catLink.style.textDecoration = ""; | |||
this.state = this.originalState; | |||
this.normalLinks.style.display = ""; | |||
this.undelLink.style.display = 'none'; | |||
return evtKill (evt); | |||
}, | |||
// Internal operations | |||
setValueFromList : function (idx) { | |||
if (typeof (idx) == 'undefined') idx = this.list.selectedIndex; | |||
if (idx >= 0 && idx < this.list.options.length) { | |||
var v = this.text.value.split ('|'); | |||
this.text.value = this.list.options[idx].text + (v.length > 1 ? '|' + v[1] : ""); | |||
this.inputExists = true; // Might be wrong if from a dab list... | |||
if (this.icon) this.icon.src = HotCat.existsYes; | |||
return true; | |||
} | |||
return false; | |||
}, | |||
sanitizeInput : function () { | |||
var v = this.text.value || ""; | |||
v = v.replace(/^(\s|_)+/, ""); // Trim leading blanks and underscores | |||
var re = new RegExp ('^(' + HotCat.category_regexp + '):'); | |||
if (re.test (v)) v = v.substring (v.indexOf (':') + 1); | |||
v = capitalize (v); | |||
// Only update the input field if there is a difference. IE8 appears to reset the selection | |||
// and place the cursor at the front upon reset, which makes our autocompletetion become a | |||
// nuisance. FF and IE6 don't seem to have this problem. | |||
if (this.text.value != null && this.text.value != v) | |||
this.text.value = v; | |||
}, | |||
makeCall : function (url, callbackObj, engine, queryKey) { | |||
var cb = callbackObj; | |||
var e = engine; | |||
var v = queryKey; | |||
var r = sajax_init_object (); | |||
cb.requests.push (r); | |||
r.open('GET', url, true); | |||
r.onreadystatechange = | |||
bind ( | |||
function () { | |||
if (r.readyState == 4) { | |||
if (r.status != 200) cb.dontCache = true; | |||
if (r.status == 200 && r.responseText != null) { | |||
var titles = e.handler (r.responseText, v); | |||
if (titles && titles.length > 0) { | |||
if (cb.allTitles == null) { | |||
cb.allTitles = titles; | |||
} else { | |||
cb.allTitles = cb.allTitles.concat (titles); | |||
} | |||
} | |||
} | |||
cb.callsMade++; | |||
} | |||
if (cb.callsMade == cb.nofCalls) { | |||
if (!cb.dontCache && !suggestionConfigs[cb.engineName].cache[v]) { | |||
suggestionConfigs[cb.engineName].cache[v] = cb.allTitles; | |||
} | |||
if (!cb.cancelled) this.showSuggestions (cb.allTitles, cb.noCompletion, v); | |||
if (cb === this.callbackObj) this.callbackObj = null; | |||
delete cb; | |||
} | |||
} | |||
,this | |||
); | |||
r.setRequestHeader ('Pragma', 'cache=yes'); | |||
r.setRequestHeader ('Cache-Control', 'no-transform'); | |||
r.send (null); | |||
}, | |||
callbackObj : null, | |||
textchange : function (dont_autocomplete, force) { | |||
// Hide all other lists | |||
CategoryEditor.makeActive (this); | |||
if (noSuggestions) { | |||
// No Ajax: just make sure the list is hidden | |||
if (this.list) this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
if (this.icon) this.icon.style.display = 'none'; | |||
return; | |||
} | |||
// Get input value, omit sort key, if any | |||
this.sanitizeInput (); | |||
var v = this.text.value; | |||
// Disregard anything after a pipe. | |||
var pipe = v.indexOf ('|'); | |||
if (pipe >= 0) v = v.substring (0, pipe); | |||
if (this.lastInput == v && !force) return; // No change | |||
this.lastInput = v; | |||
this.lastRealInput = v; | |||
if (v.length == 0) { this.showSuggestions([]); return; } | |||
if (!sajax_init_object ()) { | |||
noSuggestions = true; | |||
if (this.list) this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
if (this.icon) this.icon.style.display = 'none'; | |||
return; | |||
} | |||
if (this.callbackObj) this.callbackObj.cancelled = true; | |||
var engine = suggestionConfigs[this.engine] ? this.engine : 'combined'; | |||
if (suggestionConfigs[engine].cache[v]) { | |||
this.showSuggestions (suggestionConfigs[engine].cache[v], dont_autocomplete, v); | |||
return; | |||
} | |||
var engines = suggestionConfigs[engine].engines; | |||
this.callbackObj = | |||
{allTitles: null, requests: [], callsMade: 0, nofCalls: engines.length, noCompletion: dont_autocomplete, engineName: engine}; | |||
for (var j = 0; j < engines.length; j++) { | |||
engine = suggestionEngines[engines[j]]; | |||
var url = wgServer + wgScriptPath + engine.uri.replace (/\$1/g, encodeURIComponent (v)); | |||
this.makeCall (url, this.callbackObj, engine, v); | |||
} | |||
}, | |||
showSuggestions : function (titles, dontAutocomplete, queryKey, noEngine) { | |||
this.dab = null; | |||
if (!this.list) return; | |||
if (noSuggestions) { | |||
if (this.list) this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
if (this.icon) this.icon.style.display = 'none'; | |||
this.inputExists = true; // Default... | |||
return; | |||
} | |||
if (!noEngine) { | |||
noEngine = this.engineSelector == null; | |||
} else { | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
} | |||
if (queryKey) { | |||
if (this.lastInput.indexOf (queryKey) != 0) return; | |||
if (this.lastQuery && this.lastInput.indexOf (this.lastQuery) == 0 && this.lastQuery.length > queryKey.length) | |||
return; | |||
} | |||
this.lastQuery = queryKey; | |||
if (!titles || titles.length == 0) { | |||
if (this.list) this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
if (this.icon) this.icon.src = HotCat.existsNo; | |||
this.inputExists = false; | |||
return; | |||
} | |||
// Get current input text | |||
var v = this.text.value.split('|'); | |||
var key = v.length > 1 ? '|' + v[1] : ""; | |||
v = capitalize (v[0]); | |||
titles.sort ( | |||
function (a, b) { | |||
if (a.indexOf (b) == 0) return 1; // a begins with b: a > b | |||
if (b.indexOf (a) == 0) return -1; // b begins with a: a < b | |||
// Opensearch may return stuff not beginning with the search prefix! | |||
var prefixMatchA = (a.indexOf (v) == 0 ? 1 : 0); | |||
var prefixMatchB = (b.indexOf (v) == 0 ? 1 : 0); | |||
if (prefixMatchA != prefixMatchB) return prefixMatchB - prefixMatchA; | |||
if (a < b) return -1; | |||
if (b < a) return 1; | |||
return 0; | |||
} | |||
); | |||
// Remove duplicates | |||
for (var i = 0; i+1 < titles.length; i++) { | |||
if (titles[i] == titles[i+1]) { | |||
titles.splice (i+1, 1); | |||
i--; | |||
} | |||
} | |||
var firstTitle = titles[0]; | |||
var completed = this.autoComplete (firstTitle, v, key, dontAutocomplete); | |||
this.icon.src = completed ? HotCat.existsYes : HotCat.existsNo; | |||
this.inputExists = completed; | |||
if (completed) { | |||
this.lastInput = firstTitle; | |||
if (titles.length == 1) { | |||
this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
return; | |||
} | |||
} | |||
if (!this.is_active) { | |||
this.list.style.display = 'none'; | |||
if (this.engineSelector) this.engineSelector.style.display = 'none'; | |||
return; | |||
} | |||
var nofItems = (titles.length > 5 ? 5 : titles.length); | |||
if (nofItems <= 1) nofItems = 2; | |||
this.list.size = nofItems; | |||
this.list.style.align = 'left'; | |||
this.list.style.zIndex = 5; | |||
this.list.style.position = 'absolute'; | |||
// Compute initial list position. First the height. | |||
var listh = 0; | |||
if (this.list.style.display == 'none') { | |||
// Off-screen display to get the height | |||
this.list.style.top = this.text.offsetTop + 'px'; | |||
this.list.style.left = '-10000px'; | |||
this.list.style.display = ""; | |||
listh = this.list.offsetHeight; | |||
this.list.style.display = 'none'; | |||
} else { | |||
listh = this.list.offsetHeight; | |||
} | |||
// Approximate calculation of maximum list size | |||
var maxListHeight = listh; | |||
if (nofItems < 5) maxListHeight = (listh / nofItems) * 5; | |||
function scroll_offset (what) { | |||
var s = 'scroll' + what; | |||
return (document.documentElement ? document.documentElement[s] : 0) | |||
|| document.body[s] || 0; | |||
} | |||
function viewport (what) { | |||
if (typeof (is_safari) != 'undefined' && is_safari && !document.evaluate) | |||
return window['inner' + what]; | |||
var s = 'client' + what; | |||
if (typeof (is_opera) != 'undefined' && is_opera) return document.body[s]; | |||
return (document.documentElement ? document.documentElement[s] : 0) | |||
|| document.body[s] || 0; | |||
} | |||
function position (node) { | |||
// Stripped-down simplified position function. It's good enough for our purposes. | |||
if (node.getBoundingClientRect) { | |||
var box = node.getBoundingClientRect (); | |||
return { x : Math.round (box.left + scroll_offset ('Left')) | |||
,y : Math.round (box.top + scroll_offset ('Top')) | |||
}; | |||
} | |||
var t = 0, l = 0; | |||
do { | |||
t = t + (node.offsetTop || 0); | |||
l = l + (node.offsetLeft || 0); | |||
node = node.offsetParent; | |||
} while (node); | |||
return {x : l, y : t}; | |||
} | |||
// IE6 seems to report in this.text.offsetTop and this.text.offsetLeft global offsets?? | |||
// Possibly this has something to do with the special status of input elements in IE as | |||
// "windowed controls". Calculate the relative offsets manually. | |||
var textPos = position (this.text); | |||
var catLinePos = position (this.line); | |||
var textTop = textPos.y - catLinePos.y; | |||
var textLeft = textPos.x - catLinePos.x; | |||
if (window.ie6_bugs) { | |||
// IE6 somehow has a problem with inline-displayed forms (to which our list belongs), and will add the | |||
// offset of the beginning of the text to the offsets we'd normally calculate, which in particular with | |||
// right-aligned category lines as they occur in some older skins completely misplaces the lists, sometimes | |||
// even off-screen. This appears to affect only the horizontal positioning of the list and of the | |||
// engineSelector. Try to account for this bizarre behavior. Notes: dunno if that also occurs on IE7. | |||
var textStartPos = position (this.line.firstChild); | |||
textStartPos.x -= catLinePos.x; | |||
textLeft -= textStartPos.x; | |||
} | |||
var nl = textLeft; | |||
var nt = 0; | |||
var offset = 0; | |||
if (!noEngine) { | |||
this.engineSelector.style.zIndex = 5; | |||
this.engineSelector.style.position = 'absolute'; | |||
this.engineSelector.style.width = this.text.offsetWidth + 'px'; | |||
// Figure out the height of this selector: display it off-screen, then hide it again. | |||
if (this.engineSelector.style.display == 'none') { | |||
this.engineSelector.style.left = '-1000px'; | |||
this.engineSelector.style.top = textTop + 'px'; | |||
this.engineSelector.style.display = ""; | |||
offset = this.engineSelector.offsetHeight; | |||
this.engineSelector.style.display = 'none'; | |||
} else { | |||
offset = this.engineSelector.offsetHeight; | |||
} | |||
this.engineSelector.style.left = nl + 'px'; | |||
} | |||
if (textPos.y < maxListHeight + offset) { | |||
// The list might extend beyond the upper border of the page. Let's avoid that by placing it | |||
// below the input text field. | |||
nt = textTop + this.text.offsetHeight + offset + 1; | |||
if (!noEngine) this.engineSelector.style.top = textTop + this.text.offsetHeight + 'px'; | |||
} else { | |||
nt = textTop - listh - offset; | |||
if (!noEngine) this.engineSelector.style.top = textTop - offset + 'px'; | |||
} | |||
this.list.style.top = nt + 'px'; | |||
this.list.style.width = ""; // No fixed width (yet) | |||
this.list.style.left = nl + 'px'; | |||
// (Re-)fill the list | |||
while (this.list.firstChild) this.list.removeChild (this.list.firstChild); | |||
for (var i = 0 ; i < titles.length ; i++) { | |||
var opt = make ('option') ; | |||
opt.appendChild (make (titles[i], true)); | |||
this.list.appendChild (opt); | |||
} | |||
if (!noEngine) this.engineSelector.style.display = ""; | |||
this.list.style.display = 'block'; | |||
// Set the width of the list | |||
var scroll = scroll_offset ('Left'); | |||
var view_w = viewport ('Width'); | |||
var l_pos = position (this.list); | |||
if (this.list.offsetWidth < this.text.offsetWidth) { | |||
this.list.style.width = this.text.offsetWidth + 'px'; | |||
return; | |||
} | |||
// Make sure that the list fits horizontally into the browser window | |||
var w = this.list.offsetWidth; | |||
if (l_pos.x + w > scroll + view_w) { | |||
if (w > view_w) w = view_w; | |||
this.list.style.width = w + 'px'; | |||
this.list.style.left = nl - (l_pos.x + w - scroll - view_w) + 'px'; | |||
} | |||
}, | |||
autoComplete : function (newVal, actVal, key, dontModify) { | |||
if (newVal == actVal) return true; | |||
if (dontModify || newVal.indexOf (actVal) != 0) return false; | |||
// Actual input is a prefix of the new text. Fill in new text, selecting the newly added suffix | |||
// such that it can be easily removed by typing backspace if the suggestion is unwanted. | |||
if (!( this.text.setSelectionRange | |||
|| this.text.createTextRange | |||
|| typeof (this.text.selectionStart) != 'undefined' | |||
&& typeof (this.text.selectionEnd) != 'undefined' | |||
) | |||
) | |||
return false; | |||
// Here we know that we can indeed select properly. If we can't doing this would be a major | |||
// annoyance. | |||
this.text.focus(); | |||
var start = actVal.length; | |||
this.text.value = newVal + key; | |||
if (this.text.setSelectionRange) // e.g. khtml | |||
this.text.setSelectionRange (start, newVal.length); | |||
else if (this.text.createTextRange) { // IE | |||
var new_selection = this.text.createTextRange(); | |||
new_selection.move ('character', start); | |||
new_selection.moveEnd ('character', newVal.length - start); | |||
new_selection.select(); | |||
} else { | |||
this.text.selectionStart = start; | |||
this.text.selectionEnd = newVal.length; | |||
} | |||
return true; | |||
}, | |||
processKey : function (evt) { | |||
if (this.lastKey == 38 || this.lastKey == 40) { // Up and down arrows | |||
if (this.list.style.display != 'none') { | |||
// List is visible, so there are suggestions | |||
this.highlightSuggestion (this.lastKey == 38 ? -1 : 1); | |||
// Kill the event, otherwise some browsers (e.g., Firefox) may additionally treat an up-arrow as | |||
// "place the text cursor at the front", which we don't want here. | |||
return evtKill (evt); | |||
} else if ( this.keyCount <= 1 | |||
&& (!this.callbackObj || this.callbackObj.callsMade == this.callbackObj.nofCalls) | |||
) | |||
{ | |||
// If no suggestions displayed, get them, unless we're already getting them. | |||
this.textchange (); | |||
} | |||
} | |||
return true; | |||
}, | |||
highlightSuggestion : function (dir) { | |||
if (noSuggestions || !this.list || this.list.style.display == 'none') return; | |||
var curr = this.list.selectedIndex; | |||
var tgt = curr < 0 ? 0 : curr + dir; | |||
tgt = tgt < 0 ? 0 : tgt; | |||
if (tgt != curr && tgt < this.list.options.length) { | |||
if (curr >= 0 && curr < this.list.options.length) this.list.options[curr].selected = false; | |||
this.list.options[tgt].selected = true; | |||
// Get current input text | |||
var v = this.text.value.split('|'); | |||
var key = v.length > 1 ? '|' + v[1] : ""; | |||
var completed = this.autoComplete (this.list.options[tgt].text, this.lastInput, key, false); | |||
if (!completed) { | |||
this.text.value = this.list.options[tgt].text + key; | |||
} | |||
this.lastInput = this.list.options[tgt].text; | |||
} | |||
}, | |||
resetKeySelection : function () { | |||
if (noSuggestions || !this.list || this.list.style.display == 'none') return; | |||
var curr = this.list.selectedIndex; | |||
if (curr >= 0 && curr < this.list.options.length) { | |||
this.list.options[curr].selected = false; | |||
// Get current input text | |||
var v = this.text.value.split('|'); | |||
var key = v.length > 1 ? '|' + v[1] : ""; | |||
this.text.value = this.lastRealInput + key; | |||
this.lastInput = this.lastRealInput; | |||
} | |||
} | |||
}; // end CategoryEditor.prototype | |||
function initialize () { | |||
// User configurations. Do this here, called from the onload handler, so that users can | |||
// override it easily in their own user script files by just declaring variables. JSconfig | |||
// is some feature used at Wikimedia Commons. | |||
HotCat.no_autocommit = window.hotcat_no_autocommit | |||
|| typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatNoAutoCommit'] | |||
|| 0; | |||
HotCat.suggest_delay = window.hotcat_suggestion_delay | |||
|| typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatSuggestionDelay'] | |||
|| 100; | |||
HotCat.editbox_width = window.hotcat_editbox_width | |||
|| typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatEditBoxWidth'] | |||
|| 40; | |||
HotCat.suggestions = window.hotcat_suggestions | |||
|| typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatSuggestions'] | |||
|| 'combined'; | |||
if (typeof (HotCat.suggestions) != 'string' || !suggestionConfigs[HotCat.suggestions]) | |||
HotCat.suggestions = 'combined'; | |||
HotCat.fixed_search = window.hotcat_suggestions_fixed | |||
|| typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatFixedSuggestions'] | |||
|| false; | |||
// Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]] | |||
is_rtl = hasClass (document.body, 'rtl'); | |||
if (!is_rtl) { | |||
if (document.defaultView && document.defaultView.getComputedStyle) { // Gecko etc. | |||
is_rtl = document.defaultView.getComputedStyle (document.body, null).getPropertyValue ('direction'); | |||
} else if (document.body.currentStyle) { // IE, has subtle differences to getComputedStyle | |||
is_rtl = document.body.currentStyle['direction']; | |||
} else { // Not exactly right, but best effort | |||
is_rtl = document.body.style['direction']; | |||
} | |||
is_rtl = (is_rtl == 'rtl'); | |||
} | |||
} | |||
function can_edit () { | |||
var container = null; | |||
switch (skin) { | |||
case 'cologneblue': | |||
container = document.getElementById ('quickbar'); | |||
// Fall through | |||
case 'standard': | |||
case 'nostalgia': | |||
if (!container) container = document.getElementById ('topbar'); | |||
var lks = container.getElementsByTagName ('a'); | |||
for (var i = 0; i < lks.length; i++) { | |||
if ( param ('title', lks[i].href) == wgPageName | |||
&& param ('action', lks[i].href) == 'edit') | |||
return true; | |||
} | |||
return false; | |||
default: | |||
// all modern skins: | |||
return document.getElementById ('ca-edit') != null; | |||
} | |||
return false; | |||
} | |||
function setup_upload () { | |||
onUpload = true; | |||
// Add an empty category bar above the "watch this" box, and change the onsubmit handler. | |||
var ip = document.getElementById ('wpWatchthis'); | |||
if (!ip) return; | |||
var reupload = document.getElementById ('wpForReUpload'); | |||
var destFile = document.getElementById ('wpDestFile'); | |||
if ( (reupload && !!reupload.value) | |||
|| (destFile && (destFile.disabled || destFile.readonly))) | |||
return; // re-upload form... | |||
// Insert a table row with two fields (label and empty category bar) | |||
ip = ip.parentNode.parentNode; // The containing <tr> | |||
var newRow = make ('tr'); | |||
var labelCell = make ('td'); | |||
var lineCell = make ('td'); | |||
newRow.appendChild (labelCell); | |||
newRow.appendChild (lineCell); | |||
// Create the category line | |||
catLine = make ('div'); | |||
catLine.className = 'catlinks'; | |||
catLine.id = 'catlinks'; | |||
catLine.style.textAlign = 'left'; | |||
lineCell.appendChild (catLine); | |||
// Create the label | |||
var label = null; | |||
if (typeof (UFUI) != 'undefined' && | |||
typeof (UFUI.getLabel) == 'function') { | |||
try { | |||
label = UFUI.getLabel ('wpCategoriesUploadLbl'); | |||
} catch (ex) { | |||
label = null; | |||
} | |||
} | |||
if (!label) { | |||
labelCell.id = 'hotcatLabel'; | |||
labelCell.appendChild (make (HotCat.categories), true); | |||
} else { | |||
labelCell.id = 'hotcatLabelTranslated'; | |||
labelCell.appendChild (label); | |||
} | |||
labelCell.className = 'mw-label'; | |||
labelCell.style.textAlign = 'right'; | |||
labelCell.style.verticalAlign = 'middle'; | |||
// Change the onsubmit handler | |||
var form = document.getElementById ('upload') || document.getElementById ('mw-upload-form'); | |||
if (form) { | |||
var optionsTable = document.getElementById ('mw-htmlform-options'); | |||
if (optionsTable) optionsTable.width = '100%'; | |||
ip.parentNode.insertBefore (newRow, ip); | |||
textHeight = labelCell.firstChild.offsetHeight; | |||
form.onsubmit = (function (oldSubmit) { | |||
return function () { | |||
var do_submit = true; | |||
if (oldSubmit) { | |||
if (typeof (oldSubmit) == 'string') | |||
do_submit = eval (oldSubmit); | |||
else if (typeof (oldSubmit) == 'function') | |||
do_submit = oldSubmit.apply (form, arguments); | |||
} | |||
if (!do_submit) return false; | |||
closeForm (); | |||
// Copy the categories | |||
var eb = document.getElementById ('wpUploadDescription') | |||
|| document.getElementById ('wpDesc'); | |||
for (var i = 0; i < editors.length; i++) { | |||
var t = editors[i].currentCategory; | |||
if (!t) continue ; | |||
var key = editors[i].currentKey; | |||
var new_cat = '[[' + HotCat.category_canonical + ':' + t + (key ? '|' + key : "") + ']]'; | |||
// Only add if not already present | |||
var cleanedText = eb.value.replace(/<\!--(\s|\S)*?--\>/g, "") | |||
.replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, ""); | |||
if (!find_category (cleanedText, t, true)) { | |||
eb.value += '\n' + new_cat; | |||
} | |||
} | |||
return true; | |||
}; | |||
}) (form.onsubmit); | |||
} | |||
} | |||
var cleanedText = null; | |||
function isOnPage (span) { | |||
var catTitle = title (span.firstChild.getAttribute ('href', 2)); | |||
if (!catTitle) return null; | |||
catTitle = catTitle.substr (catTitle.indexOf (':') + 1).replace (/_/g, ' '); | |||
var result = { title : catTitle, match : ["", "", ""] }; | |||
if (pageText === null) return result; | |||
if (cleanedText === null) { | |||
cleanedText = pageText.replace(/<\!--(\s|\S)*?--\>/g, "") | |||
.replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, ""); | |||
} | |||
result.match = find_category (cleanedText, catTitle, true); | |||
return result; | |||
} | |||
var initialized = false; | |||
function setup () { | |||
if (initialized) return; | |||
initialized = true; | |||
// Find the category bar, or create an empty one if there isn't one. Then add -/+- links after | |||
// each category, and add the + link. | |||
catLine = catLine // Special:Upload | |||
|| document.getElementById ('mw-normal-catlinks') // MW >= 1.13alpha | |||
|| getElementsByClassName (document , 'p' , 'catlinks')[0]; // MW < 1.13 | |||
var hiddenCats = document.getElementById ('mw-hidden-catlinks'); | |||
if (!catLine) { | |||
var footer = null; | |||
if (!hiddenCats) { | |||
footer = getElementsByClassName (document , 'div' , 'printfooter')[0]; | |||
if (!footer) return; // Don't know where to insert the category line | |||
} | |||
catLine = make ('div'); | |||
catLine.id = 'mw-normal-catlinks'; | |||
catLine.style.textAlign = 'left'; | |||
// Add a label | |||
var label = make ('a'); | |||
label.href = wgArticlePath.replace ('$1', 'Special:Categories'); | |||
label.title = HotCat.categories; | |||
label.appendChild (make (HotCat.categories, true)); | |||
catLine.appendChild (label); | |||
catLine.appendChild (make (':', true)); | |||
// Insert the new category line | |||
var container = (hiddenCats ? hiddenCats.parentNode : document.getElementById ('catlinks')); | |||
if (!container) { | |||
container = make ('div'); | |||
container.id = 'catlinks'; | |||
footer.parentNode.insertBefore (container, footer.nextSibling); | |||
} | |||
container.className = 'catlinks noprint'; | |||
container.style.display = ""; | |||
if (!hiddenCats) { | |||
container.appendChild (catLine); | |||
} else { | |||
container.insertBefore (catLine, hiddenCats); | |||
} | |||
textHeight = label.firstChild.offsetHeight; | |||
} // end if catLine exists | |||
catLine.style.position = 'relative'; | |||
if (is_rtl) catLine.dir = 'rtl'; | |||
// Create editors for all existing categories | |||
function createEditors (line) { | |||
var cats = line.getElementsByTagName ('span'); | |||
// Copy cats, otherwise it'll also magically contain our added spans as it is a live collection! | |||
var copyCats = new Array (cats.length); | |||
for (var i = 0; i < cats.length; i++) copyCats[i] = cats[i]; | |||
var editor = null; | |||
for (var i = 0; i < copyCats.length; i++) { | |||
var test = isOnPage (copyCats[i]); | |||
if (test !== null && test.match !== null) { | |||
editor = new CategoryEditor (line, copyCats[i], test.title, test.match[2]); | |||
} | |||
} | |||
return copyCats.length > 0 ? copyCats[copyCats.length-1] : null; | |||
} | |||
var lastSpan = createEditors (catLine); | |||
// Create one to add a new category | |||
var editor = new CategoryEditor(catLine, null, null, lastSpan != null); | |||
if (!onUpload) { | |||
if (pageText !== null && hiddenCats) { | |||
hiddenCats.style.position = 'relative'; | |||
if (is_rtl) hiddenCats.dir = 'rtl'; | |||
createEditors (hiddenCats); | |||
} | |||
// And finally add the "multi-mode" span. (Do this at the end, otherwise it ends up in the list above.) | |||
var enableMulti = make ('span'); | |||
enableMulti.className = 'noprint'; | |||
if (is_rtl) enableMulti.dir = 'rtl'; | |||
catLine.insertBefore (enableMulti, catLine.firstChild.nextSibling); | |||
enableMulti.appendChild (make ('\xa0', true)); // nbsp | |||
multiSpan = make ('span'); | |||
enableMulti.appendChild (multiSpan); | |||
multiSpan.innerHTML = '(<a>' + HotCat.addmulti + '</a>)'; | |||
var lk = multiSpan.getElementsByTagName ('a')[0]; | |||
lk.onclick = function (evt) {setMultiInput (); return evtKill (evt);}; | |||
lk.style.cursor = 'pointer'; | |||
} | |||
cleanedText = null; | |||
} | |||
function setPage (json) { | |||
if (!json || !json.query || !json.query.pages) return; | |||
for (var p in json.query.pages) { | |||
var page = json.query.pages[p]; | |||
if (!page.revisions || page.revisions.length == 0) break; | |||
pageText = page.revisions[0]['*']; | |||
pageTime = page.revisions[0].timestamp.replace (/\D/g, ""); | |||
break; | |||
} | |||
} | |||
function getPage () { | |||
// We know we have an article here. | |||
if (wgArticleId == 0) { | |||
// Doesn't exist yet. | |||
pageText = ""; | |||
pageTime = null; | |||
setup (); | |||
} else { | |||
var url = wgServer + wgScriptPath + '/api.php?format=json&callback=HotCat.start&action=query&titles=' | |||
+ encodeURIComponent (wgPageName) + '&prop=info%7Crevisions&rvprop=content%7Ctimestamp'; | |||
var s = make ('script'); | |||
s.src = url; | |||
s.type = 'text/javascript'; | |||
HotCat.start = function (json) { setPage (json); setup (); }; | |||
document.getElementsByTagName ('head')[0].appendChild (s); | |||
window.setTimeout (setup, 4000); // 4 seconds. Just in case getting the wikitext takes longer. | |||
} | |||
} | |||
function run () { | |||
initialize (); | |||
if (is_rtl && window.ie6_bugs) return; // Disabled! IE6 with RTL is just too broken... | |||
if (wgNamespaceNumber == -1 && wgCanonicalSpecialPageName == 'Upload' && wgUserName) { | |||
setup_upload (); | |||
setup (); | |||
// Check for state restoration | |||
if ( typeof (UploadForm) != 'undefined' | |||
&& typeof (UploadForm.previous_hotcat_state) != 'undefined' | |||
&& UploadForm.previous_hotcat_state != null) | |||
UploadForm.previous_hotcat_state = setState (UploadForm.previous_hotcat_state); | |||
} else { | |||
if (!wgIsArticle || wgAction != 'view' || !can_edit() || HotCat.disable()) return; | |||
getPage (); | |||
} | |||
} | |||
// Legacy stuff | |||
function closeForm () { | |||
// Close all open editors without redirect resolution and other asynchronous stuff. | |||
for (var i = 0; i < editors.length; i++) { | |||
if (editors[i].state == CategoryEditor.OPEN) { | |||
editors[i].cancel(); | |||
} else if (editors[i].state == CategoryEditor.CHANGE_PENDING) { | |||
editors[i].sanitizeInput (); | |||
var value = editors[i].text.value.split('|'); | |||
var key = null; | |||
if (value.length > 1) key = value[1]; | |||
var v = value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, ""); | |||
if (v.length == 0) { | |||
editors[i].cancel (); | |||
} else { | |||
editors[i].currentCategory = v; | |||
editors[i].currentKey = key; | |||
editors[i].currentExists = this.inputExists; | |||
editors[i].close (); | |||
} | |||
} | |||
} | |||
} | |||
function getState () { | |||
var result = null; | |||
for (var i = 0; i < editors.length; i++) { | |||
var text = editors[i].currentCategory; | |||
var key = editors[i].currentKey; | |||
if (text && text.length > 0) { | |||
if (key && key.length > 0) text += '|' + key; | |||
if (result == null) | |||
result = text; | |||
else | |||
result = result + '\n' + text; | |||
} | |||
} | |||
return result; | |||
} | |||
function setState (state) { | |||
var cats = state.split ('\n'); | |||
if (cats.length == 0) return null; | |||
if (initialized && editors.length == 1 && editors[0].isAddCategory) { | |||
// Insert new spans and create new editors for them. | |||
var newSpans = []; | |||
var before = editors.length == 1 ? editors[0].span : null; | |||
for (var i = 0; i < cats.length; i++) { | |||
if (cats[i].length == 0) continue; | |||
var cat = cats[i].split ('|'); | |||
var key = cat.length > 1 ? cat[1] : null; | |||
cat = cat[0]; | |||
var lk = make ('a'); lk.href = wgArticlePath.split ('$1').join ('Category:' + encodeURI (cat)); | |||
lk.appendChild (make (cat, true)); | |||
lk.title = cat; | |||
var span = make ('span'); | |||
span.appendChild (lk); | |||
if (i == 0) catLine.insertBefore (make (' ', true), before); | |||
catLine.insertBefore (span, before); | |||
if (before && i+1 < cats.length) parent.insertBefore (make (' | ', true), before); | |||
newSpans.push ({element: span, title: cat, 'key': key}); | |||
} | |||
// And change the last one... | |||
if (before) { | |||
before.parentNode.insertBefore (make (' | ', true), before); | |||
} | |||
var editor = null; | |||
for (var i = 0; i < newSpans.length; i++) { | |||
editor = new CategoryEditor (catLine, newSpans[i].element, newSpans[i].title, newSpans[i].key); | |||
} | |||
} | |||
return null; | |||
} | |||
// Now export these legacy functions | |||
window.hotcat_get_state = function () { return getState(); }; | |||
window.hotcat_set_state = function (state) { return setState (state); }; | |||
window.hotcat_close_form = function () { closeForm (); }; | |||
addOnloadHook (run); | |||
})(); | |||
} // end if (guard) | |||
//</source> |
Versionen från 5 januari 2011 kl. 08.38
/* JavaScript som skrivs här körs varje gång en användare laddar en sida. */ //<source lang="javascript"> /* Ajax-based simple Category manager. Allows adding/removing/changing categories on a page view. Supports multiple category changes, as well as redirect and disambiguation resolution. Also plugs into the upload form. Search engines to use for the suggestion list are configurable, and can be selected interactively. Author: [[User:Lupo]], April-May 2010 License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0) Choose whichever license of these you like best :-) */ // Globals: // (inline script on the page): // wgNamespaceNumber, wgCanonicalSpecialPageName, wgNamespaceIds (optional), wgFormattedNamespaces (optional) // wgScript, wgServer, wgArticlePath, wgScriptPath, wgAction, wgPageName, wgTitle, wgUserName, wgIsArticle, // wgArticleId // ajax.js // sajax_init_object // wikibits.js // addOnloadHook, window.ie6_bugs if (typeof (HotCat) == 'undefined') { // Guard against double inclusions // Configuration stuff. var HotCat = { // Localize these messages to the main language of your wiki. messages : { cat_removed : 'tog bort [[Kategori:$1]]' ,cat_added : 'lade till [[Kategori:$1]]' ,cat_keychange: 'ändrade sorteringen i [[Kategori:$1]]: ' ,cat_notFound : 'Kategorin "$1" hittades inte' ,cat_exists : 'Kategorin "$1" finns redan, lades inte till.' ,cat_resolved : ' (omdirigeringen [[Kategori:$1]] rättades)' ,uncat_removed: 'tog bort {{uncategorized}}' ,using : ' med [[Wikipedia:Hotcat|Hotcat]]' ,multi_change : '$1 kategorier' // $1 is replaced by a number ,commit : 'Spara' // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, // see localization hook below. ,ok : 'OK' // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, // see localization hook below. ,cancel : 'Avbryt' // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, // see localization hook below. ,multi_error : 'Kunde inte hämta sidans text från servern. Dina ändringar sparades inte.' +' Försök igen senare.' // Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, // see localization hook below. } ,category_regexp : '[Cc]ategory|[Kk]ategori' // Regular sub-expression matching all possible names for the category namespace. Is automatically localized // correctly if you're running MediaWiki 1.16 or later. Otherwise, set it appropriately, e.g. at the German // Wikipedia, use '[Cc]ategory|[Kk]ategorie', or at the Chinese Wikipedia, use '[Cc]ategory|分类|分類'. ,category_canonical : 'Kategori' // The standard category name on your wiki. Is automatically localized correctly if you're running // MediaWiki 1.16 or later; otherwise, set it to the preferred category name (e.g., "Kategorie"). ,categories : 'Kategorier' // Plural of category_canonical ,disambig_category : null // Any category in this category is deemed a disambiguation category; i.e., a category that should not contain // any items, but that contains links to other categories where stuff should be categorized. If you don't have // that concept on your wiki, set it to null. ,redir_category : 'Kategoriomdirigering' // Any category in this category is deemed a (soft) redirect to some other category defined by the first link // to another category. If your wiki doesn't have soft category redirects, set this to null. ,links : {change: '(±)', remove: '(-)', add: '(+)', restore: '(×)', undo: '(×)'} // The little modification links displayed after category names. ,addmulti : '<span>+<sup>+</sup></span>' // The HTML content of the "enter multi-mode" link at the front. ,disable : function () { // Return true to disable HotCat return ( wgNamespaceNumber < 0 // Special pages; Special:Upload is handled differently || wgNamespaceNumber == 6 // Fil || wgNamespaceNumber == 7 // Fildiskussion || wgNamespaceNumber == 10 // Mall || typeof (wgNamespaceIds) != 'unknown' && ( wgNamespaceNumber == wgNamespaceIds['creator'] || wgNamespaceNumber == wgNamespaceIds['timedtext'] ) ); } ,uncat_regexp : /\{\{\s*([Uu]ncat(egori[sz]ed( image)?)?|[Nn]ocat|[Nn]eedscategory)[^}]*\}\}/g // A regexp matching a templates used to mark uncategorized pages, if your wiki does have that. // If not, set it to null. ,existsYes : 'http://upload.wikimedia.org/wikipedia/commons/thumb/b/be/P_yes.svg/20px-P_yes.svg.png' ,existsNo : 'http://upload.wikimedia.org/wikipedia/commons/thumb/4/42/P_no.svg/20px-P_no.svg.png' // The images used for the little indication icon. Should not need changing. }; if (wgUserLanguage != wgContentLanguage) importScript ('MediaWiki:Gadget-HotCat.js/' + wgUserLanguage); // Localization hook to localize HotCat.messages.commit and HotCat.messages.multi_error. For German, the // file would be "MediaWiki:Gadget-HotCat.js/de", and its contents could be for instance // // HotCat.messages.commit = 'Speichern'; // HotCat.messages.ok = 'OK'; // HotCat.messages.cancel = 'Abbrechen'; // HotCat.messages.multi_error = 'Seitentext konnte nicht vom Server geladen werden. Die Änderungen können ' // +'leider nicht gespeichert werden.'; // No further changes should be necessary here. However, you should also localize the search engine names. // Search in this file for "Localize the names here", and localize the names there. (function () { // First auto-localize the category names if (typeof (wgFormattedNamespaces) != 'undefined' && wgFormattedNamespaces['14']) { function create_regexp_str (name) { if (!name || name.length == 0) return ""; var initial = name.substr (0, 1); name = name.substring(1).replace (/[ _]/g, '[ _]').replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1'); return '[' + initial.toLowerCase () + initial.toUpperCase () + ']' + name; } HotCat.category_canonical = wgFormattedNamespaces['14']; HotCat.category_regexp += '|' + create_regexp_str (HotCat.category_canonical); for (var cat_name in wgNamespaceIds) { if ( typeof (cat_name) == 'string' && cat_name.toLowerCase () != HotCat.category_canonical.toLowerCase () && wgNamespaceIds[cat_name] == 14) { HotCat.category_regexp += '|' + create_regexp_str (cat_name); } } } // Utility functions. Yes, this duplicates some functionality that also exists in other places, but // to keep this whole stuff in a single file not depending on any other on-wiki Javascripts, we re-do // these few operations here. function bind (func, target) { var f = func, tgt = target; return function () { return f.apply (tgt, arguments); }; } function make (arg, literal) { if (!arg) return null; return literal ? document.createTextNode (arg) : document.createElement (arg); } function param (name, uri) { if (typeof (uri) == 'undefined' || uri === null) uri = document.location.href; var re = RegExp ('[&?]' + name + '=([^&#]*)'); var m = re.exec (uri); if (m && m.length > 1) return decodeURIComponent(m[1]); return null; } function title (href) { if (!href) return null; var script = wgScript + '?'; if (href.indexOf (script) == 0 || href.indexOf (wgServer + script) == 0) { // href="/w/index.php?title=..." return param ('title', href); } else { // href="/wiki/..." var prefix = wgArticlePath.replace ('$1', ""); if (href.indexOf (prefix) != 0) prefix = wgServer + prefix; // Fully expanded URL? if (href.indexOf (prefix) == 0) return decodeURIComponent (href.substring (prefix.length)); } return null; } function hasClass (elem, name) { return (' ' + elem.className + ' ').indexOf (' ' + name + ' ') >= 0; } function capitalize (str) { if (!str || str.length == 0) return str; return str.substr(0, 1).toUpperCase() + str.substr (1); } // Text modification var findCatsRE = new RegExp ('\\[\\[\\s*(?:' + HotCat.category_regexp + ')\\s*:\[^\\]\]+\\]\\]', 'g'); function replaceByBlanks (match) { return match.replace(/(\s|\S)/g, ' '); // /./ doesn't match linebreaks. /(\s|\S)/ does. } function find_category (wikitext, category, once) { var cat_name = category.replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1'); var initial = cat_name.substr (0, 1); var cat_regex = new RegExp ('\\[\\[\\s*(' + HotCat.category_regexp + ')\\s*:\\s*' + (initial == '\\' ? initial : '[' + initial.toUpperCase() + initial.toLowerCase() + ']') + cat_name.substring (1).replace (/[ _]/g, '[ _]') + '\\s*(\\|.*?)?\\]\\]', 'g' ); if (once) return cat_regex.exec (wikitext); var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks) .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks); var result = []; var curr_match = null; while ((curr_match = cat_regex.exec (copiedtext)) != null) { result.push ({match : curr_match}); } result.re = cat_regex; return result; // An array containing all matches, with positions, in result[i].match } function change_category (wikitext, toRemove, toAdd, key) { function find_insertionpoint (wikitext) { var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks) .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks); // Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element". var index = -1; findCatsRE.lastIndex = 0; while (findCatsRE.exec(copiedtext) != null) index = findCatsRE.lastIndex; // We should try to find interwiki links here, but that's for later. return index; } var summary = []; var nameSpace = HotCat.category_canonical; var cat_point = -1; // Position of removed category; if (key) key = '|' + key; var keyChange = (toRemove && toAdd && toRemove == toAdd && toAdd.length > 0); if (toRemove && toRemove.length > 0) { var matches = find_category (wikitext, toRemove); if (!matches || matches.length == 0) { return {text: wikitext, 'summary': summary, error: HotCat.messages.cat_notFound.replace ('$1', toRemove)}; } else { var before = wikitext.substring (0, matches[0].match.index); var after = wikitext.substring (matches[0].match.index + matches[0].match[0].length); if (matches.length > 1) { // Remove all occurrences in after matches.re.lastIndex = 0; after = after.replace (matches.re, ""); } if (toAdd) { nameSpace = matches[0].match[1] || nameSpace; if (!key) key = matches[0].match[2]; // Remember the category key, if any. } // Remove whitespace (properly): strip whitespace, but only up to the next line feed. // If we then have two linefeeds in a row, remove one. Otherwise, if we have two non- // whitespace characters, insert a blank. var i = before.length - 1; while (i >= 0 && before.charAt (i) != '\n' && before.substr (i, 1).search (/\s/) >= 0) i--; var j = 0; while (j < after.length && after.charAt (j) != '\n' && after.substr (j, 1).search (/\s/) >= 0) j++; if (i >= 0 && before.charAt (i) == '\n' && j < after.length && after.charAt (j) == '\n') i--; if (i >= 0) before = before.substring (0, i+1); else before = ""; if (j < after.length) after = after.substring (j); else after = ""; if (before.length > 0 && before.substring (before.length - 1).search (/\S/) >= 0 && after.length > 0 && after.substr (0, 1).search (/\S/) >= 0) before += ' '; cat_point = before.length; wikitext = before + after; if (!keyChange) summary.push (HotCat.messages.cat_removed.replace ('$1', toRemove)); } } if (toAdd && toAdd.length > 0) { var matches = find_category (wikitext, toAdd); if (matches && matches.length > 0) { return {text: wikitext, 'summary': summary, error : HotCat.messages.cat_exists.replace ('$1', toAdd)}; } else { if (cat_point < 0) cat_point = find_insertionpoint (wikitext); var newcatstring = '\n[[' + nameSpace + ':' + toAdd + (key || "") + ']]'; if (cat_point >= 0) { wikitext = wikitext.substring (0, cat_point) + newcatstring + wikitext.substring (cat_point); } else { wikitext += newcatstring; } if (keyChange) { summary.push (HotCat.messages.cat_keychange.replace ('$1', toAdd) + '"' + key.substring(1) + '"'); } else { summary.push (HotCat.messages.cat_added.replace ('$1', toAdd)); } if (HotCat.uncat_regexp) { var txt = wikitext.replace (HotCat.uncat_regexp, ""); // Remove "uncat" templates if (txt.length != wikitext.length) { wikitext = txt; summary.push (HotCat.messages.uncat_removed); } } } } return {text: wikitext, 'summary': summary, error: null}; } if (wgAction == 'edit') { // Legacy code based on URI parameters, can add/remove/change only one single category. Still // used for single-category changes, and newly to supply a default edit summary for multi category // changes. var toRemove = param ('hotcat_removecat'); var toAdd = param ('hotcat_newcat'); if (toAdd) { toAdd = capitalize (toAdd.replace (/_/g, ' ').replace (/^\s+|\s+$/g, "")); if (toAdd.length == 0) toAdd = null; } if (toRemove) { toRemove = capitalize (toRemove.replace (/_/g, ' ').replace (/^\s+|\s+$/g, "")); if (toRemove.length == 0) toRemove = null; } if (toAdd || toRemove) { addOnloadHook (function () { if (!document.editform || !document.editform.wpTextbox1) return; var comment = param ('hotcat_comment') || ""; var cat_key = param ('hotcat_sortkey'); var result = change_category (document.editform.wpTextbox1.value, toRemove, toAdd, cat_key); var do_commit = !HotCat.noCommit && !result.error && param ('hotcat_nocommit') != '1'; document.editform.wpTextbox1.value = result.text; if (result.summary && result.summary.length > 0) document.editform.wpSummary.value = result.summary.join ('; ') + comment + HotCat.messages.using; document.editform.wpMinoredit.checked = true ; if (result.error) alert (result.error); if (do_commit) { // Hide the entire edit section so as not to tempt the user into editing... var content = document.getElementById ('bodyContent') // monobook & vector skin || document.getElementById ('mw_contentholder') // modern skin || document.getElementById ('article'); // classic skins if (content) content.style.display = 'none'; document.editform.submit(); } }); } return; } // The real HotCat UI function evtKeys (e) { e = e || window.event || window.Event; // W3C, IE, Netscape var code = 0; if (typeof (e.ctrlKey) != 'undefined') { // All modern browsers // Ctrl-click seems to be overloaded in FF/Mac (it opens a pop-up menu), so treat cmd-click // as a ctrl-click, too. if (e.ctrlKey || e.metaKey) code |= 1; if (e.shiftKey) code |= 2; } else if (typeof (e.modifiers) != 'undefined') { // Netscape... if (e.modifiers & (Event.CONTROL_MASK | Event.META_MASK)) code |= 1; if (e.modifiers & Event.SHIFT_MASK) code |= 2; } return code; } function evtKill (e) { e = e || window.event || window.Event; // W3C, IE, Netscape if (typeof (e.preventDefault) != 'undefined') { e.preventDefault (); e.stopPropagation (); } else e.cancelBubble = true; return false; } var catLine = null; var onUpload = false; var editors = []; var commitButton = null; var commitForm = null; var multiSpan = null; var pageText = null; var pageTime = null; var watchCreate = false; var watchEdit = false; var minorEdits = false; var is_rtl = false; function setMultiInput () { if (commitButton || onUpload) return; commitButton = make ('input'); commitButton.type = 'button'; commitButton.value = HotCat.messages.commit; commitButton.onclick = multiSubmit; if (multiSpan) { multiSpan.parentNode.replaceChild (commitButton, multiSpan); } else { catLine.appendChild (commitButton); } // Get the preferences, so that we can set wpWatchthis correctly later on. Must use Ajax here. if (wgUserName) { var request = sajax_init_object (); request.open ('GET', wgServer + wgScriptPath + '/api.php?format=json&action=query&meta=userinfo&uiprop=options', true); request.onreadystatechange = function () { if (request.readyState != 4) return; if (request.status == 200 && request.responseText && request.responseText.charAt(0) == '{') { var json = eval ('(' + request.responseText + ')'); if (json && json.query && json.query.userinfo && json.query.userinfo.options) { watchCreate = json.query.userinfo.options.watchcreations == '1'; watchEdit = json.query.userinfo.options.watchdefault == '1'; minorEdits = json.query.userinfo.options.minordefault == 1; } } }; request.setRequestHeader ('Pragma', 'cache=yes'); request.setRequestHeader ('Cache-Control', 'no-transform'); request.send (null); } } function currentTimestamp () { var now = new Date(); var ts = "" + now.getUTCFullYear(); function two (s) { return s.substr (s.length - 2); } ts = ts + two ('0' + (now.getUTCMonth() + 1)) + two ('0' + now.getUTCDate()) + two ('00' + now.getUTCHours()) + two ('00' + now.getUTCMinutes()) + two ('00' + now.getUTCSeconds()); return ts; } function performChanges () { // Don't use the edit API or LAPI, it's always bothersome to report back errors like edit // conflicts. Instead, make one remote call (blocking, because we can't continue anyway if // it doesn't succeed), getting the page text. Perform the changes on the text, then construct // a form to submit all this as a diff. if (pageText === null) { var request = sajax_init_object (); var uri = wgServer + wgScriptPath + '/api.php?format=json&action=query&titles=' + encodeURIComponent (wgPageName) + '&prop=info%7Crevisions&rvprop=content%7Ctimestamp'; request.open ('GET', uri, false); // Yes, synchronous request.send (null); if (request.status == 200 && request.responseText && request.responseText.charAt(0) == '{') { setPage (eval ('(' + request.responseText + ')')); } } if (pageText === null) { alert (HotCat.messages.multi_error); return; } // Create a form and submit it if (!commitForm) { var formContainer = make ('div'); formContainer.style.display = 'none'; document.body.appendChild (formContainer); formContainer.innerHTML = '<form method="post" enctype="multipart/form-data" action="' + wgScript + '?title=' + encodeURIComponent (wgPageName) + '&action=edit">' + '<input type="hidden" name="wpTextbox1" />' + '<input type="hidden" name="wpSummary" value="" />' + '<input type="checkbox" name="wpMinoredit" value="1" />' + '<input type="checkbox" name="wpWatchthis" value="1" />' + '<input type="hidden" name="wpEdittime" />' + '<input type="hidden" name="wpStarttime" />' + '<input type="hidden" name="wpDiff" value="wpDiff" />' + '</form>'; commitForm = formContainer.firstChild; } var result = { text : pageText }; var changed = [], added = [], deleted = [], changes = 0; for (var i=0; i < editors.length; i++) { if (editors[i].state == CategoryEditor.CHANGED) { result = change_category ( result.text , editors[i].originalCategory , editors[i].currentCategory , editors[i].currentKey ); if (!result.error) { changes++; if (!editors[i].originalCategory || editors[i].originalCategory.length == 0) { added.push (editors[i].currentCategory); } else { changed.push ({from : editors[i].originalCategory, to : editors[i].currentCategory}); } } } else if (editors[i].state == CategoryEditor.DELETED) { result = change_category (result.text, editors[i].originalCategory, null, null); if (!result.error) { changes++; deleted.push (editors[i].originalCategory); } } } // Fill in the form and submit it commitForm.wpMinoredit.checked = minorEdits; commitForm.wpWatchthis.checked = wgArticleId == 0 && watchCreate || watchEdit; if (wgArticleId > 0) { if (changes == 1) { if (result.summary && result.summary.length > 0) commitForm.wpSummary.value = result.summary.join ('; ') + HotCat.messages.using; commitForm.wpMinoredit.checked = true; } else if (changes > 1) { var summary = []; if (deleted.length == 1) summary.push ('-[[' + HotCat.category_canonical + ':' + deleted[0] + ']]'); else if (deleted.length > 1) summary.push ('- ' + HotCat.messages.multi_change.replace ('$1', "" + deleted.length)); if (added.length == 1) summary.push ('+[[' + HotCat.category_canonical + ':' + added[0] + ']]'); else if (added.length > 1) summary.push ('+ ' + HotCat.messages.multi_change.replace ('$1', "" + added.length)); if (changed.length == 1) { if (changed[0].from != changed[0].to) { summary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + ']]→[[' + HotCat.category_canonical + ':' + changed[0].to + ']]'); } else { summary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + ']]'); } } else if (changed.length > 1) { summary.push ('± ' + HotCat.messages.multi_change.replace ('$1', "" + changed.length)); } if (summary.length > 0) commitForm.wpSummary.value = summary.join ('; ') + HotCat.messages.using; } } commitForm.wpTextbox1.value = result.text; commitForm.wpStarttime.value = currentTimestamp (); commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value; commitForm.submit(); } function resolveMulti (toResolve, callback) { for (var i = 0; i < toResolve.length; i++) { toResolve[i].dab = null; toResolve[i].dabInput = toResolve[i].lastInput; } if (noSuggestions) { callback (toResolve); return; } var request = sajax_init_object (); if (!request) { noSuggestions = true; callback (toResolve); return; } var url = wgServer + wgScriptPath + '/api.php'; // Use %7C instead of |, otherwise Konqueror insists on re-encoding the arguments, resulting in doubly encoded // category names. (That is a bug in Konqueror. Other browsers don't have this problem.) var args = 'action=query&prop=info%7Clinks%7Ccategories&plnamespace=14&pllimit=50' + '&cllimit=' + (toResolve.length * 10) // Category limit is global, link limit is per page + '&format=json&titles='; for (var i = 0; i < toResolve.length; i++) { args += encodeURIComponent ('Category:' + toResolve[i].dabInput); if (i+1 < toResolve.length) args += '%7C'; } if (url.length + args.length + 1 > 2000) { // Lowest common denominator: IE has a URI length limit of 2083 request.open ('POST', url, true); request.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded'); } else { url += '?' + args; args = null; request.open ('GET', url, true); } request.onreadystatechange = function () { if (request.readyState != 4) return; if (request.status != 200) { callback (toResolve); return; } resolveRedirects (toResolve, eval ('(' + request.responseText + ')')); callback (toResolve); }; request.setRequestHeader ('Pragma', 'cache=yes'); request.setRequestHeader ('Cache-Control', 'no-transform'); request.send (args); } function resolveOne (page, toResolve) { var cats = page.categories; var lks = page.links; var is_dab = false; var is_redir = typeof (page.redirect) == 'string'; // Hard redirect? if (!is_redir && cats && (HotCat.disambig_category || HotCat.redir_category)) { for (var c = 0; c < cats.length; c++) { var cat = cats[c]['title']; // Strip namespace prefix if (cat) { cat = cat.substring (cat.indexOf (':') + 1).replace(/_/g, ' '); if (cat == HotCat.disambig_category) { is_dab = true; break; } else if (cat == HotCat.redir_category) { is_redir = true; break; } } } } if (!is_redir && !is_dab) return; if (!lks || lks.length == 0) return; var titles = []; for (var i = 0; i < lks.length; i++) { if ( lks[i]['ns'] == 14 // Category namespace && lks[i]['title'] && lks[i]['title'].length > 0) // Name not empty { // Internal link to existing thingy. Extract the page name and remove the namespace. var match = lks[i]['title']; titles.push (match.substring (match.indexOf (':') + 1)); if (is_redir) break; } } for (var j = 0; j < toResolve.length; j++) { if (toResolve[j].dabInput != page.title.substring (page.title.indexOf (':') + 1)) continue; if (titles.length > 1) { toResolve[j].dab = titles; } else { toResolve[j].inputExists = true; // Might actually be wrong... toResolve[j].icon.src = HotCat.existsYes; toResolve[j].text.value = titles[0] + (toResolve[j].currentKey != null ? '|' + toResolve[j].currentKey : ""); } } } function resolveRedirects (toResolve, params) { if (!params || !params.query || !params.query.pages) return; for (var p in params.query.pages) resolveOne (params.query.pages[p], toResolve); } function multiSubmit () { var toResolve = []; for (var i = 0; i < editors.length; i++) { if (editors[i].state == CategoryEditor.CHANGE_PENDING || editors[i].state == CategoryEditor.OPEN) toResolve.push (editors[i]); } if (toResolve.length == 0) { performChanges (); return; } resolveMulti ( toResolve , function (resolved) { var firstDab = null; var dontChange = false; for (var i = 0; i < resolved.length; i++) { if (resolved[i].lastInput != resolved[i].dabInput) { // We didn't disable all the open editors, but we did asynchronous calls. It is // theoretically possible that the user changed something... dontChange = true; } else { if (resolved[i].dab) { if (!firstDab) firstDab = resolved[i]; } else { if (resolved[i].acceptCheck(true)) resolved[i].commit(); } } } if (firstDab) { CategoryEditor.makeActive (firstDab); } else if (!dontChange) { performChanges (); } } ); } var noSuggestions = false; var suggestionEngines = { opensearch : { uri : '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=$1' // $1 = search term ,handler : // Function to convert result of uri into an array of category names function (responseText, queryKey) { if (responseText.charAt (0) != '[') return null; var queryResult = eval ('(' + responseText + ')'); if (queryResult != null && queryResult.length == 2 && queryResult[0] == queryKey) { var titles = queryResult[1]; for (var i = 0; i < titles.length; i++) { titles[i] = titles[i].substring (titles[i].indexOf (':') + 1); // rm namespace } return titles; } return null; } } ,internalsearch : { uri : '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1' ,handler : function (responseText, queryKey) { if (responseText.charAt (0) != '{') return null; var queryResult = eval ('(' + responseText + ')'); if (queryResult && queryResult.query && queryResult.query.allpages) { var titles = queryResult.query.allpages; for (var i = 0; i < titles.length; i++) { titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace if (titles[i].indexOf (queryKey) != 0) { titles.splice (i, 1); // Doesn't start with the query key i--; } } return titles; } return null; } } }; // Localize the names here. var suggestionConfigs = { searchindex : {name: 'Search index', engines: ['opensearch'], cache: {}} ,pagelist : {name: 'Page list', engines: ['internalsearch'], cache: {}} ,combined : {name: 'Combined search', engines: ['opensearch', 'internalsearch'], cache: {}} }; function CategoryEditor () { this.initialize.apply (this, arguments); }; CategoryEditor.UNCHANGED = 0; CategoryEditor.OPEN = 1; // Open, but no input yet CategoryEditor.CHANGE_PENDING = 2; // Open, some input made CategoryEditor.CHANGED = 3; CategoryEditor.DELETED = 4; CategoryEditor.makeActive = function (toActivate) { for (var i = 0; i < editors.length; i++) { if (editors[i] != toActivate) editors[i].inactivate (); } toActivate.is_active = true; if (toActivate.dab) { toActivate.showSuggestions (toActivate.dab, false, null, true); // do autocompletion, no key, no engine selector toActivate.dab = null; } }; CategoryEditor.prototype = { initialize : function (line, span, after, key) { // If a span is given, 'after' is the category title, otherwise it may be an element after which to // insert the new span. 'key' is likewise overloaded; if a span is given, it is the category key (if // known), otherwise it is a boolean indicating whether a bar shall be prepended. if (!span) { this.isAddCategory = true; // Create add span and append to catLinks this.originalCategory = ""; this.originalKey = null; this.originalExists = false; span = make ('span'); span.className = 'noprint'; if (key) { span.appendChild (make (' | ', true)); if (after) { after.parentNode.insertBefore (span, after.nextSibling); after = after.nextSibling; } else { line.appendChild (span); } } else if (line.firstChild) { span.appendChild (make (' ', true)); line.appendChild (span); } this.linkSpan = make ('span'); this.linkSpan.className = 'noprint'; var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this); lk.appendChild (make (HotCat.links.add, true)); this.linkSpan.appendChild (lk); span = make ('span'); span.className = 'noprint'; if (is_rtl) span.dir = 'rtl'; span.appendChild (this.linkSpan); if (after) after.parentNode.insertBefore (span, after.nextSibling); else line.appendChild (span); this.normalLinks = null; this.undelLink = null; this.catLink = null; } else { if (is_rtl) span.dir = 'rtl'; this.isAddCategory = false; this.catLink = span.firstChild; this.originalCategory = after; this.originalKey = (key && key.length > 1) ? key.substr(1) : null; // > 1 because it includes the leading bar this.originalExists = !hasClass (this.catLink, 'new'); // Create change and del links this.makeLinkSpan (); span.appendChild (this.linkSpan); } this.line = line; this.engine = HotCat.suggestions; this.span = span; this.currentCategory = this.originalCategory; this.currentExists = this.originalExists; this.currentKey = this.originalKey; this.state = CategoryEditor.UNCHANGED; this.lastSavedState = CategoryEditor.UNCHANGED; this.lastSavedCategory = this.originalCategory; this.lastSavedKey = this.originalKey; this.lastSavedExists = this.originalExists; editors[editors.length] = this; }, makeLinkSpan : function () { this.normalLinks = make ('span'); var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.remove, this); lk.appendChild (make (HotCat.links.remove, true)); this.normalLinks.appendChild (make (' ', true)); this.normalLinks.appendChild (lk); lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this); lk.appendChild (make (HotCat.links.change, true)); this.normalLinks.appendChild (make (' ', true)); this.normalLinks.appendChild (lk); this.linkSpan = make ('span'); this.linkSpan.className = 'noprint'; this.linkSpan.appendChild (this.normalLinks); this.undelLink = make ('span'); this.undelLink.style.display = 'none'; lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.restore, this); lk.appendChild (make (HotCat.links.restore, true)); this.undelLink.appendChild (make (' ', true)); this.undelLink.appendChild (lk); this.linkSpan.appendChild (this.undelLink); }, open : function (evt) { if (this.isAddCategory && !onUpload) { var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one } if (!commitButton && !onUpload) { for (var i = 0; i < editors.length; i++) { if (editors[i].state != CategoryEditor.UNCHANGED) { setMultiInput(); break; } } } if (!this.form) { var form = make ('form'); form.method = 'POST'; form.onsubmit = bind (this.accept, this); this.form = form; var text = make ('input'); text.type = 'text'; text.size = HotCat.editbox_width; if (!noSuggestions) { text.onkeyup = bind ( function (evt) { evt = evt || window.event || window.Event; // W3C, IE, Netscape var key = evt.keyCode || 0; if (key == 38 || key == 40) { // In case a browser doesn't generate keypress events for arrow keys... if (this.keyCount == 0) return this.processKey (evt); } else { if (key == 27) this.resetKeySelection (); // Also do this for ESC as a workaround for Firefox bug 524360 // https://bugzilla.mozilla.org/show_bug.cgi?id=524360 var dont_autocomplete = (key == 8 || key == 46 || key == 27); // BS, DEL, ESC this.state = CategoryEditor.CHANGE_PENDING; var self = this; window.setTimeout (function () {self.textchange (dont_autocomplete);}, HotCat.suggest_delay); } } ,this ); text.onkeydown = bind ( function (evt) { evt = evt || window.event || window.Event; // W3C, IE, Netscape this.lastKey = evt.keyCode || 0; this.keyCount = 0; // Handle return explicitly, to override the default form submission to be able to check for ctrl if (evt.keyCode == 13) this.accept (evt); } ,this ); // And handle continued pressing of arrow keys text.onkeypress = bind (function (evt) {this.keyCount++; return this.processKey (evt);}, this); } text.onfocus = bind (function () { CategoryEditor.makeActive (this); }, this); this.text = text; this.icon = make ('img'); var list = null; if (!noSuggestions) { list = make ('select'); list.onclick = bind (function () { if (this.setValueFromList ()) this.textchange (); }, this); list.ondblclick = bind (function (e) { if (this.setValueFromList ()) this.accept (e); }, this); list.onchange = bind (function (e) { this.setValueFromList (); this.text.focus(); }, this); list.onkeyup = bind ( function (evt) { evt = evt || window.event || window.Event; // W3C, IE, Netscape if (evt.keyCode == 27) { this.resetKeySelection (); this.text.focus(); var self = this; window.setTimeout (function () {self.textchange (true);}, HotCat.suggest_delay); } else if (evt.keyCode == 13) { this.accept (evt); } } ,this ); if (!HotCat.fixed_search) { var engineSelector = make ('select'); for (var key in suggestionConfigs) { var opt = make ('option'); opt.value = key; if (key == this.engine) opt.selected = true; opt.appendChild (make (suggestionConfigs[key].name, true)); engineSelector.appendChild (opt); } engineSelector.onchange = bind ( function () { this.engine = this.engineSelector.options[this.engineSelector.selectedIndex].value; this.textchange (true, true); // Don't autocomplete, force re-display of list } ,this ); this.engineSelector = engineSelector; } } this.list = list; function button_label (id, defaultText) { var label = null; if ( onUpload && typeof (UFUI) != 'undefined' && typeof (UIElements) != 'undefined' && typeof (UFUI.getLabel) == 'function') { try { label = UFUI.getLabel (id, true); // Extract the plain text. IE doesn't know that Node.TEXT_NODE == 3 while (label && label.nodeType != 3) label = label.firstChild; } catch (ex) { label = null; } } if (!label || !label.data) return defaultText; return label.data; } var OK = make ('input'); OK.type = 'submit'; OK.value = button_label ('wpOkUploadLbl', HotCat.messages.ok); this.ok = OK; var cancel = make ('input'); cancel.type = 'button'; cancel.value = button_label ('wpCancelUploadLbl', HotCat.messages.cancel); cancel.onclick = bind (this.cancel, this); this.cancelButton = cancel; if (list) form.appendChild (list); if (this.engineSelector) form.appendChild (this.engineSelector); form.appendChild (text); if (!noSuggestions) form.appendChild (this.icon); form.appendChild (OK); form.appendChild (cancel); form.style.display = 'none'; this.span.appendChild (form); } if (this.list) this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; this.currentCategroy = this.lastSavedCategory; this.currentExits = this.lastSavedExists; this.currentKey = this.lastSavedKey; this.icon.src = this.currentExists ? HotCat.existsYes : HotCat.existsNo; this.text.value = this.currentCategory + (this.currentKey ? '|' + this.currentKey : ""); this.originalState = this.state; this.lastInput = this.currentCategory; this.inputExists = this.currentExists; this.state = this.state == CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING; // Display the form if (this.catLink) this.catLink.style.display = 'none'; this.linkSpan.style.display = 'none'; this.form.style.display = 'inline'; CategoryEditor.makeActive (this); // Kill the event before focussing, otherwise IE will kill the onfocus event! var result = evtKill (evt); this.text.focus(); return result; }, cancel : function () { if (this.isAddCategory && !onUpload) { this.removeEditor(); // We added a new adder when opening return; } // Close, re-display link if (this.list) this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; this.form.style.display = 'none'; if (this.catLink) this.catLink.style.display = ""; this.linkSpan.style.display = ""; this.state = this.originalState; this.currentCategory = this.lastSavedCategory; this.currentKey = this.lastSavedKey; this.currentExists = this.lastSavedExists; }, removeEditor : function () { var next = this.span.nextSibling; if (next) next.parentNode.removeChild (next); this.span.parentNode.removeChild (this.span); for (var i = 0; i < editors.length; i++) { if (editors[i] == this) { editors.splice (i, 1); break; } } var self = this; window.setTimeout (function () {delete self;}, 10); }, rollback : function (evt) { this.undoLink.parentNode.removeChild (this.undoLink); this.undoLink = null; this.currentCategory = this.originalCategory; this.currentKey = this.originalKey; this.currentExists = this.originalExists; this.lastSavedCategory = this.originalCategory; this.lastSavedKey = this.originalKey; this.lastSavedExists = this.originalExists; this.state = CategoryEditor.UNCHANGED; if (!this.currentCategory || this.currentCategory.length == 0) { // It was a newly added category. Remove the whole editor. this.removeEditor(); } else { // Redisplay the link... this.catLink.removeChild (this.catLink.firstChild); this.catLink.appendChild (make (this.currentCategory, true)); this.catLink.href = wgArticlePath.replace ('$1', 'Category:' + this.currentCategory); this.catLink.title = ""; this.catLink.className = this.currentExists ? "" : 'new'; } return evtKill (evt); }, inactivate : function () { if (this.list) this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; this.is_active = false; }, acceptCheck : function (dontCheck) { this.sanitizeInput (); var value = this.text.value.split('|'); var key = null; if (value.length > 1) key = value[1]; var v = capitalize (value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, "")); this.lastInput = v; if (v.length == 0) { this.cancel (); return false; } if ( !dontCheck && ( ( v == this.lastSavedCategory && ( key == this.lastSavedKey || key === null && this.lastSavedKey.length == 0 || this.lastSavedKey === null && key.length == 0 ) ) || wgNamespaceNumber == 14 && v == wgTitle ) ) { this.cancel (); return false; } this.currentCategory = v; this.currentKey = key; this.currentExists = this.inputExists; return true; }, accept : function (evt) { this.noCommit = (evtKeys (evt) & 1) != 0; var result = evtKill (evt); if (this.acceptCheck ()) { var toResolve = [this]; var original = this.currentCategory; resolveMulti ( toResolve , function (resolved) { if (resolved[0].dab) { CategoryEditor.makeActive (resolved[0]); } else { if (resolved[0].acceptCheck(true)) { resolved[0].commit ( (resolved[0].currentCategory != original) ? HotCat.messages.cat_resolved.replace ('$1', original) : null ); } } } ); } return result; }, close : function () { if (!this.catLink) { // Create a catLink this.catLink = make ('a'); this.catLink.appendChild (make ('foo', true)); this.catLink.style.display = 'none'; this.span.insertBefore (this.catLink, this.span.firstChild.nextSibling); } this.catLink.removeChild (this.catLink.firstChild); this.catLink.appendChild (make (this.currentCategory, true)); this.catLink.href = wgArticlePath.replace ('$1', 'Category:' + this.currentCategory); this.catLink.title = ""; this.catLink.className = this.currentExists ? "" : 'new'; this.lastSavedCategory = this.currentCategory; this.lastSavedKey = this.currentKey; this.lastSavedExists = this.currentExists; // Close form and redisplay category if (this.list) this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; this.form.style.display = 'none'; this.catLink.style.display = ""; if (this.isAddCategory) { if (onUpload) { var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one } this.isAddCategory = false; this.linkSpan.parentNode.removeChild (this.linkSpan); this.makeLinkSpan (); this.span.appendChild (this.linkSpan); } if (!this.undoLink && !onUpload) { // Append an undo link. var span = make ('span'); var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.rollback, this); lk.appendChild (make (HotCat.links.undo, true)); span.appendChild (make (' ', true)); span.appendChild (lk); this.normalLinks.appendChild (span); this.undoLink = span; } this.linkSpan.style.display = ""; this.state = CategoryEditor.CHANGED; }, commit : function (comment) { // Check again to catch problem cases after redirect resolution if ( ( this.currentCategory == this.originalCategory && (this.currentKey == this.originalKey || this.currentKey === null && this.originalKey.length == 0 || this.originalKey === null && this.currentKey.length == 0 ) ) || wgNamespaceNumber == 14 && this.currentCategory == wgTitle ) { this.cancel (); return; } if (commitButton || onUpload) { this.close (); } else { if (this.list) this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; // Execute change from this.originalCategory to this.currentCategory|this.currentKey, var editlk = wgServer + wgScript + '?title=' + encodeURIComponent (wgPageName) + '&action=edit'; var url = editlk + '&hotcat_newcat=' + encodeURIComponent (this.currentCategory); if (this.currentKey) url += '&hotcat_sortkey=' + encodeURIComponent (this.currentKey); if (this.originalCategory.length > 0) url += '&hotcat_removecat=' + encodeURIComponent (this.originalCategory); if (comment) url = url + '&hotcat_comment=' + encodeURIComponent (comment); if (this.noCommit || HotCat.no_autocommit) url = url + '&hotcat_nocommit=1'; document.location = url; } }, remove : function (evt) { this.doRemove (evtKeys (evt) & 1); return evtKill (evt); }, doRemove : function (noCommit) { if (this.isAddCategory) { // Empty input on adding a new category this.cancel (); return; } if (!commitButton && !onUpload) { for (var i = 0; i < editors.length; i++) { if (editors[i].state != CategoryEditor.UNCHANGED) { setMultiInput(); break; } } } if (commitButton) { this.catLink.style.textDecoration = 'line-through'; this.originalState = this.state; this.state = CategoryEditor.DELETED; this.normalLinks.style.display = 'none'; this.undelLink.style.display = ""; } else { if (onUpload) { // Remove this editor completely this.removeEditor (); } else { // Execute single category deletion. var editlk = wgServer + wgScript + '?title=' + encodeURIComponent (wgPageName) + '&action=edit'; if (noCommit || HotCat.no_autocommit) editlk += '&hotcat_nocommit=1'; document.location = editlk + '&hotcat_removecat=' + encodeURIComponent (this.originalCategory); } } }, restore : function (evt) { // Can occur only if we do have a commit button and are not on the upload form this.catLink.style.textDecoration = ""; this.state = this.originalState; this.normalLinks.style.display = ""; this.undelLink.style.display = 'none'; return evtKill (evt); }, // Internal operations setValueFromList : function (idx) { if (typeof (idx) == 'undefined') idx = this.list.selectedIndex; if (idx >= 0 && idx < this.list.options.length) { var v = this.text.value.split ('|'); this.text.value = this.list.options[idx].text + (v.length > 1 ? '|' + v[1] : ""); this.inputExists = true; // Might be wrong if from a dab list... if (this.icon) this.icon.src = HotCat.existsYes; return true; } return false; }, sanitizeInput : function () { var v = this.text.value || ""; v = v.replace(/^(\s|_)+/, ""); // Trim leading blanks and underscores var re = new RegExp ('^(' + HotCat.category_regexp + '):'); if (re.test (v)) v = v.substring (v.indexOf (':') + 1); v = capitalize (v); // Only update the input field if there is a difference. IE8 appears to reset the selection // and place the cursor at the front upon reset, which makes our autocompletetion become a // nuisance. FF and IE6 don't seem to have this problem. if (this.text.value != null && this.text.value != v) this.text.value = v; }, makeCall : function (url, callbackObj, engine, queryKey) { var cb = callbackObj; var e = engine; var v = queryKey; var r = sajax_init_object (); cb.requests.push (r); r.open('GET', url, true); r.onreadystatechange = bind ( function () { if (r.readyState == 4) { if (r.status != 200) cb.dontCache = true; if (r.status == 200 && r.responseText != null) { var titles = e.handler (r.responseText, v); if (titles && titles.length > 0) { if (cb.allTitles == null) { cb.allTitles = titles; } else { cb.allTitles = cb.allTitles.concat (titles); } } } cb.callsMade++; } if (cb.callsMade == cb.nofCalls) { if (!cb.dontCache && !suggestionConfigs[cb.engineName].cache[v]) { suggestionConfigs[cb.engineName].cache[v] = cb.allTitles; } if (!cb.cancelled) this.showSuggestions (cb.allTitles, cb.noCompletion, v); if (cb === this.callbackObj) this.callbackObj = null; delete cb; } } ,this ); r.setRequestHeader ('Pragma', 'cache=yes'); r.setRequestHeader ('Cache-Control', 'no-transform'); r.send (null); }, callbackObj : null, textchange : function (dont_autocomplete, force) { // Hide all other lists CategoryEditor.makeActive (this); if (noSuggestions) { // No Ajax: just make sure the list is hidden if (this.list) this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; if (this.icon) this.icon.style.display = 'none'; return; } // Get input value, omit sort key, if any this.sanitizeInput (); var v = this.text.value; // Disregard anything after a pipe. var pipe = v.indexOf ('|'); if (pipe >= 0) v = v.substring (0, pipe); if (this.lastInput == v && !force) return; // No change this.lastInput = v; this.lastRealInput = v; if (v.length == 0) { this.showSuggestions([]); return; } if (!sajax_init_object ()) { noSuggestions = true; if (this.list) this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; if (this.icon) this.icon.style.display = 'none'; return; } if (this.callbackObj) this.callbackObj.cancelled = true; var engine = suggestionConfigs[this.engine] ? this.engine : 'combined'; if (suggestionConfigs[engine].cache[v]) { this.showSuggestions (suggestionConfigs[engine].cache[v], dont_autocomplete, v); return; } var engines = suggestionConfigs[engine].engines; this.callbackObj = {allTitles: null, requests: [], callsMade: 0, nofCalls: engines.length, noCompletion: dont_autocomplete, engineName: engine}; for (var j = 0; j < engines.length; j++) { engine = suggestionEngines[engines[j]]; var url = wgServer + wgScriptPath + engine.uri.replace (/\$1/g, encodeURIComponent (v)); this.makeCall (url, this.callbackObj, engine, v); } }, showSuggestions : function (titles, dontAutocomplete, queryKey, noEngine) { this.dab = null; if (!this.list) return; if (noSuggestions) { if (this.list) this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; if (this.icon) this.icon.style.display = 'none'; this.inputExists = true; // Default... return; } if (!noEngine) { noEngine = this.engineSelector == null; } else { if (this.engineSelector) this.engineSelector.style.display = 'none'; } if (queryKey) { if (this.lastInput.indexOf (queryKey) != 0) return; if (this.lastQuery && this.lastInput.indexOf (this.lastQuery) == 0 && this.lastQuery.length > queryKey.length) return; } this.lastQuery = queryKey; if (!titles || titles.length == 0) { if (this.list) this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; if (this.icon) this.icon.src = HotCat.existsNo; this.inputExists = false; return; } // Get current input text var v = this.text.value.split('|'); var key = v.length > 1 ? '|' + v[1] : ""; v = capitalize (v[0]); titles.sort ( function (a, b) { if (a.indexOf (b) == 0) return 1; // a begins with b: a > b if (b.indexOf (a) == 0) return -1; // b begins with a: a < b // Opensearch may return stuff not beginning with the search prefix! var prefixMatchA = (a.indexOf (v) == 0 ? 1 : 0); var prefixMatchB = (b.indexOf (v) == 0 ? 1 : 0); if (prefixMatchA != prefixMatchB) return prefixMatchB - prefixMatchA; if (a < b) return -1; if (b < a) return 1; return 0; } ); // Remove duplicates for (var i = 0; i+1 < titles.length; i++) { if (titles[i] == titles[i+1]) { titles.splice (i+1, 1); i--; } } var firstTitle = titles[0]; var completed = this.autoComplete (firstTitle, v, key, dontAutocomplete); this.icon.src = completed ? HotCat.existsYes : HotCat.existsNo; this.inputExists = completed; if (completed) { this.lastInput = firstTitle; if (titles.length == 1) { this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; return; } } if (!this.is_active) { this.list.style.display = 'none'; if (this.engineSelector) this.engineSelector.style.display = 'none'; return; } var nofItems = (titles.length > 5 ? 5 : titles.length); if (nofItems <= 1) nofItems = 2; this.list.size = nofItems; this.list.style.align = 'left'; this.list.style.zIndex = 5; this.list.style.position = 'absolute'; // Compute initial list position. First the height. var listh = 0; if (this.list.style.display == 'none') { // Off-screen display to get the height this.list.style.top = this.text.offsetTop + 'px'; this.list.style.left = '-10000px'; this.list.style.display = ""; listh = this.list.offsetHeight; this.list.style.display = 'none'; } else { listh = this.list.offsetHeight; } // Approximate calculation of maximum list size var maxListHeight = listh; if (nofItems < 5) maxListHeight = (listh / nofItems) * 5; function scroll_offset (what) { var s = 'scroll' + what; return (document.documentElement ? document.documentElement[s] : 0) || document.body[s] || 0; } function viewport (what) { if (typeof (is_safari) != 'undefined' && is_safari && !document.evaluate) return window['inner' + what]; var s = 'client' + what; if (typeof (is_opera) != 'undefined' && is_opera) return document.body[s]; return (document.documentElement ? document.documentElement[s] : 0) || document.body[s] || 0; } function position (node) { // Stripped-down simplified position function. It's good enough for our purposes. if (node.getBoundingClientRect) { var box = node.getBoundingClientRect (); return { x : Math.round (box.left + scroll_offset ('Left')) ,y : Math.round (box.top + scroll_offset ('Top')) }; } var t = 0, l = 0; do { t = t + (node.offsetTop || 0); l = l + (node.offsetLeft || 0); node = node.offsetParent; } while (node); return {x : l, y : t}; } // IE6 seems to report in this.text.offsetTop and this.text.offsetLeft global offsets?? // Possibly this has something to do with the special status of input elements in IE as // "windowed controls". Calculate the relative offsets manually. var textPos = position (this.text); var catLinePos = position (this.line); var textTop = textPos.y - catLinePos.y; var textLeft = textPos.x - catLinePos.x; if (window.ie6_bugs) { // IE6 somehow has a problem with inline-displayed forms (to which our list belongs), and will add the // offset of the beginning of the text to the offsets we'd normally calculate, which in particular with // right-aligned category lines as they occur in some older skins completely misplaces the lists, sometimes // even off-screen. This appears to affect only the horizontal positioning of the list and of the // engineSelector. Try to account for this bizarre behavior. Notes: dunno if that also occurs on IE7. var textStartPos = position (this.line.firstChild); textStartPos.x -= catLinePos.x; textLeft -= textStartPos.x; } var nl = textLeft; var nt = 0; var offset = 0; if (!noEngine) { this.engineSelector.style.zIndex = 5; this.engineSelector.style.position = 'absolute'; this.engineSelector.style.width = this.text.offsetWidth + 'px'; // Figure out the height of this selector: display it off-screen, then hide it again. if (this.engineSelector.style.display == 'none') { this.engineSelector.style.left = '-1000px'; this.engineSelector.style.top = textTop + 'px'; this.engineSelector.style.display = ""; offset = this.engineSelector.offsetHeight; this.engineSelector.style.display = 'none'; } else { offset = this.engineSelector.offsetHeight; } this.engineSelector.style.left = nl + 'px'; } if (textPos.y < maxListHeight + offset) { // The list might extend beyond the upper border of the page. Let's avoid that by placing it // below the input text field. nt = textTop + this.text.offsetHeight + offset + 1; if (!noEngine) this.engineSelector.style.top = textTop + this.text.offsetHeight + 'px'; } else { nt = textTop - listh - offset; if (!noEngine) this.engineSelector.style.top = textTop - offset + 'px'; } this.list.style.top = nt + 'px'; this.list.style.width = ""; // No fixed width (yet) this.list.style.left = nl + 'px'; // (Re-)fill the list while (this.list.firstChild) this.list.removeChild (this.list.firstChild); for (var i = 0 ; i < titles.length ; i++) { var opt = make ('option') ; opt.appendChild (make (titles[i], true)); this.list.appendChild (opt); } if (!noEngine) this.engineSelector.style.display = ""; this.list.style.display = 'block'; // Set the width of the list var scroll = scroll_offset ('Left'); var view_w = viewport ('Width'); var l_pos = position (this.list); if (this.list.offsetWidth < this.text.offsetWidth) { this.list.style.width = this.text.offsetWidth + 'px'; return; } // Make sure that the list fits horizontally into the browser window var w = this.list.offsetWidth; if (l_pos.x + w > scroll + view_w) { if (w > view_w) w = view_w; this.list.style.width = w + 'px'; this.list.style.left = nl - (l_pos.x + w - scroll - view_w) + 'px'; } }, autoComplete : function (newVal, actVal, key, dontModify) { if (newVal == actVal) return true; if (dontModify || newVal.indexOf (actVal) != 0) return false; // Actual input is a prefix of the new text. Fill in new text, selecting the newly added suffix // such that it can be easily removed by typing backspace if the suggestion is unwanted. if (!( this.text.setSelectionRange || this.text.createTextRange || typeof (this.text.selectionStart) != 'undefined' && typeof (this.text.selectionEnd) != 'undefined' ) ) return false; // Here we know that we can indeed select properly. If we can't doing this would be a major // annoyance. this.text.focus(); var start = actVal.length; this.text.value = newVal + key; if (this.text.setSelectionRange) // e.g. khtml this.text.setSelectionRange (start, newVal.length); else if (this.text.createTextRange) { // IE var new_selection = this.text.createTextRange(); new_selection.move ('character', start); new_selection.moveEnd ('character', newVal.length - start); new_selection.select(); } else { this.text.selectionStart = start; this.text.selectionEnd = newVal.length; } return true; }, processKey : function (evt) { if (this.lastKey == 38 || this.lastKey == 40) { // Up and down arrows if (this.list.style.display != 'none') { // List is visible, so there are suggestions this.highlightSuggestion (this.lastKey == 38 ? -1 : 1); // Kill the event, otherwise some browsers (e.g., Firefox) may additionally treat an up-arrow as // "place the text cursor at the front", which we don't want here. return evtKill (evt); } else if ( this.keyCount <= 1 && (!this.callbackObj || this.callbackObj.callsMade == this.callbackObj.nofCalls) ) { // If no suggestions displayed, get them, unless we're already getting them. this.textchange (); } } return true; }, highlightSuggestion : function (dir) { if (noSuggestions || !this.list || this.list.style.display == 'none') return; var curr = this.list.selectedIndex; var tgt = curr < 0 ? 0 : curr + dir; tgt = tgt < 0 ? 0 : tgt; if (tgt != curr && tgt < this.list.options.length) { if (curr >= 0 && curr < this.list.options.length) this.list.options[curr].selected = false; this.list.options[tgt].selected = true; // Get current input text var v = this.text.value.split('|'); var key = v.length > 1 ? '|' + v[1] : ""; var completed = this.autoComplete (this.list.options[tgt].text, this.lastInput, key, false); if (!completed) { this.text.value = this.list.options[tgt].text + key; } this.lastInput = this.list.options[tgt].text; } }, resetKeySelection : function () { if (noSuggestions || !this.list || this.list.style.display == 'none') return; var curr = this.list.selectedIndex; if (curr >= 0 && curr < this.list.options.length) { this.list.options[curr].selected = false; // Get current input text var v = this.text.value.split('|'); var key = v.length > 1 ? '|' + v[1] : ""; this.text.value = this.lastRealInput + key; this.lastInput = this.lastRealInput; } } }; // end CategoryEditor.prototype function initialize () { // User configurations. Do this here, called from the onload handler, so that users can // override it easily in their own user script files by just declaring variables. JSconfig // is some feature used at Wikimedia Commons. HotCat.no_autocommit = window.hotcat_no_autocommit || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatNoAutoCommit'] || 0; HotCat.suggest_delay = window.hotcat_suggestion_delay || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatSuggestionDelay'] || 100; HotCat.editbox_width = window.hotcat_editbox_width || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatEditBoxWidth'] || 40; HotCat.suggestions = window.hotcat_suggestions || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatSuggestions'] || 'combined'; if (typeof (HotCat.suggestions) != 'string' || !suggestionConfigs[HotCat.suggestions]) HotCat.suggestions = 'combined'; HotCat.fixed_search = window.hotcat_suggestions_fixed || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatFixedSuggestions'] || false; // Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]] is_rtl = hasClass (document.body, 'rtl'); if (!is_rtl) { if (document.defaultView && document.defaultView.getComputedStyle) { // Gecko etc. is_rtl = document.defaultView.getComputedStyle (document.body, null).getPropertyValue ('direction'); } else if (document.body.currentStyle) { // IE, has subtle differences to getComputedStyle is_rtl = document.body.currentStyle['direction']; } else { // Not exactly right, but best effort is_rtl = document.body.style['direction']; } is_rtl = (is_rtl == 'rtl'); } } function can_edit () { var container = null; switch (skin) { case 'cologneblue': container = document.getElementById ('quickbar'); // Fall through case 'standard': case 'nostalgia': if (!container) container = document.getElementById ('topbar'); var lks = container.getElementsByTagName ('a'); for (var i = 0; i < lks.length; i++) { if ( param ('title', lks[i].href) == wgPageName && param ('action', lks[i].href) == 'edit') return true; } return false; default: // all modern skins: return document.getElementById ('ca-edit') != null; } return false; } function setup_upload () { onUpload = true; // Add an empty category bar above the "watch this" box, and change the onsubmit handler. var ip = document.getElementById ('wpWatchthis'); if (!ip) return; var reupload = document.getElementById ('wpForReUpload'); var destFile = document.getElementById ('wpDestFile'); if ( (reupload && !!reupload.value) || (destFile && (destFile.disabled || destFile.readonly))) return; // re-upload form... // Insert a table row with two fields (label and empty category bar) ip = ip.parentNode.parentNode; // The containing <tr> var newRow = make ('tr'); var labelCell = make ('td'); var lineCell = make ('td'); newRow.appendChild (labelCell); newRow.appendChild (lineCell); // Create the category line catLine = make ('div'); catLine.className = 'catlinks'; catLine.id = 'catlinks'; catLine.style.textAlign = 'left'; lineCell.appendChild (catLine); // Create the label var label = null; if (typeof (UFUI) != 'undefined' && typeof (UFUI.getLabel) == 'function') { try { label = UFUI.getLabel ('wpCategoriesUploadLbl'); } catch (ex) { label = null; } } if (!label) { labelCell.id = 'hotcatLabel'; labelCell.appendChild (make (HotCat.categories), true); } else { labelCell.id = 'hotcatLabelTranslated'; labelCell.appendChild (label); } labelCell.className = 'mw-label'; labelCell.style.textAlign = 'right'; labelCell.style.verticalAlign = 'middle'; // Change the onsubmit handler var form = document.getElementById ('upload') || document.getElementById ('mw-upload-form'); if (form) { var optionsTable = document.getElementById ('mw-htmlform-options'); if (optionsTable) optionsTable.width = '100%'; ip.parentNode.insertBefore (newRow, ip); textHeight = labelCell.firstChild.offsetHeight; form.onsubmit = (function (oldSubmit) { return function () { var do_submit = true; if (oldSubmit) { if (typeof (oldSubmit) == 'string') do_submit = eval (oldSubmit); else if (typeof (oldSubmit) == 'function') do_submit = oldSubmit.apply (form, arguments); } if (!do_submit) return false; closeForm (); // Copy the categories var eb = document.getElementById ('wpUploadDescription') || document.getElementById ('wpDesc'); for (var i = 0; i < editors.length; i++) { var t = editors[i].currentCategory; if (!t) continue ; var key = editors[i].currentKey; var new_cat = '[[' + HotCat.category_canonical + ':' + t + (key ? '|' + key : "") + ']]'; // Only add if not already present var cleanedText = eb.value.replace(/<\!--(\s|\S)*?--\>/g, "") .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, ""); if (!find_category (cleanedText, t, true)) { eb.value += '\n' + new_cat; } } return true; }; }) (form.onsubmit); } } var cleanedText = null; function isOnPage (span) { var catTitle = title (span.firstChild.getAttribute ('href', 2)); if (!catTitle) return null; catTitle = catTitle.substr (catTitle.indexOf (':') + 1).replace (/_/g, ' '); var result = { title : catTitle, match : ["", "", ""] }; if (pageText === null) return result; if (cleanedText === null) { cleanedText = pageText.replace(/<\!--(\s|\S)*?--\>/g, "") .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, ""); } result.match = find_category (cleanedText, catTitle, true); return result; } var initialized = false; function setup () { if (initialized) return; initialized = true; // Find the category bar, or create an empty one if there isn't one. Then add -/+- links after // each category, and add the + link. catLine = catLine // Special:Upload || document.getElementById ('mw-normal-catlinks') // MW >= 1.13alpha || getElementsByClassName (document , 'p' , 'catlinks')[0]; // MW < 1.13 var hiddenCats = document.getElementById ('mw-hidden-catlinks'); if (!catLine) { var footer = null; if (!hiddenCats) { footer = getElementsByClassName (document , 'div' , 'printfooter')[0]; if (!footer) return; // Don't know where to insert the category line } catLine = make ('div'); catLine.id = 'mw-normal-catlinks'; catLine.style.textAlign = 'left'; // Add a label var label = make ('a'); label.href = wgArticlePath.replace ('$1', 'Special:Categories'); label.title = HotCat.categories; label.appendChild (make (HotCat.categories, true)); catLine.appendChild (label); catLine.appendChild (make (':', true)); // Insert the new category line var container = (hiddenCats ? hiddenCats.parentNode : document.getElementById ('catlinks')); if (!container) { container = make ('div'); container.id = 'catlinks'; footer.parentNode.insertBefore (container, footer.nextSibling); } container.className = 'catlinks noprint'; container.style.display = ""; if (!hiddenCats) { container.appendChild (catLine); } else { container.insertBefore (catLine, hiddenCats); } textHeight = label.firstChild.offsetHeight; } // end if catLine exists catLine.style.position = 'relative'; if (is_rtl) catLine.dir = 'rtl'; // Create editors for all existing categories function createEditors (line) { var cats = line.getElementsByTagName ('span'); // Copy cats, otherwise it'll also magically contain our added spans as it is a live collection! var copyCats = new Array (cats.length); for (var i = 0; i < cats.length; i++) copyCats[i] = cats[i]; var editor = null; for (var i = 0; i < copyCats.length; i++) { var test = isOnPage (copyCats[i]); if (test !== null && test.match !== null) { editor = new CategoryEditor (line, copyCats[i], test.title, test.match[2]); } } return copyCats.length > 0 ? copyCats[copyCats.length-1] : null; } var lastSpan = createEditors (catLine); // Create one to add a new category var editor = new CategoryEditor(catLine, null, null, lastSpan != null); if (!onUpload) { if (pageText !== null && hiddenCats) { hiddenCats.style.position = 'relative'; if (is_rtl) hiddenCats.dir = 'rtl'; createEditors (hiddenCats); } // And finally add the "multi-mode" span. (Do this at the end, otherwise it ends up in the list above.) var enableMulti = make ('span'); enableMulti.className = 'noprint'; if (is_rtl) enableMulti.dir = 'rtl'; catLine.insertBefore (enableMulti, catLine.firstChild.nextSibling); enableMulti.appendChild (make ('\xa0', true)); // nbsp multiSpan = make ('span'); enableMulti.appendChild (multiSpan); multiSpan.innerHTML = '(<a>' + HotCat.addmulti + '</a>)'; var lk = multiSpan.getElementsByTagName ('a')[0]; lk.onclick = function (evt) {setMultiInput (); return evtKill (evt);}; lk.style.cursor = 'pointer'; } cleanedText = null; } function setPage (json) { if (!json || !json.query || !json.query.pages) return; for (var p in json.query.pages) { var page = json.query.pages[p]; if (!page.revisions || page.revisions.length == 0) break; pageText = page.revisions[0]['*']; pageTime = page.revisions[0].timestamp.replace (/\D/g, ""); break; } } function getPage () { // We know we have an article here. if (wgArticleId == 0) { // Doesn't exist yet. pageText = ""; pageTime = null; setup (); } else { var url = wgServer + wgScriptPath + '/api.php?format=json&callback=HotCat.start&action=query&titles=' + encodeURIComponent (wgPageName) + '&prop=info%7Crevisions&rvprop=content%7Ctimestamp'; var s = make ('script'); s.src = url; s.type = 'text/javascript'; HotCat.start = function (json) { setPage (json); setup (); }; document.getElementsByTagName ('head')[0].appendChild (s); window.setTimeout (setup, 4000); // 4 seconds. Just in case getting the wikitext takes longer. } } function run () { initialize (); if (is_rtl && window.ie6_bugs) return; // Disabled! IE6 with RTL is just too broken... if (wgNamespaceNumber == -1 && wgCanonicalSpecialPageName == 'Upload' && wgUserName) { setup_upload (); setup (); // Check for state restoration if ( typeof (UploadForm) != 'undefined' && typeof (UploadForm.previous_hotcat_state) != 'undefined' && UploadForm.previous_hotcat_state != null) UploadForm.previous_hotcat_state = setState (UploadForm.previous_hotcat_state); } else { if (!wgIsArticle || wgAction != 'view' || !can_edit() || HotCat.disable()) return; getPage (); } } // Legacy stuff function closeForm () { // Close all open editors without redirect resolution and other asynchronous stuff. for (var i = 0; i < editors.length; i++) { if (editors[i].state == CategoryEditor.OPEN) { editors[i].cancel(); } else if (editors[i].state == CategoryEditor.CHANGE_PENDING) { editors[i].sanitizeInput (); var value = editors[i].text.value.split('|'); var key = null; if (value.length > 1) key = value[1]; var v = value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, ""); if (v.length == 0) { editors[i].cancel (); } else { editors[i].currentCategory = v; editors[i].currentKey = key; editors[i].currentExists = this.inputExists; editors[i].close (); } } } } function getState () { var result = null; for (var i = 0; i < editors.length; i++) { var text = editors[i].currentCategory; var key = editors[i].currentKey; if (text && text.length > 0) { if (key && key.length > 0) text += '|' + key; if (result == null) result = text; else result = result + '\n' + text; } } return result; } function setState (state) { var cats = state.split ('\n'); if (cats.length == 0) return null; if (initialized && editors.length == 1 && editors[0].isAddCategory) { // Insert new spans and create new editors for them. var newSpans = []; var before = editors.length == 1 ? editors[0].span : null; for (var i = 0; i < cats.length; i++) { if (cats[i].length == 0) continue; var cat = cats[i].split ('|'); var key = cat.length > 1 ? cat[1] : null; cat = cat[0]; var lk = make ('a'); lk.href = wgArticlePath.split ('$1').join ('Category:' + encodeURI (cat)); lk.appendChild (make (cat, true)); lk.title = cat; var span = make ('span'); span.appendChild (lk); if (i == 0) catLine.insertBefore (make (' ', true), before); catLine.insertBefore (span, before); if (before && i+1 < cats.length) parent.insertBefore (make (' | ', true), before); newSpans.push ({element: span, title: cat, 'key': key}); } // And change the last one... if (before) { before.parentNode.insertBefore (make (' | ', true), before); } var editor = null; for (var i = 0; i < newSpans.length; i++) { editor = new CategoryEditor (catLine, newSpans[i].element, newSpans[i].title, newSpans[i].key); } } return null; } // Now export these legacy functions window.hotcat_get_state = function () { return getState(); }; window.hotcat_set_state = function (state) { return setState (state); }; window.hotcat_close_form = function () { closeForm (); }; addOnloadHook (run); })(); } // end if (guard) //</source>