User:Zrosz/labelcollect2.js
Jump to navigation
Jump to search
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/**
Label Collector
By Zrosz, [[User:Zrosz]]
Using Steven Levithan's XRegExp 2.0 library, MIT License, http://xregexp.com/
See [[User:Zrosz/LC]] for documentation
<nowiki>
*/
(function() {
"use strict";
// Supported languages
// Regular expressions in XRegExp syntax (http://xregexp.com/syntax/), main magic happening in "suggest":
// - descAnd: term used to concatenate two values a and b with "and", as in "actor and singer" (must contain two named groups a and b)
// - descBy: term used to concatenate two values a and b with "by", as in "book by Stephen King" (must contain two named groups a and b)
// - descFromPlace: term used to concatenate two values a and b with "from", as in "band from Germany" (must contain two named groups a and b) [currently not used]
// - descFromTime: term used to concatenate two values a and b with "from", as in "film from 1980" (must contain two named groups a and b; note that in English this is empty: "{b} {a}" -> "1980 film")
// - descIn: term used to concatenate two values a and b with "in", as in "town in China" (must contain two named groups a and b)
// - descOf: term used to concatenate two values a and b with "of", as in "episode of Breaking Bad" (must contain two named groups a and b)
// - separator: sentence separator (must contain a named group "sentence" to return the sentence), used to give only the sentence of the article if no better suggestion can be made
// - suggest: main expression to find the description suggestion in the article text (must contain a named group "desc" to return the suggestion)
// Other data:
// - script: writing system used in that language; used to suggest labels for all languages that use the same script if all existing labels of that script are the same
// - sisters: languages that should use the same rules and projects than this language (e.g. en-gb should use the same rules as en, and it should take contents from enwiki & Co)
// If something isn't defined for one language, it will fall back to "defaultLang"
var languageData = {
"defaultLang": {
"separator": "^(?<sentence>.*?([ \\(](?!(ca|Dr|etc|I|II|IV|IX|Inc|Jr|Ltd|Mr|Mrs|Ms|St|V|VI|X|XI|XV|XX)\\.)\\p{Alphabetic}{2,}|\\p{Alphabetic}{4,}|\\d{4}|\\d\\p{Alphabetic}|\\)))\\.(\\s.*)?$",
"descAnd": "{a} & {b}",
"descBy": "{a}, {b}",
"descFromPlace": "{a}, {b}",
"descFromTime": "{b} {a}",
"descIn": "{a}, {b}",
"descOf": "{b} {a}"
},
"af": {
"suggest": "( (is) (die|('|’)n)) (?<desc>.*)$",
"descAnd": "{a} en {b}",
"descBy": "{a} deur {b}",
"descFromPlace": "{a} van {b}",
"descFromTime": "{b}-{a}",
"descIn": "{a} in {b}",
"script": "Latin"
},
"az": {
"suggest": "( (-|–|—)) (?<desc>.*)$",
"descAnd": "{a} və {b}",
"script": "Latin"
},
"bg": {
"suggest": "( (са|е)( професионална)?) (?<desc>.*)$",
"descAnd": "{a} и {b}",
"descBy": "{a} на {b}",
"descFromPlace": "{a} от {b}",
"descFromTime": "{a} от {b}",
"descIn": "{a} в {b}",
"script": "Cyrillic"
},
"ca": {
"suggest": "( (és|fou|són) (el |l('|’)|la |una? ))(?<desc>.*)$",
"descAnd": "{a} i {b}",
"descBy": "{a} de {b}",
"descFromPlace": "{a} de {b}",
"descFromTime": "{a} el {b}",
"descIn": "{a} a {b}",
"script": "Latin"
},
"cs": {
"suggest": "( (byl(a|i|o|y)?|je|jsou))( znám(á|é|í|ý)? jako)?( ((proslul|slavn|takzvan|významn|znám)(á|é|í|ý)|přední)| tzv\\.)? (?<desc>.*?)(, (pokračovatel|proslul|proslaven|slavn|významn|znám).*)?(, v (letech|roce).*|,( do)? roku|, po roce)?(, (ve |se |do )?kte(r|ř).*|, jen?ž.*)?$",
"descAnd": "{a} a {b}",
"descFromPlace": "{a} ze {b}",
"descFromTime": "{a} z {b}",
"descIn": "{a} v {b}",
"script": "Latin"
},
"da": {
"suggest": "( (er|var) (en|et)?) (?<desc>.*)$",
"descAnd": "{a} og {b}",
"descBy": "{a} af {b}",
"descFromPlace": "{a} fra {b}",
"descFromTime": "{a} fra {b}",
"descIn": "{a} i {b}",
"script": "Latin"
},
"de": {
"sisters": [ "de-at", "de-ch" ],
"suggest": "( ((ist|sind|war(en)?)( (das|der|die|ein(e|er)?))( ehemaliger?)?( bekannter?| professioneller?)?)|((bezeichnet|nennt)( man)? (das|den|die|ein(en?)?))) (?<desc>.*?)(, (das|der|die|welcher?).*)?$",
"descAnd": "{a} und {b}",
"descBy": "{a} von {b}",
"descFromPlace": "{a} aus {b}",
"descFromTime": "{a} von {b}",
"descIn": "{a} in {b}",
"descOf": "{a} von {b}",
"script": "Latin"
},
"el": {
"suggest": "( (είναι|ήταν)( (ο|η))?) (?<desc>.*)$",
"descAnd": "{a} και {b}",
"descBy": "{a} των {b}",
"descFromPlace": "{a} από {b}",
"script": "Greek"
},
"en": {
"sisters": [ "en-ca", "en-gb" ],
"suggest": "( ((are|has been|have been|is|was|were) (an?|the)( defunct| extinct| former| retired)?( award-winning| discovered (on|by)| distinguished| famous| influential| noted| professional| renowned| well-known)?)|(refers to( an?| the)?)) (?<desc>.*?)((,? )(and )?(best known|currently|famous|focuss?ing|known|most notably|playing|probably|starring|that|which|who).*)?$",
"descAnd": "{a} and {b}",
"descBy": "{a} by {b}",
"descFromPlace": "{a} from {b}",
"descFromTime": "{b} {a}",
"descIn": "{a} in {b}",
"descOf": "{a} of {b}",
"script": "Latin"
},
"eo": {
"suggest": "( (est(a|i)s)( la)?) (?<desc>.*)$",
"descAnd": "{a} kaj {b}",
"descBy": "{a} de {b}",
"descFromPlace": "{a} de {b}",
"descFromTime": "{a} de {b}",
"descIn": "{a} en {b}",
"script": "Latin"
},
"es": {
"suggest": "( (es|fue|son)( (el|la|le|un(a|o)?))( ex)?) (?<desc>.*?)(,? (que).*)?$",
"descAnd": "{a} y {b}",
"descBy": "{a} de {b}",
"descFromPlace": "{a} de {b}",
"descFromTime": "{a} de {b}",
"descIn": "{a} de {b}",
"descOf": "{a} de {b}",
"script": "Latin"
},
"et": {
"suggest": "( (olid?|on)) (?<desc>.*)$",
"descAnd": "{a} ja {b}",
"descFromTime": "{a} on {b}",
"script": "Latin"
},
"fi": {
"suggest": "( (oli|on)) (?<desc>.*)$",
"descAnd": "{a} ja {b}",
"descFromTime": "{a} vuodelta {b}",
"script": "Latin"
},
"fr": {
"suggest": "( (est|était|sera|seront|sont) (l('|’)|la |les? |une? )(ancien )?)(?<desc>.*?)((,? connu|,? née?|,? qui|, spécialiste).*)?$",
"descAnd": "{a} et {b}",
"descBy": "{a} de {b}",
"descFromPlace": "{a} de {b}",
"descFromTime": "{a} sorti en {b}",
"descIn": "{a} en {b}",
"descOf": "{a} de {b}",
"script": "Latin"
},
"gan": {
"separator": "^(?<sentence>[^。]*)。",
"suggest": "(係|系|是)(?<desc>.*)$",
"descAnd": "{a}和{b}",
"descIn": "{a}在{b}",
"script": "Chinese"
},
"gl": {
"suggest": "( (é|e|es|foi|son) (o|unh?a?)) (?<desc>.*)$",
"descAnd": "{a} e {b}",
"descBy": "{a} de {b}",
"descFromPlace": "{a} de {b}",
"descFromTime": "{a} de {b}",
"descIn": "{a} en {b}",
"script": "Latin"
},
"gsw": {
"suggest": "( (isch) (de|dr|e|s)) (?<desc>.*)$",
"descAnd": "{a} un {b}",
"descBy": "{a} vo {b}",
"descFromPlace": "{a} us {b}",
"descFromTime": "{a} vo {b}",
"descIn": "{a} in {b}",
"script": "Latin"
},
"hr": {
"suggest": "(( bio)? (je)( bio)?) (?<desc>.*)$",
"descAnd": "{a} i {b}",
"descFromPlace": "{a} iz {b}",
"descFromTime": "{a} iz {b}",
"descIn": "{a} u {b}",
"script": "Latin"
},
"hu": {
"suggest": "( (egy)) (?<desc>.*)$",
"descAnd": "{a} és {b}",
"script": "Latin"
},
"id": {
"suggest": "( (adalah|merupakan)( sebuah| seorang)?) (?<desc>.*)$",
"descAnd": "{a} dan {b}",
"descFromPlace": "{a} dari {b}",
"descFromTime": "{a} dari tahun {b}",
"descIn": "{a} di {b}",
"script": "Latin"
},
"it": {
"suggest": "( (è|sono) (il |l('|’)|una? ))(?<desc>.*)$",
"descAnd": "{a} e {b}",
"descBy": "{a} da {b}",
"descFromPlace": "{a} di {b}",
"descFromTime": "{a} del {b}",
"descIn": "{a} in {b}",
"script": "Latin"
},
"ja": {
"separator": "^(?<sentence>[^。]*)。",
"suggest": "(は)(?<desc>.*)$",
"descAnd": "{a}と{b}",
"descBy": "{a}の{b}",
"descIn": "{a}の{b}",
"script": "Japanese"
},
"kk": {
"suggest": "( (-|–|—)) (?<desc>.*)$",
"script": "Cyrillic"
},
"ko": {
"suggest": "(이다? )(?<desc>.*)$",
"script": "Korean"
},
"la": {
"suggest": "( est) (?<desc>.*)$",
"descAnd": "{a} et {b}",
"descFromPlace": "{a} e {b}",
"descFromTime": "{a} a {b}",
"script": "Latin"
},
"lb": {
"suggest": "( (ass|sinn) (den|e|eng)) (?<desc>.*)$",
"script": "Latin"
},
"lt": {
"suggest": "( (-|–|—|yra)) (?<desc>.*)$",
"descAnd": "{a} ir {b}",
"script": "Latin"
},
"lzh": {
"sisters": [ "zh-classical" ],
"separator": "^(?<sentence>[^。]*)。",
"suggest": "(者|,){1,2}為?(?<desc>.*)也?$",
"descAnd": "{a}及{b}",
"descIn": "{a}在{b}",
"script": "Chinese"
},
"ms": {
"suggest": "( (adalah|ialah|merupakan)( sebuah)?) (?<desc>.*)$",
"descAnd": "{a} dan {b}",
"descFromPlace": "{a} dari {b}",
"descFromTime": "{a} dari {b}",
"descIn": "{a} di {b}",
"script": "Latin"
},
"nb": {
"suggest": "( (er|var) (en|et)?) (?<desc>.*)$",
"descAnd": "{a} og {b}",
"descBy": "{a} av {b}",
"descFromPlace": "{a} fra {b}",
"descFromTime": "{a} fra {b}",
"descIn": "{a} i {b}",
"script": "Latin"
},
"nl": {
"suggest": "( (is|was|zijn) (de|een|één)) (?<desc>.*)$",
"descAnd": "{a} en {b}",
"descBy": "{a} van {b}",
"descFromPlace": "{a} van {b}",
"descFromTime": "{a} uit {b}",
"descIn": "{a} in {b}",
"script": "Latin"
},
"nn": {
"suggest": "( (er|var) (den|ei(n|t)?)) (?<desc>.*)$",
"descAnd": "{a} og {b}",
"descBy": "{a} av {b}",
"descFromPlace": "{a} fra {b}",
"descFromTime": "{a} frå {b}",
"descIn": "{a} i {b}",
"script": "Latin"
},
"oc": {
"suggest": "( (es|foguèt|son)( una?)?) (?<desc>.*)$",
"descAnd": "{a} e {b}",
"script": "Latin"
},
"pl": {
"suggest": "( (-|–|—|byly?|jest)) (?<desc>.*)$",
"descAnd": "{a} a {b}",
"descBy": "{a} z {b}",
"descFromPlace": "{a} z {b}",
"descFromTime": "{a} z {b}",
"descIn": "{a} w {b}",
"script": "Latin"
},
"pt": {
"sisters": [ "pt-br" ],
"suggest": "( (é|foi) (a|o|uma?)) (?<desc>.*)$",
"descAnd": "{a} e {b}",
"descBy": "{a} do {b}",
"descFromPlace": "{a} de {b}",
"descFromTime": "{a} de {b}",
"descIn": "{a} na {b}",
"script": "Latin"
},
"ro": {
"suggest": "( (a fost|este)( cel| o| una?)?) (?<desc>.*)$",
"descAnd": "{a} și {b}",
"descBy": "{a} de {b}",
"descFromPlace": "{a} din {b}",
"descFromTime": "{a} din {b}",
"descIn": "{a} în {b}",
"script": "Latin"
},
"ru": {
"suggest": "( (-|–|—)) (?<desc>.*)$",
"descAnd": "{a} и {b}",
"descFromPlace": "{a} из {b}",
"descIn": "{a} в {b}",
"script": "Cyrillic"
},
"sh": {
"suggest": "( (je)( (bio|na))?) (?<desc>.*)$",
"descFromTime": "{a} iz {b}",
"script": "Latin"
},
"sk": {
"suggest": "( (bol(a|i|o)?|je|sú))( významn(á|é|ý))? (?<desc>.*?)(, ktor.*)?$",
"descAnd": "{a} a {b}",
"descFromPlace": "{a} z {b}",
"descFromTime": "{a} z {b}",
"descIn": "{a} v {b}",
"script": "Latin"
},
"sl": {
"suggest": "( je) (?<desc>.*)$",
"descAnd": "{a} in {b}",
"descFromPlace": "{a} iz {b}",
"descFromTime": "{a} iz {b}",
"descIn": "{a} v {b}",
"script": "Latin"
},
"sr": {
"suggest": "( (је)) (?<desc>.*)$",
"descAnd": "{a} и {b}",
"descFromPlace": "{a} из {b}",
"descFromTime": "{a} из {b}",
"descIn": "{a} у {b}",
"script": "Cyrillic"
},
"sv": {
"suggest": "( (är|var) (en|ett)) (?<desc>.*)$",
"descAnd": "{a} och {b}",
"descBy": "{a} av {b}",
"descFromPlace": "{a} från {b}",
"descFromTime": "{a} från {b}",
"descIn": "{a} i {b}",
"script": "Latin"
},
"uk": {
"suggest": "((-|–|—)) (?<desc>.*)$",
"descAnd": "{a} та {b}",
"descFromPlace": "{a} із {b}",
"descIn": "{a} в {b}",
"script": "Cyrillic"
},
"vi": {
"suggest": "( (là)( tên)? (m?t)) (?<desc>.*)$",
"descAnd": "{a} và {b}",
"descBy": "{a} của {b}",
"descIn": "{a} ở {b}",
"script": "Latin-QuocNgu"
},
"wuu": {
"separator": "^(?<sentence>[^。]*)。",
"suggest": "(是)(?<desc>.*)$",
"descAnd": "{a}搭{b}",
"descIn": "{a}勒勒{b}",
"script": "Chinese"
},
"yue": {
"sisters": [ "zh-yue" ],
"separator": "^(?<sentence>[^。]*)。",
"suggest": "(喺|係|系|是)(?<desc>.*)$",
"descAnd": "{a}同{b}",
"descIn": "{a}在{b}",
"script": "Chinese"
},
"zh": {
"separator": "^(?<sentence>[^。]*)。",
"suggest": "(是|为|為)(?<desc>.*)$",
"descAnd": "{a}和{b}",
"descIn": "{a}在{b}",
"script": "Chinese"
}
};
// Language remappings (per https://noc.wikimedia.org/conf/InitialiseSettings.php.txt)
var realLanguage = {
"als": "gsw",
"bat_smg": "sgs",
"be_x_old": "be-tarask",
"bh": "bho",
"crh": "crh-latn",
"fiu_vro": "vro",
"no": "nb",
"roa_rup": "rup",
"simple": "en",
"zh_classical": "lzh",
"zh_min_nan": "nan",
"zh_yue": "yue"
};
// i18n Labels
// Skip a translation to fall back to English version
var i18n = {
"intError": {
"de": "Fehler: {txt}",
"en": "Error: {txt}"
},
"intFailedToLoad": {
"de": "Fehler beim Laden: ",
"en": "Failed to load: "
},
"intLabelMissing": {
"de": "Fehler: Text fehlt",
"en": "Error: Text missing"
},
"intTitle": {
"en": "Label Collector"
},
"intTitleShort": {
"en": "LC"
},
"btnActions": {
"de": "Aktionen",
"en": "Actions"
},
"btnAdd": {
"de": "Sprache hinzufügen",
"en": "Add language"
},
"btnClearAll": {
"de": "Alle Bezeichner, Beschreibungen und Aliasse leeren/löschen",
"en": "Clear (empty) all labels, descriptions, aliases"
},
"btnClose": {
"de": "Schließen",
"en": "Close"
},
"btnExpandCollapseAll": {
"de": "Alle auf-/zuklappen",
"en": "Expand/collapse all"
},
"btnJumpTo": {
"de": "Los",
"en": "Go"
},
"btnOkay": {
"en": "Okay"
},
"btnOptions": {
"de": "Optionen",
"en": "Options"
},
"btnResetAll": {
"de": "Alle Bezeichner, Beschreibungen und Aliasse zurücksetzen",
"en": "Reset all labels, descriptions, aliases to existing values"
},
"btnSaveClose": {
"de": "Speichern & Schließen",
"en": "Save & Close"
},
"btnSaveNext": {
"de": "Speichern & Nächstes",
"en": "Save & Next"
},
"btnSkip": {
"de": "Überspringen",
"en": "Skip"
},
"btnSuggest": {
"en": "?!"
},
"btnReset": {
"en": "x"
},
"fldJumpTo": {
"de": "Q..., User:..., Special:Random oder Special:Search/...",
"en": "Q..., User:..., Special:Random or Special:Search/..."
},
"lnkContributions": {
"de": "Beiträge",
"en": "Contribs"
},
"lnkFeedback": {
"en": "Feedback"
},
"lnkHelp": {
"de": "Hilfe",
"en": "Help"
},
"lnkHistory": {
"de": "Versionen",
"en": "history"
},
"lnkTalkPage": {
"de": "Diskussion",
"en": "Talk"
},
"lnkWatchlist": {
"de": "Beobachtung",
"en": "Watchlist"
},
"ttExistingAliases": {
"de": "Bestehende Aliasse: {txt}",
"en": "Existing aliases: {txt}"
},
"ttExistingDescription": {
"de": "Bestehende Beschreibung: {txt}",
"en": "Existing description: {txt}"
},
"ttExistingLabel": {
"de": "Bestehender Bezeichner: {txt}",
"en": "Existing label: {txt}"
},
"ttReset": {
"de": "Zurücksetzen zum bestehenden Wert",
"en": "Reset to existing value"
},
"ttSuggest": {
"de": "Setze den Vorschlag ein: {txt}",
"en": "Insert suggestion: {txt}"
},
"ttSuggestNone": {
"de": "Kein Vorschlag",
"en": "No suggestion"
},
"ttWontSave": {
"de": "Achtung: Evt. vorhandenene Änderungen werden nicht gespeichert",
"en": "Attention: Any changes made here won't be saved"
},
"txtAlias": {
"de": "Alias",
"en": "alias"
},
"txtAliases": {
"de": "Aliasse",
"en": "aliases"
},
"txtAlwaysExpandEditorLangs": {
"de": "Editor-Sprachen immer ausklappen",
"en": "Always expand editor languages"
},
"txtAlwaysExpandOtherLangs": {
"de": "Nicht-Editor-Sprachen immer ausklappen",
"en": "Always expand non-editor languages"
},
"txtApiError": {
"de": "API-Fehler",
"en": "API error"
},
"txtAutoFill": {
"de": "Vorschläge für Bezeichner, Beschreibungen und Aliasse automatisch eintragen",
"en": "Automatically fill proposals for labels, descriptions and aliasses"
},
"txtCleanDescription": {
"de": "Beschreibung automatisch bereinigen (Wiki-Markup, doppelte Leerzeichen, etc.)",
"en": "Automatically clean description (wiki markup, double spaces, etc.)"
},
"txtDebugMode": {
"de": "Debug-Modus",
"en": "Debug mode"
},
"txtFailedToLoadNext": {
"de": "Nächstes Item konnte nicht geladen werden.",
"en": "Failed to load next item."
},
"txtFailedToLoadSearchResults": {
"de": "Suche nach '{term}' konnte nicht ausgeführt werden oder ergab keine Ergebnisse.",
"en": "Search for '{term}' could not be performed or did not deliver any results."
},
"txtFailedToLoadUsersItems": {
"de": "Bearbeitete Items des Benutzers {term} konnten nicht geladen werden.",
"en": "Failed to load items edited by user {term}."
},
"txtFailedToItem": {
"de": "Item {txt} konnte nicht geladen werden.",
"en": "Failed to load item {txt}."
},
"txtFailedToParseIntro": {
"de": "Fehler beim Verarbeiten des Artikeltexts von {db}: {txt}",
"en": "Failed to parse {db} article intro: {txt}"
},
"txtFailedToReadInput": {
"de": "Eingabe ist keine Item-ID und kein Benutzername: {txt}",
"en": "Input is no item ID or user name: {txt}"
},
"txtInvalidProject": {
"de": "Ungültige Projektdefinition: {txt}",
"en": "Invalid project definition: {txt}"
},
"txtInvalidLanguage": {
"de": "{code} ist keine gültige Sprache. Die Änderung wird ignoriert.",
"en": "{code} is no valid language. The change will be ignored."
},
"txtInvalidLanguageToAdd": {
"de": "{code} ist keine gültige Sprache, oder bereits vorhanden.",
"en": "{code} is no valid language, or already present."
},
"txtInvalidRegex": {
"de": "Ungültiger regulärer Ausdruck für {db}: {txt}",
"en": "Invalid regular expression for {db}: {txt}"
},
"txtLanguage": {
"de": "Sprachcode:",
"en": "Language code:"
},
"txtLoading": {
"de": "lädt...",
"en": "loading..."
},
"txtNoSitelink": {
"de": "(Keine Sitelinks)",
"en": "(No sitelinks)"
},
"txtNothing": {
"en": "—"
},
"txtNothingToSave": {
"de": "Keine Änderung zu speichern",
"en": "Nothing to save"
},
"txtRedirect": {
"de": "(Weiterleitung)",
"en": "(Redirect)"
},
"txtSavingFailed": {
"de": "Speichern fehlgeschlagen: {txt}",
"en": "Saving failed: {txt}"
},
"txtSkipIfEditorLangsSetOrNoLinks": {
"de": "Items überspringen, wenn für alle Editor-Sprachen bereits Bezeichner und Beschreibungen gesetzt sind oder keine Links vorhanden sind",
"en": "Skip items if all editor languages labels and descriptions are set already or there are no sitelinks for them"
},
"txtSkipIfEditorLangsSet": {
"de": "Items überspringen, wenn für alle Editor-Sprachen bereits Bezeichner und Beschreibungen gesetzt sind",
"en": "Skip items if all editor languages labels and descriptions are set already"
},
"txtEditorLanguages": {
"de": "Editor-Sprachen: ",
"en": "Editor languages: "
},
"statusInitItem": {
"de": "Initialisiere Item...",
"en": "Initializing item..."
},
"statusInitProjects": {
"de": "Initialisiere Sprachen und Projekte...",
"en": "Initializing languages and projects..."
},
"statusLoadBabel": {
"de": "Lade Babel-Sprachen von der Benutzerseite [[{userpage}]]...",
"en": "Loading Babel languages from userpage [[{userpage}]]..."
},
"statusLoadEntity": {
"de": "Lade Item {qid}...",
"en": "Loading item {qid}..."
},
"statusLoadItemLabels": {
"de": "Lade Labels von verwendeten Items: {qids}",
"en": "Loading external item labels: {qids}"
},
"statusLoadNextItem": {
"de": "Suche nächstes Item zum Bearbeiten, ab Q{qid}...",
"en": "Searching for next item to edit, starting from Q{qid}..."
},
"statusLoadSearchResults": {
"de": "Lade Items mit dem Suchstring '{term}'...",
"en": "Load items containing search string '{term}'..."
},
"statusLoadUserEdits": {
"de": "Lade bearbeitete Items des Benutzers {term}...",
"en": "Load items edited by user {term}..."
},
"statusLoadXRegExp": {
"de": "Lade XRegExp-Bibliothek: ",
"en": "Loading XRegExp library: "
}
};
// Used properties
var properties = {
"administrativeType": 132,
"administrativeUnit": 131,
"author": 50,
"astronomicalObject": 60,
"citizenship": 27,
"composer": 86,
"continent": 30,
"country": 17,
"creator": 170,
"dateOfPublication": 577,
"designer": 287,
"developer": 178,
"director": 57,
"editor": 98,
"electionType": 173,
"instance": 31,
"lakeType": 202,
"locatedTerrain": 706,
"locatedWater": 206,
"manufacturer": 176,
"occupation": 106,
"performer": 175,
"producer": 162,
"publisher": 123,
"series": 179,
"structureType": 168,
"subclass": 279,
"taxonRank": 105,
"texter": 676
};
// Used items
var items = {
"disambiguation": 4167410,
"episode": 1983062,
"human": 5
};
// Supported projects
var projectCodes = {
"wikibooks": "b",
"wikinews": "n",
"wikipedia": "w",
"wikiquote": "q",
"wikisource": "s",
"wikivoyage": "voy"
};
// Colors
var colors = {
"background": "#B7C5E2",
"body": "#FFFFFF",
"border": "#8A9AB9",
"enabled": "#EEFFEE",
"existing": "#EEEEEE",
"headerCollapsed": "#C8C8D3",
"headerCollapsedBabel": "#C8C8D8",
"headerExpanded": "#8A9AB9",
"link": "#0645AD",
"linkVisited": "#0B0080",
"modified": "#EEFFEE",
"same": "#EEEEEE",
"talkAlert": "#FF0000",
"triangle": "#888888"
};
// CSS Classes
var classes = {
"alias": "lcAlias",
"babel": "lcBabel",
"buttons": "lcButtons",
"center": "lcCentered",
"collapsed": "lcCollapsed",
"desc": "lcDesc",
"ellipsis": "lcEllipsis",
"entry": "lcEntry",
"existing": "lcExisting",
"header": "lcHeader",
"headerLabel": "lcHeaderLabel",
"label": "lcLabel",
"main": "lcMain",
"mainButton": "lcMainButton",
"modified": "lcModified",
"mwSpinner": "mw-spinner",
"newValue": "lcNew",
"nonBabel": "lcNonBabel",
"preview": "lcPreview",
"secondButton": "lcSecondButton",
"talkAlert": "lcTalkpageAlert",
"top": "lcTop",
"triangle": "lcTriangle",
"triangleRight": "lcTriangleRight"
};
// CSS IDs
var ids = {
"aliasesExisting": "lcAliasesExisting_",
"aliasesNew": "lcAliasesNew_",
"descExisting": "lcDescExisting_",
"descNew": "lcDescNew_",
"header": "lcHeader",
"labelExisting": "lcLabelExisting_",
"labelNew": "lcLabelNew_",
"mwBodyContent": "bodyContent",
"pageTitle": "lcPageTitle",
"preview": "lcPreview_",
"previewArea": "lcPreviewArea_",
"suggestAlias": "lcSuggestAlias_",
"suggestDesc": "lcSuggestDesc_",
"suggestLabel": "lcSuggestLabel_",
"talkPage": "lcTalkPage",
"userNav": "lcUserNav"
};
// Interal configuration
var lcConfig = {
"aliasesSummaryTmp": "alias{plural} ({txt})",
"alwaysExpandEditorLangs": false,
"alwaysExpandOtherLangs": false,
"autoFill": true,
"checkNextItems": 50,
"checkUserContribs": 500,
"checkUserTalk": 10,
"cleanDescription": true,
"debugMode": false,
"descSummaryTmp": "description{plural} ({txt})",
"labelSummaryTmp": "label{plural} ({txt})",
"pageHistory": "//www.wikidata.org/w/index.php?action=history&title=",
"pageTool": "User:YMS/LC",
"pageToolTalk": "User talk:YMS/LC",
"pageUserContrib": "Special:Contributions/" + mw.config.get("wgUserName"),
"pageUserTalk": "User talk:" + mw.config.get("wgUserName"),
"pageUserWatch": "Special:Watchlist",
"skipIfEditorLangsSetOrNoLinks": true,
"skipIfEditorLangsSet": false,
"summaryTmp": "; [[User:YMS/LC|LC.js]]",
"timeout": 15000
};
// Internal global variables
var lcGlobal = {
"cachedItemLabels": {},
"claimValues": undefined,
"entity": "",
"globalLabel": {},
"hasMessages": false,
"instances": [],
"itemList": [],
"languages": {},
"saveCounter": 0,
"searchTerm": undefined,
"srOffset": undefined,
"usersContribs": undefined,
"itemPointer": 0,
"userLanguages": [],
"userUcContinue": undefined
};
// Startup
$(document).ready(function() {
debug("Start");
initTool();
});
// Shortcut method to avoid concatenation with "." all the time
function $getByClass(className) {
return $("." + className);
}
// Shortcut method to avoid concatenation with "#" all the time
function $getById(idName) {
return $("#" + idName);
}
// Adds an entry for a language not yet displayed
function addEntry(language, forceShow) {
// Get language data
var lang = language.lang;
var ignoreSitelinks = language.ignoreSitelinks;
var projects = language.projects;
var script = language.script;
// Detect sitelinks, label, description and aliases
var $sitelinksList = $("<span />");
var aliases = "";
var label = (lcGlobal.entity.labels === undefined || lcGlobal.entity.labels[lang] === undefined) ? "" : lcGlobal.entity.labels[lang].value;
var newLabel = "";
var sitelinks = [];
var $sitelinkEntry;
var code, db, value;
debug("addEntry", lang);
// Get sitelinks and labels
if (projects !== undefined && projects.length > 0) {
$.each(projects, function() {
db = this.db;
code = this.code;
if (lcGlobal.entity.sitelinks !== undefined && lcGlobal.entity.sitelinks[db] !== undefined) {
$sitelinkEntry = $("<b />").append($("<a />", {
"html": lcGlobal.entity.sitelinks[db].title,
"href": wikibase.sites.getSite(lcGlobal.entity.sitelinks[db].site).getUrlTo(lcGlobal.entity.sitelinks[db].title)
}));
if (sitelinks.length > 0) {
$sitelinksList.append(" / " + code + ":").append($sitelinkEntry);
} else {
$sitelinksList.append(code + ":").append($sitelinkEntry);
}
sitelinks.push(lcGlobal.entity.sitelinks[db]);
// Get label suggestion from title, if not set in Wikidata yet
if (newLabel === "") {
newLabel = parseLabel(lcGlobal.entity.sitelinks[db].title);
}
}
});
}
// Get aliases
if (lcGlobal.entity.aliases !== undefined && lcGlobal.entity.aliases[lang] !== undefined) {
$.each(lcGlobal.entity.aliases[lang], function() {
value = this.value;
aliases += ((aliases === "") ? "" : "|") + value;
});
}
// If all existing labels for one script system are the same, possibly set all other labels also
if (label !== "" && (newLabel === "" || newLabel === label)) {
if (lcGlobal.globalLabel[script] !== undefined && label !== lcGlobal.globalLabel[script]) {
lcGlobal.globalLabel[script] = "";
} else {
lcGlobal.globalLabel[script] = label;
}
}
// Suggest globalLabel for newly added entries
if (label === "" && newLabel === "" && checkGlobalLabelValidity(script)) {
newLabel = lcGlobal.globalLabel[lcGlobal.languages[lang].script];
}
showEntry(language, aliases, forceShow, newLabel, sitelinks, $sitelinksList);
}
// Adds an entry for a language not yet displayed
function addLanguage() {
var lang = window.prompt(getText(i18n.txtLanguage)).toLowerCase();
debug("addLanguage", lang);
if (lcGlobal.languages[lang] === undefined || $getById(ids.labelExisting + lang).length > 0) {
showMessage(getText(i18n.txtInvalidLanguageToAdd, { "code": lang }));
} else {
addEntry(lcGlobal.languages[lang], true);
$("body").animate({ "scrollTop": $getByClass(classes.main)[0].scrollHeight }, 300);
}
}
// Apply CSS definitions
function applyCSS() {
mw.util.addCSS(getText("body { padding: 2px 20px; background: {bg}; font-size: 100%; overflow-y: scroll; }",
{ "bg": colors.body }));
mw.util.addCSS(getText("a { color: {col}; }",
{ "col" : colors.link }));
mw.util.addCSS(getText("a:visited { color: {col}; }",
{ "col": colors.linkVisited }));
mw.util.addCSS(getText(".{class}#{id} { width: 100%; max-width: 100%; }",
{ "class": classes.main, "id": ids.mwBodyContent }));
mw.util.addCSS(getText(".{class} table { width: 100%; }",
{ "class": classes.main }));
mw.util.addCSS(getText(".{class} { background: {bg}; border: 1px solid {border}; margin: 0px; padding: 0px; }",
{ "class": classes.entry, "bg": colors.background, "border": colors.border }));
mw.util.addCSS(getText(".{class} > div { padding: 5px; }",
{ "class": classes.entry }));
mw.util.addCSS(getText(".{class} + .{childClass} { margin-top: 8px; }",
{ "class": classes.babel, "childClass": classes.nonBabel }));
mw.util.addCSS(getText(".{class} + .{childClass} { margin-top: 8px; }",
{ "class": classes.nonBabel, "childClass": classes.babel }));
mw.util.addCSS(getText(".{class} .{subClass} { background: {bg}; }",
{ "class": classes.babel, "subClass": classes.header, "bg": colors.headerCollapsedBabel }));
mw.util.addCSS(getText(".{class} .{subClass} { background: {bg}; }",
{ "class": classes.nonBabel, "subClass": classes.header, "bg": colors.headerCollapsed }));
mw.util.addCSS(getText(".{class}:not(.{subClass}) { background: {bg}; }",
{ "class": classes.header, "subClass": classes.collapsed, "bg": colors.headerExpanded }));
mw.util.addCSS(getText(".{class} table tr td:first-child { width: 15px; }",
{ "class": classes.header }));
mw.util.addCSS(getText(".{class} table tr td:nth-child(2) { white-space: nowrap; padding-right: 10px; }",
{ "class": classes.header }));
mw.util.addCSS(getText(".{class} { width: 100%; }",
{ "class": classes.headerLabel }));
mw.util.addCSS(getText(".{class} { width: 0px; height: 0px; border-top: none; border-right: none; border-bottom: 10px solid {border}; border-left: 10px solid transparent; }",
{ "class": classes.triangle, "border": colors.triangle }));
mw.util.addCSS(getText(".{class} { border-top: 6px solid transparent; border-right:none; border-bottom: 6px solid transparent; border-left: 10px solid {border}; }",
{ "class": classes.triangleRight, "border": colors.triangle }));
mw.util.addCSS(getText(".{class} table { border-spacing: 0px; }",
{ "class": classes.main }));
mw.util.addCSS(getText(".{class} { width: 40%; }",
{ "class": classes.existing }));
mw.util.addCSS(getText(".{class} { width: 60%; }",
{ "class": classes.newValue }));
mw.util.addCSS(getText(".{class} input[type=text] { background: {bg}; }",
{ "class": classes.existing, "bg": colors.existing }));
mw.util.addCSS(getText(".{class} input[type=text] { background: {bg}; }",
{ "class": classes.newValue, "bg": colors.same }));
mw.util.addCSS(getText(".{class} input[type=text].{subClass} { background: {bg}; }",
{ "class": classes.newValue, "subClass": classes.modified, "bg": colors.modified }));
mw.util.addCSS(getText(".{class} input[type=text] { width: 100%; font-weight: bold; }",
{ "class": classes.label }));
mw.util.addCSS(getText(".{class} input[type=text] { width: 100%; font-style: italic; }",
{ "class": classes.alias }));
mw.util.addCSS(getText(".{class} input[type=text] { width: 100%; }",
{ "class": classes.desc }));
mw.util.addCSS(getText(".{class} input[type=button] { margin: 0px; }",
{ "class": classes.main }));
mw.util.addCSS(getText(".{class} input[type=button]:enabled { background: {bg}; }",
{ "class": classes.main, "bg": colors.enabled }));
mw.util.addCSS(getText(".{class} table { table-layout: fixed }",
{ "class": classes.preview }));
mw.util.addCSS(getText(".{class} table tr td:first-child { width: 15px; }",
{ "class": classes.preview }));
mw.util.addCSS(getText(".{class} table tr td:nth-child(2) { width: 30px; }",
{ "class": classes.preview }));
mw.util.addCSS(getText(".{class} table tr td:last-child { width: 100%; }",
{ "class": classes.preview }));
mw.util.addCSS(getText(".{class} { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; width: 100%; }",
{ "class": classes.ellipsis }));
mw.util.addCSS(getText(".{class} p { line-height: 1em; }",
{ "class": classes.ellipsis }));
mw.util.addCSS(getText(".{class}:not(.{notClass}) .{subClass} { display: none; }",
{ "class": classes.header, "notClass": classes.collapsed, "subClass": classes.headerLabel }));
mw.util.addCSS(getText(".{class} table { width: 100%; margin-top: 10px; margin-bottom: 10px; }",
{ "class": classes.buttons }));
mw.util.addCSS(getText(".{class} input { padding: 10px; }",
{ "class": classes.buttons }));
mw.util.addCSS(getText(".{class} tr td:first-child { text-align: left; }",
{ "class": classes.buttons }));
mw.util.addCSS(getText(".{class} tr td:last-child { text-align: right; }",
{ "class": classes.buttons }));
mw.util.addCSS(getText(".{class}, .{secondclass} { min-width: 150px; }",
{ "class": classes.mainButton, "secondclass": classes.secondButton }));
mw.util.addCSS(getText(".{class} { font-weight: bold; }",
{ "class": classes.mainButton }));
mw.util.addCSS(getText("#{id} { text-align: right; list-style-type: none; list-style-image: none; margin: 0px; padding: 0px; font-size: 80%; }",
{ "id": ids.userNav }));
mw.util.addCSS(getText("#{id} li { display: inline; padding: 0px 5px; }",
{ "id": ids.userNav }));
mw.util.addCSS(getText(".{class}#{id} { color: {col}; font-weight: bold; }",
{ "class": classes.talkAlert, "col": colors.talkAlert, "id": ids.talkPage }));
mw.util.addCSS(getText("#{id} { text-align: left; font-size: 180%; width: 90%; }",
{ "id": ids.pageTitle }));
mw.util.addCSS(getText("#{id} sup { font-size: 50%; padding-left: 10px; }",
{ "id": ids.pageTitle }));
mw.util.addCSS(getText(".{class} { display: table; width: 100%; }",
{ "class": classes.top }));
mw.util.addCSS(getText(".{class} span, .{class} form, .{class} ul { display: table-cell; vertical-align: top; text-align: center; white-space: nowrap; }",
{ "class": classes.top }));
}
// Action for the "Save & Close" button
function buttonActionSaveClose() {
enableButtons(false);
saveItemReload();
}
// Action for the "Save & Next" button
function buttonActionSaveNext() {
enableButtons(false);
saveItemNext();
}
// Action for the "Skip" button
function buttonActionSkip() {
enableButtons(false);
loadNextItem();
}
// Action for the "Talk Page" link
function buttonActionTalkPage() {
lcGlobal.hasMessages = false;
$getById(ids.talkPage).toggleClass(classes.talkAlert, lcGlobal.hasMessages);
}
// Returns whether this item and this script system allows the application of the globalLabel suggestion
function checkGlobalLabelValidity(script) {
var hasGlobalLabel = (script !== undefined && lcGlobal.globalLabel[script] !== undefined && lcGlobal.globalLabel[script] !== "");
var isDisambig, isPerson;
if (lcGlobal.instances === undefined) {
debug("checkGlobalLabelValidity false", script);
return false;
}
isDisambig = inArray(items.disambiguation, lcGlobal.instances);
isPerson = inArray(items.human, lcGlobal.instances);
debug("checkGlobalLabelValidity", (hasGlobalLabel && (isDisambig || isPerson)), hasGlobalLabel, isDisambig, isPerson);
return (hasGlobalLabel && (isDisambig || isPerson));
}
// Polls the user's talk page for new messages. If there are, the according link is highlighted.
function checkUserTalkPage() {
debug("checkUserTalkPage");
$.ajax({
"type": "POST",
"url": mw.util.wikiScript("api"),
"data": {
"action": "query",
"format": "json",
"meta": "userinfo",
"uiprop": "hasmsg"
},
"success": function(data) {
if (! data.hasOwnProperty("error") && data.query !== undefined && data.query.userinfo !== undefined) {
debug("checkUserTalkPage success");
lcGlobal.hasMessages = (data.query.userinfo.messages !== undefined);
$getById(ids.talkPage).toggleClass(classes.talkAlert, lcGlobal.hasMessages);
} else {
debug("checkUserTalkPage error", getAjaxErrorMessage(data));
}
}
});
}
// Clean a value (remove duplicated or leading/trailing spaces)
function cleanValue($fld) {
return XRegExp.replace($fld.val().trim(), XRegExp.cache("\\s{2,}", "g"), " ");
}
// Remove aliases from one language
// Can't be done in the same API call as the other changes, and has to be done separately for any language
function clearAliases(clearLanguages, loadNext) {
var existingValues, lang, summary;
if (clearLanguages !== undefined && clearLanguages.length > 0) {
lang = clearLanguages.shift();
existingValues = $getById(ids.aliasesExisting + lang).val();
summary = existingValues.replace("|", ", ") + lcConfig.summaryTmp;
debug("clearAliases", lang);
// Send secondary request to server: Clear aliases
$.ajax({
"type": "POST",
"url": mw.util.wikiScript("api"),
"data": {
"action": "wbsetaliases",
"assert": "user",
"format": "json",
"id": lcGlobal.entity.id,
"language": lang,
"remove": existingValues,
"summary": summary,
"type": "item",
"token": mw.user.tokens.get("editToken")
},
"success": function(data) {
if (data.hasOwnProperty("error")) {
showMessage(getText(i18n.txtSavingFailed, { "txt": getAjaxErrorMessage(data) }));
debug("clearAliases error", getAjaxErrorMessage(data));
enableButtons(true);
} else {
debug("clearAliases success");
}
// Clear aliases in next language
clearAliases(clearLanguages, loadNext);
},
"error": function() {
showMessage(getText(i18n.txtSavingFailed, { "txt": i18n.txtApiError }));
debug("clearAliases error");
enableButtons(true);
}
});
} else {
// No more aliases to clear, load next item
if (loadNext) {
loadNextItem();
} else {
loadItemPage(lcGlobal.entity.id);
}
}
}
// Action: Clear all
function clearAll() {
$.each(lcGlobal.languages, function() {
var lang = this.lang;
// Skip if collapsed
if (! $getById(ids.labelNew + lang).is(":visible")) {
return true; // continue each
}
debug("clear", lang);
setFieldValue(ids.labelNew + lang, "", false);
setFieldValue(ids.aliasesNew + lang, "", false);
setFieldValue(ids.descNew + lang, "", false);
});
}
// Create a button
function $createButton(clickFunction, accessKey, label, tooltip, id, cls, disabled) {
var $btn = $("<input />", {
"accesskey": accessKey,
"class": cls,
"disabled": disabled,
"id": id,
"title": (tooltip === null) ? label : tooltip,
"type": "button",
"value": label
});
if (clickFunction !== null && clickFunction !== undefined) {
$btn.click(function() {
clickFunction();
});
}
return $btn;
}
// Create a div
function $createDiv(cls, id) {
var $div = $("<div />", {
"class": cls,
"id": id
});
return $div;
}
// Create an input field with a pattern, placeholder, etc.
function $createInputFieldPattern(maxlength, pattern, placeholder, tooltip, required) {
var $fld = $("<input />", {
"maxlength": maxlength,
"pattern": pattern,
"placeholder": placeholder,
"title": tooltip,
"type": "text",
"required": required
});
return $fld;
}
// Create an input field with a value
function $createInputFieldValue(disabled, id, lang, tooltip, value) {
var $fld = $("<input />", {
"disabled": disabled,
"id": id,
"lang": lang,
"title": tooltip,
"type": "text",
"value": value
});
return $fld;
}
// Create an HTML link
function $createLink(url, text, clickFunction, id) {
var $lnk = $("<a />", {
"href": mw.util.getUrl(url),
"html": text,
"id": id
});
if (clickFunction !== null && clickFunction !== undefined) {
$lnk.click(function() {
clickFunction();
});
}
return $lnk;
}
// Create the basic HTML structure
function createPage() {
var $header = $("<span />", {
"id": ids.pageTitle
});
var $btnSaveNext = $createButton(buttonActionSaveNext, "s", getText(i18n.btnSaveNext), null, null, classes.mainButton);
var $btnSaveClose = $createButton(buttonActionSaveClose, "c", getText(i18n.btnSaveClose), null, null, classes.secondButton);
var $btnSkip = $createButton(buttonActionSkip, "k", getText(i18n.btnSkip), getText(i18n.ttWontSave), null, classes.mainButton);
var $btnActions = $createButton(showActions, "a", getText(i18n.btnActions));
var $btnOptions = $createButton(showOptions, "o", getText(i18n.btnOptions));
var $fldJumpTo = $createInputFieldPattern(255, "([qQ][1-9][0-9]*|User:.+|Special:Random|Special:Search/.+)", getText(i18n.fldJumpTo), getText(i18n.fldJumpTo), "required");
var $btnJumpTo = $createSubmitButton("g", getText(i18n.btnJumpTo), getText(i18n.ttWontSave));
var $frmJumpTo = $("<form />");
var $headerDiv = $createDiv(classes.top);
var $buttonsDiv = $createDiv(classes.buttons);
var $buttonLine = $("<tr />");
var $buttonLeft = $("<td />");
var $buttonRight = $("<td />");
var $userNav = $("<ul />", { "id": ids.userNav });
$frmJumpTo.submit(function() {
jumpTo($fldJumpTo.val());
return false;
});
$frmJumpTo.append($fldJumpTo);
$frmJumpTo.append($btnJumpTo);
$userNav.append($("<li />").append($createLink(lcConfig.pageUserWatch, getText(i18n.lnkWatchlist))));
$userNav.append($("<li />").append($createLink(lcConfig.pageUserTalk, getText(i18n.lnkTalkPage), buttonActionTalkPage, ids.talkPage)));
$userNav.append($("<li />").append($createLink(lcConfig.pageUserContrib, getText(i18n.lnkContributions))));
$userNav.append($("<li />").append($createLink(lcConfig.pageToolTalk, getText(i18n.lnkFeedback))));
$userNav.append($("<li />").append($createLink(lcConfig.pageTool, getText(i18n.lnkHelp))));
$headerDiv.append($header);
$headerDiv.append($frmJumpTo);
$headerDiv.append($userNav);
$buttonRight.append([ $btnSaveNext, $btnSaveClose, $btnSkip ]);
$buttonLeft.append([ $btnActions, $btnOptions ]);
$buttonLine.append([ $buttonLeft, $buttonRight ]);
$buttonsDiv.append($("<table />").append($buttonLine));
$buttonsDiv.find("input").updateTooltipAccessKeys();
$btnJumpTo.updateTooltipAccessKeys();
$("body").empty();
$("body").append($headerDiv);
$("body").append($buttonsDiv);
$("body").append($createDiv(null, ids.mwBodyContent));
$("body").append($buttonsDiv.clone(true));
// All main buttons should have the same width
$("." + classes.mainButton + ", ." + classes.secondButton).width(Math.max($btnSaveNext.width(), $btnSaveClose.width(), $btnSkip.width()));
}
// Create a submit button
function $createSubmitButton(accessKey, label, tooltip) {
var $btn = $("<input />", {
"accesskey": accessKey,
"title": tooltip,
"type": "submit",
"value": label
});
return $btn;
}
// Print out a debug log message (if debug mode is enabled)
function debug() {
if (lcConfig.debugMode) {
console.group();
console.log(arguments);
console.trace();
console.groupEnd();
}
}
// Enable/disable buttons
// e.g. to prevent that the "Save" button is pressed multiple times before the next page is loaded, or to allow another save after the first one has failed
function enableButtons(enable) {
$getByClass(classes.buttons).find("input").attr("disabled", ! enable);
}
// From the items delivered by the API, select the next one which exists and should not be skipped
function findNextValidItem(entities) {
var loadThis = false;
var entity, gotDescription, gotLabel, gotSitelink, language, project;
// Load first existing item (remind that the API query returns results for missing items, too)
$.each(entities, function () {
entity = this;
if (entity.missing === undefined) {
// Item exists, so remember it
lcGlobal.entity = entity;
// Check whether we should skip the item
$.each(lcGlobal.userLanguages, function() {
language = this;
gotLabel = (lcGlobal.entity.labels !== undefined && lcGlobal.entity.labels[language] !== undefined);
gotDescription = (lcGlobal.entity.descriptions !== undefined && lcGlobal.entity.descriptions[language] !== undefined);
gotSitelink = false;
if (lcGlobal.languages[language].projects !== undefined) {
$.each(lcGlobal.languages[language].projects, function() {
project = this;
gotSitelink = (lcGlobal.entity.sitelinks !== undefined && lcGlobal.entity.sitelinks[project.db] !== undefined);
if (gotSitelink) {
return false; // break each
}
});
}
if (lcConfig.skipIfEditorLangsSet && gotLabel && gotDescription) {
// Skip
debug("skipped item, editor langs set", entity);
} else if (lcConfig.skipIfEditorLangsSetOrNoLinks && (! gotSitelink || (gotLabel && gotDescription))) {
// Skip
debug("skipped item, editor langs set or no sitelinks", entity);
} else {
loadThis = true;
debug("load item", entity);
return false; // break each
}
});
if (loadThis) {
return false; // break each
}
} else {
debug("skipped missing item", entity);
}
});
return loadThis;
}
// Generates a description from the Wikidata statements, if possible in the selected language (otherwise undefined)
// In prepareMode, no description is generated, but a list of the needed items is returned
function generateDescription(prepareMode, lang) {
var itemIds = [];
var language = (lcGlobal.languages[lang] === undefined) ? {} : lcGlobal.languages[lang];
var composers, foundYear, date, dates, description, texters, year;
var creators = getFirstPropertysValues([
properties.performer,
properties.developer,
properties.director,
properties.composer,
properties.author,
properties.manufacturer,
properties.designer,
properties.creator,
properties.producer,
properties.editor,
properties.publisher
]);
var locationsBig = getFirstPropertysValues([
properties.country,
properties.continent
]);
var locationsSmall = getFirstPropertysValues([
properties.administrativeUnit,
properties.locatedTerrain,
properties.locatedWater
]);
if (getClaimValues(properties.occupation) !== undefined) {
// Persons (occupation)
debug("generateDescription person", lang);
description = generateDescriptionInternal([[ getClaimValues(properties.occupation) ]], lang, itemIds);
} else if (getClaimValues(properties.series) !== undefined && isInstance(items.episode)) {
// Episode of a series
debug("generateDescription episode", lang);
description = generateDescriptionInternal([
[ getClaimValues(properties.instance) ],
[ getClaimValues(properties.series), language.descOf ]
], lang, itemIds);
} else if (creators !== undefined) {
// Creative works (creator of ONE kind, possibly year of publication), products
// (Exception: musical works may have composer and texter if they don't have a performer)
dates = getClaimValues(properties.dateOfPublication);
texters = getClaimValues(properties.texter);
composers = getClaimValues(properties.composer);
// Special rule for musical works without a performer known:
// Add lyrics texter only if available and different from composer
if (creators !== composers || creators === texters) {
texters = undefined;
}
if (dates !== undefined) {
$.each(dates, function() {
// Extract the year from the ISO 8601 date manually, since Date.parse() fails to do so on some browsers
// (this implementation fails on B.C. dates - considered irrelevant)
date = XRegExp.exec(this, XRegExp.cache("\\+(?<year>[1-9][0-9]*)-"));
foundYear = (date !== null && date !== undefined && date.year !== undefined);
// While there may be several dates given (e.g. publication in different countries),
// only the earliest one is of interest here
if (foundYear && (year === undefined || parseInt(date.year, 10) < parseInt(year, 10))) {
year = date.year;
}
debug("found year", this, date, date.year, year, foundYear);
});
}
debug("generateDescription work", lang);
description = generateDescriptionInternal([
[ lcGlobal.instances ],
[ creators, language.descBy ],
[ texters, language.descAnd ],
[ year, language.descFromTime ]
], lang, itemIds);
} else if (locationsSmall !== undefined || locationsBig !== undefined) {
// Places (ONE location type, possibly additional country)
debug("generateDescription location", lang);
description = generateDescriptionInternal([
[ lcGlobal.instances ],
[ locationsSmall, language.descIn ],
[ locationsBig, language.descIn ]
], lang, itemIds);
}
if (description === undefined && lcGlobal.instances !== undefined && (lcGlobal.instances.length !== 1 || lcGlobal.instances[0] !== items.human)) {
// For everything else and for everything where the above methods failed for a missing label:
// Try to just print the instance value(s)
debug("generateDescription other", lang);
description = generateDescriptionInternal([[ lcGlobal.instances ]], lang, itemIds);
}
return (prepareMode) ? itemIds : description;
}
// Generates a description from the given claim values, possibly using the given connectors
// As soon as one part is unknown, undefined is returned
function generateDescriptionInternal(values, lang, itemIds) {
var connector, i, description, labels, len, value;
for (i = 0, len = values.length; i < len; i++) {
value = values[i][0];
connector = values[i][1];
if (value !== undefined) {
if (typeof value === "string") {
labels = value;
} else {
$.merge(itemIds, value);
labels = getConcatenatedItemLabels(value, lang);
}
if (connector !== undefined && description !== undefined && labels !== undefined) {
description = getText(connector, {
"a": description,
"b": labels
});
} else if (i === 0) {
description = labels;
} else {
description = undefined;
}
}
}
return description;
}
// Extract the error message from an AJAX response
function getAjaxErrorMessage(data) {
debug("ajax error", data);
if (data.error.messages !== undefined && data.error.messages.html !== undefined && data.error.messages.html["*"] !== undefined) {
return data.error.messages.html["*"];
} else {
return JSON.stringify(data);
}
}
// Access the cached item labels without throwing an error when undefined
function getCachedItemLabel(item, lang) {
if (lcGlobal.cachedItemLabels[item] !== undefined) {
return lcGlobal.cachedItemLabels[item][lang];
}
return; // return undefined
}
// Return all values of claims with the given property (lazy load on first call)
function getClaimValues(property) {
var pid, prop, snak, val;
if (property === undefined) {
return; // return undefined
}
if (lcGlobal.claimValues === undefined) {
lcGlobal.claimValues = {};
$.each(properties, function() {
prop = this;
pid = "P" + prop;
val = [];
if (lcGlobal.entity.claims !== undefined && lcGlobal.entity.claims[pid] !== undefined) {
$.each(lcGlobal.entity.claims[pid], function(key, value) {
snak = value.mainsnak;
if (snak === undefined || snak.datavalue === undefined || snak.datavalue.value === undefined) {
return true; // continue each
}
if (snak.datavalue.value["numeric-id"] !== undefined) {
val.push(snak.datavalue.value["numeric-id"]);
} else if (snak.datavalue.time !== undefined) {
val.push(snak.datavalue.time);
}
});
}
if (val.length > 0) {
lcGlobal.claimValues[prop] = val;
}
});
}
return lcGlobal.claimValues[property];
}
// Returns the translated labels of the items (e.g. A,B,C) as a proper list ("A, B and C")
// Returns undefined as soon as there is one undefined part (i.e. an item label missing in that language)
function getConcatenatedItemLabels(array, lang) {
var ret = "";
var i, item, len;
if (lang !== null && array !== undefined) {
for (i = 0, len = array.length; i < len; i++) {
item = getCachedItemLabel(array[i], lang);
if (item === undefined || i === 0) {
ret = item;
} else if (i < len - 1) {
ret += ", " + item;
} else {
ret = getText(lcGlobal.languages[lang].descAnd, {
"a": ret,
"b": item
});
}
}
}
return ret;
}
// Create editor for label, description or alias
function $getEditorLine(lang, existingValue, idSuggest, classField, idFieldExisting, idFieldNew, txtFieldExisting) {
var $line = $("<tr />");
var $btnSuggest = $createButton(null, null, getText(i18n.btnSuggest), getText(i18n.ttSuggestNone), idSuggest + lang, null, true);
var $btnReset = $createButton(null, null, getText(i18n.btnReset), getText(i18n.ttReset), null, null, true);
$btnReset.click(function() {
// Reset "new" field to "existing" value
setFieldValue(idFieldNew + lang, $getById(idFieldExisting + lang).val(), false);
});
$line.append($("<td />", { "class": classField + " " + classes.existing }).append($createInputFieldValue(true, idFieldExisting + lang, lang, txtFieldExisting, existingValue)));
$line.append($("<td />").append($btnReset));
$line.append($("<td />").append($btnSuggest));
$line.append($("<td />", { "class": classField + " " + classes.newValue }).append($createInputFieldValue(true, idFieldNew + lang, lang)));
return $line;
}
// Returns the values of the first of the given properties that actually has a value
function getFirstPropertysValues(props) {
var i, len, values;
if (props !== undefined) {
for (i = 0, len = props.length; i < len; i++) {
values = getClaimValues(props[i]);
if (values !== undefined) {
return values;
}
}
}
return; // return undefined
}
// Load all languages supported by Wikidata, set user languages
function getLanguages(onlyUserLanguages) {
var db, inUserLanguages, lang, site;
$.each(wikibase.sites.getSites(), function(index, value) {
db = index;
site = value._siteDetails;
lang = (realLanguage[site.languageCode] === undefined) ? site.languageCode : realLanguage[site.languageCode];
inUserLanguages = inArray(lang, lcGlobal.userLanguages);
if ((onlyUserLanguages && ! inUserLanguages) || (! onlyUserLanguages && inUserLanguages)) {
return true; // continue each
}
// Add language
if (lcGlobal.languages[lang] === undefined) {
lcGlobal.languages[lang] = {
"lang": lang,
"projects": []
};
// Load definitions from languageData
if (languageData[lang] !== undefined) {
lcGlobal.languages[lang].descAnd = (languageData[lang].descAnd !== undefined) ? languageData[lang].descAnd : languageData.defaultLang.descAnd;
lcGlobal.languages[lang].descBy = (languageData[lang].descBy !== undefined) ? languageData[lang].descBy : languageData.defaultLang.descBy;
lcGlobal.languages[lang].descFromPlace = (languageData[lang].descFromPlace !== undefined) ? languageData[lang].descFromPlace : languageData.defaultLang.descFromPlace;
lcGlobal.languages[lang].descFromTime = (languageData[lang].descFromTime !== undefined) ? languageData[lang].descFromTime : languageData.defaultLang.descFromTime;
lcGlobal.languages[lang].descIn = (languageData[lang].descIn !== undefined) ? languageData[lang].descIn : languageData.defaultLang.descIn;
lcGlobal.languages[lang].descOf = (languageData[lang].descOf !== undefined) ? languageData[lang].descOf : languageData.defaultLang.descOf;
lcGlobal.languages[lang].script = languageData[lang].script;
lcGlobal.languages[lang].sisters = languageData[lang].sisters;
lcGlobal.languages[lang].separator = (languageData[lang].separator !== undefined) ? languageData[lang].separator : languageData.defaultLang.separator;
lcGlobal.languages[lang].suggest = languageData[lang].suggest;
}
}
// Add project
if (projectCodes[site.group] !== undefined) {
lcGlobal.languages[lang].projects.push({
"db": db,
"code": (realLanguage[site.languageCode] === undefined) ? projectCodes[site.group] : (site.languageCode + "-" + projectCodes[site.group]),
"group": site.group
});
}
});
// Sort projects: Wikipedia first
$.each(lcGlobal.languages, function(index) {
lang = index;
lcGlobal.languages[lang].projects.sort(function(a, b) {
if (a.group === "wikipedia" && b.group !== "wikipedia") {
return -1;
} else if (a.group !== "wikipedia" && b.group === "wikipedia") {
return 1;
} else {
return a.db.localeCompare(b.db);
}
});
});
}
// Create a preview section for a project and language
function getPreviewLine(db, lang) {
var $previewLine = $("<tr />");
// Prepare display
$previewLine.append($("<td />").append($createDiv(classes.triangle + " " + classes.triangleRight)));
$previewLine.append($("<td />").append($createButton(null, null, getText(i18n.btnSuggest), getText(i18n.ttSuggestNone), ids.suggestDesc + db + lang, null, true)));
$previewLine.append($("<td />").append($createDiv(classes.ellipsis, ids.preview + db + lang)));
$getById(ids.previewArea + lang).find("table").append($previewLine);
// Collapsing
$previewLine.click(function(event) {
// Don't toggle if the "Suggest" button or the text is clicked (or selected)
if (! $(event.target).is("input[type=button],span")) {
$getById(ids.preview + db + lang).toggleClass(classes.ellipsis);
$previewLine.find("." + classes.triangle).toggleClass(classes.triangleRight);
}
});
}
// Extend sister languages: Sister languages get the same rules and projects than the main language
function getSisterLanguages() {
var lang, sisterlang, sisters;
$.each(lcGlobal.languages, function(index) {
sisters = this.sisters;
lang = index;
if (sisters !== undefined) {
$.each(sisters, function () {
sisterlang = this;
if (lcGlobal.languages[sisterlang] === undefined) {
lcGlobal.languages[sisterlang] = { "lang": sisterlang };
}
});
// Override all sister languages' projects by main language projects
$.each(sisters, function () {
sisterlang = this;
// Copy main language regexes and all projects to sister
lcGlobal.languages[sisterlang].descAnd = lcGlobal.languages[lang].descAnd;
lcGlobal.languages[sisterlang].descBy = lcGlobal.languages[lang].descBy;
lcGlobal.languages[sisterlang].descFromPlace = lcGlobal.languages[lang].descFromPlace;
lcGlobal.languages[sisterlang].descFromTime = lcGlobal.languages[lang].descFromTime;
lcGlobal.languages[sisterlang].descIn = lcGlobal.languages[lang].descIn;
lcGlobal.languages[sisterlang].descOf = lcGlobal.languages[lang].descOf;
lcGlobal.languages[sisterlang].ignoreSitelinks = true;
lcGlobal.languages[sisterlang].projects = lcGlobal.languages[lang].projects;
lcGlobal.languages[sisterlang].script = lcGlobal.languages[lang].script;
lcGlobal.languages[sisterlang].separator = lcGlobal.languages[lang].separator;
lcGlobal.languages[sisterlang].sisters = undefined;
lcGlobal.languages[sisterlang].suggest = lcGlobal.languages[lang].suggest;
});
}
});
}
// Internationalisation of a text, incl. string formatting
// Parameters may be strings or i18n object (which get translated then)
// First parameter may contain placeholders {txt}, {year}, ... (which get replaced by the other parameters then)
function getText(text, format) {
var lang = mw.config.get("wgUserLanguage");
var returnText = "";
var textInternal;
returnText = getTextInternal(text, lang);
if (format !== undefined && returnText !== undefined) {
$.each(format, function(key, value) {
textInternal = getTextInternal(value, lang);
// Dollar signs in existing labels etc. have to be doubled, otherwise they would disturb the template system
textInternal = XRegExp.replace(textInternal, XRegExp.cache("\\$", "gm"), "\$$\$$");
returnText = XRegExp.replace(returnText, XRegExp.cache("\\{" + key + "\\}", "gm"), textInternal);
});
}
return returnText;
}
// Internationalisation (if an i18n object has been passed instead of a string)
function getTextInternal(text, lang) {
if (typeof text === "string") {
return text;
} else if (text === undefined) {
return; // return undefined
} else if (text[lang] === undefined && text.en === undefined) {
return getText(i18n.intLabelMissing);
} else if (text[lang] !== undefined) {
return text[lang];
} else {
// Fallback to en
return text.en;
}
}
// Helper method to shorten jQuery's check whether a value is in an array
function inArray(value, array) {
return ($.inArray(value, array) > -1);
}
// Display the current item
function initItem() {
var newItemIds = [];
var qids = "";
var qidsText = "";
var item, itemIds;
lcGlobal.claimValues = undefined;
lcGlobal.globalLabel = {};
showStatus(getText(i18n.statusInitItem));
window.location = mw.util.getUrl(lcConfig.pageTool) + "#" + lcGlobal.entity.id;
setTitle(lcGlobal.entity.id);
lcGlobal.instances = getFirstPropertysValues([
properties.taxonRank,
properties.instance,
properties.subclass,
properties.astronomicalObject,
properties.administrativeType,
properties.structureType,
properties.electionType,
properties.lakeType
]);
// Load labels for items used to generate description from statements
itemIds = generateDescription(true, null);
$.each(itemIds, function() {
item = this;
if (lcGlobal.cachedItemLabels[item] === undefined && ! inArray(item, newItemIds)) {
newItemIds.push(item);
qids += ((qids === "") ? "" : "|") + "Q" + item;
qidsText += ((qidsText === "") ? "" : ", ") + "Q" + item;
}
});
// Clear log display
setTitle(lcGlobal.entity.id);
if (newItemIds.length > 0) {
loadItemLabels(newItemIds, qids, qidsText);
} else {
// Generate GUI and load data
loadData();
}
}
// Initialise the GUI
function initPage() {
applyCSS();
createPage();
setTitle();
loadBabel();
}
// Initialise the supported languages and projects - extend languages to all projects supported by Wikidata itself
function initProjects() {
debug("init projects start");
// Call getLanguages() twice to sort user languages before all others
getLanguages(true);
getLanguages(false);
getSisterLanguages();
debug("init projects finish");
// Load first item entity
jumpTo(window.location.hash.substr(1));
}
// Startup method
function initTool() {
var urlAnchor = window.location.hash;
var userIsOnToolPage = (mw.config.get("wgPageName") === lcConfig.pageTool && mw.config.get("wgAction") === "view");
if (userIsOnToolPage && urlAnchor !== undefined && urlAnchor !== "") {
mw.util.addCSS("." + classes.center + "{ text-align: center; }");
mw.loader.using([ "jquery.spinner", "jquery.ui", "wikibase" ], function () {
$getById(ids.mwBodyContent).replaceWith($createDiv(classes.center, ids.mwBodyContent).append($.createSpinner({ "size": "large" })));
// Load XRegExp library
loadXRegExp();
});
}
}
// Check whether a field has been changed compared to the existing value
function isChanged(id) {
var $field = $getById(id);
var valueExisting = $field.parents("tr").find("input[type=text]").first().val();
var valueNew = $field.val();
return (valueExisting !== valueNew);
}
// Check whether the item is an instance of the given item (and nothing else)
function isInstance(item) {
return (lcGlobal.instances.length === 1 && lcGlobal.instances[0] === item);
}
// Go to the specified item or user's edits
function jumpTo(target) {
if (target.toUpperCase().substr(0, 1) === "Q") {
// Load starting item
loadEntity(target.toUpperCase());
} else if (target.toUpperCase().substr(0, 5) === "USER:") {
// Load user's edited items
lcGlobal.userUcContinue = undefined;
loadUsersEdits(target.substr(5));
} else if (target.toUpperCase() === "SPECIAL:RANDOM") {
// Load a random item
loadRandomItem();
} else if (target.toUpperCase().substr(0, 15) === "SPECIAL:SEARCH/") {
// Perform a search
lcGlobal.srOffset = undefined;
loadSearchItems(target.substr(15));
} else {
showError(getText(i18n.txtFailedToReadInput, { "txt": target }));
}
}
// Load user's Babel languages from user page categories by AJAX call and go on
function loadBabel() {
var userpage = "User:" + mw.config.get("wgUserName");
var category, page, query;
showStatus(getText(i18n.statusLoadBabel, { "userpage": userpage }));
// Add user UI language
lcGlobal.userLanguages.push(mw.config.get("wgUserLanguage"));
// Get babel languages
$.ajax({
"url": mw.util.wikiScript("api"),
"data": {
"action": "query",
"format": "json",
"prop": "categories",
"titles": userpage
},
"success": function(babeldata) {
query = babeldata.query;
if (! babeldata.hasOwnProperty("error") && query !== undefined && query.pages !== undefined && query.pages[-1] === undefined) {
debug("loadBabel success", userpage);
$.each(query.pages, function() {
page = this;
$.each(page.categories, function() {
category = XRegExp.exec(this.title, XRegExp.cache("Category:User (?<lang>\\w+)"));
if (category !== null && category !== undefined && category.lang !== undefined && ! inArray(category.lang, lcGlobal.userLanguages)) {
lcGlobal.userLanguages.push(category.lang);
}
});
});
} else {
debug("loadBabel error", getAjaxErrorMessage(data));
}
initProjects();
},
"error": function(jqXHR, textStatus) {
showError(getText(i18n.intFailedToLoad) + textStatus);
}
});
}
// Generate GUI and load data
function loadData() {
var $content = $createDiv(classes.main, ids.mwBodyContent);
var $entries, $field;
var language, script;
debug("loadData");
setTitle(lcGlobal.entity.id);
$getById(ids.mwBodyContent).replaceWith($content);
// Load data for each language
$.each(lcGlobal.languages, function() {
language = this;
addEntry(language, false);
});
enableButtons(true);
// If there is only one entry in total, auto-expand it in any case
$entries = $content.find("." + classes.entry);
if ($entries.length === 1 && $entries.first().find("." + classes.collapsed).length > 0) {
$entries.first().find("." + classes.collapsed).click();
}
// If all existing labels are the same, possibly set all other labels also
$.each($entries.find("." + classes.newValue + "." + classes.label + " input[type=text]"), function() {
$field = $(this);
script = lcGlobal.languages[$field.attr("lang")].script;
if (lcConfig.autoFill && checkGlobalLabelValidity(script)) {
setFieldValue($field.attr("id"), lcGlobal.globalLabel[script], false);
}
});
}
// Load Wikidata entity data (claims, etc.) by AJAX call and go on
function loadEntity(qid) {
var entities;
setTitle(qid);
showStatus(getText(i18n.statusLoadEntity, { "qid": qid }));
$.ajax({
"url": mw.util.wikiScript("api"),
"data": {
"action": "wbgetentities",
"format": "json",
"ids": qid
},
"success": function(data) {
entities = data.entities;
if (! data.hasOwnProperty("error") && entities !== undefined && entities[qid] !== undefined && entities[qid].missing === undefined) {
lcGlobal.entity = entities[qid];
// Start script
debug("loadEntity success");
initItem();
} else {
debug("loadEntity error", getAjaxErrorMessage(data));
showError(getText(i18n.txtFailedToItem, { "txt": qid }));
}
},
"error": function(jqXHR, textStatus) {
showError(getText(i18n.intFailedToLoad) + textStatus);
}
});
}
// Load the labels of items used in this item's claims and go on
function loadItemLabels(itemIds, qids, qidsText) {
var itemId, qid, label, sisterLang;
showStatus(getText(i18n.statusLoadItemLabels, { "qids": qidsText }));
$.ajax({
"url": mw.util.wikiScript("api"),
"data": {
"action": "wbgetentities",
"format": "json",
"ids": qids,
"props": "labels"
},
"success": function(data) {
if (! data.hasOwnProperty("error") && data.entities !== undefined) {
debug("loadItemLabels success");
$.each(itemIds, function() {
itemId = this;
qid = "Q" + itemId;
lcGlobal.cachedItemLabels[itemId] = {};
// Set labels
if (data.entities[qid] !== undefined) {
$.each(data.entities[qid].labels, function () {
label = this;
if (label.value !== undefined) {
lcGlobal.cachedItemLabels[itemId][label.language] = label.value;
}
});
}
});
// Set labels for sister languages, if needed
$.each(lcGlobal.cachedItemLabels, function(id, labels) {
$.each(labels, function (lang, thisLabel) {
if (thisLabel !== undefined && languageData[lang] !== undefined && languageData[lang].sisters !== undefined) {
$.each(languageData[lang].sisters, function() {
sisterLang = this;
if (lcGlobal.cachedItemLabels[id][sisterLang] === undefined) {
lcGlobal.cachedItemLabels[id][sisterLang] = thisLabel;
}
});
}
});
});
} else {
debug("loadItemLabels error", getAjaxErrorMessage(data));
}
// Generate GUI and load data
loadData();
},
"error": function(jqXHR, textStatus) {
showError(getText(i18n.intFailedToLoad) + textStatus);
}
});
}
// Load a list of items based on a search query
function loadItemList(term, params, termStore, offset, searchResult, loadMsg, errMsg) {
var newList = [];
var result;
showStatus(getText(loadMsg, { "term": term }));
termStore = term;
if (offset === undefined) {
offset = { "continue": "" };
}
$.extend(params, offset);
$.ajax({
"url": mw.util.wikiScript("api"),
"data": params,
"success": function(data) {
if (! data.hasOwnProperty("error") && data.query !== undefined && data.query[searchResult] !== undefined) {
offset = data["continue"];
debug("loadItemList success", offset);
// Only add each item once
$.each(data.query[searchResult], function () {
result = this;
if (! inArray(result.title, lcGlobal.itemList)) {
lcGlobal.itemList.push(result.title);
newList.push(result.title);
}
});
} else {
debug("loadItemList error", getAjaxErrorMessage(data));
offset = undefined;
}
if (newList.length > 0) {
loadNextItem();
} else {
offset = undefined;
showError(getText(errMsg, { "term": term }));
}
},
"error": function() {
offset = undefined;
showError(getText(errMsg, { "term": term }));
}
});
}
// Loads the item page (i.e. close the Label Collector)
function loadItemPage(qid) {
if (qid !== undefined) {
window.location = mw.util.getUrl(qid);
} else {
showError(getText(i18n.txtFailedToItem, { "txt": qid }));
}
}
// Load next item's page (on "Skip" or "Save & Next")
function loadNextItem() {
var ids = "";
var loadThis = false;
var i, id, len;
setTitle();
// Scroll to top, in case the browser doesn't do this on its own after the page is cleared
$("body").animate({ "scrollTop": 0 }, 0);
// Check next couple of items for existence
if (lcGlobal.itemList.length === 0) {
// Standard mode: Next items by numeric ID
id = parseInt(lcGlobal.entity.id.substr(1), 10);
debug("loadNextItem standard mode", id);
showStatus(getText(i18n.statusLoadNextItem, { "qid": String(id + 1) }));
for (i = 1; i <= lcConfig.checkNextItems; i++) {
ids += ((ids === "") ? "" : "|") + "Q" + (id + i);
}
} else {
// User contribution mode / search mode: Next items in itemList
lcGlobal.itemPointer++;
debug("loadNextItem user/search mode", lcGlobal.itemPointer);
if (lcGlobal.itemList.length <= lcGlobal.itemPointer) {
if (lcGlobal.userUcContinue !== undefined) {
loadUsersEdits(lcGlobal.usersContribs);
} else if (lcGlobal.srOffset !== undefined) {
loadSearchItems(lcGlobal.searchTerm);
} else {
showError(getText(i18n.txtFailedToLoadNext));
}
return;
}
id = lcGlobal.itemList[lcGlobal.itemPointer].substr(1);
showStatus(getText(i18n.statusLoadNextItem, { "qid": String(id) }));
for (i = 0, len = lcGlobal.itemList.length; i < lcConfig.checkNextItems && lcGlobal.itemPointer < len; i++, lcGlobal.itemPointer++) {
ids += ((ids === "") ? "" : "|") + (lcGlobal.itemList[lcGlobal.itemPointer]);
}
}
debug("loadNextItem query", ids);
// Query API to check if items exist and should be edited
if (ids !== "") {
$.ajax({
"url": mw.util.wikiScript("api"),
"data": {
"action": "wbgetentities",
"format": "json",
"ids": ids,
"props": "aliases|claims|descriptions|labels|sitelinks"
},
"success": function(data) {
debug("loadNextItem success");
lcGlobal.entity = undefined;
// Load first existing item (remind that the API query returns results for missing items, too)
loadThis = findNextValidItem(data.entities);
if (lcGlobal.entity === undefined) {
// Not a single existing item returned by query - we might have reached the end (or a gap big enough that we can't tell)
showError(getText(i18n.txtFailedToLoadNext));
} else if (loadThis) {
// Found a valid item to load
if (lcGlobal.itemList.length > 0) {
// Reset itemPointer
lcGlobal.itemPointer = $.inArray(lcGlobal.entity.id, lcGlobal.itemList);
}
initItem();
} else {
// There have been items, but all had to be skipped - check next bunch
loadNextItem();
}
},
"error": function() {
// API error
showError(getText(i18n.txtFailedToLoadNext));
}
});
} else {
// No IDs set (probably reached end of user's loaded contributions
showError(getText(i18n.txtFailedToLoadNext));
}
}
// Loads a random item (that can be edited)
function loadRandomItem() {
debug("loadRandomItem");
$.ajax({
"type": "POST",
"url": mw.util.wikiScript("api"),
"data": {
"action": "query",
"format": "json",
"list": "random",
"rnlimit": 1,
"rnnamespace": "0"
},
"success": function(data) {
if (! data.hasOwnProperty("error") && data.query !== undefined && data.query.random !== undefined && data.query.random[0] !== undefined) {
debug("loadRandomItem success");
// Get random QID
lcGlobal.entity.id = data.query.random[0].title;
// Search next item to be edited
loadNextItem();
} else {
debug(getAjaxErrorMessage(data));
showError(getText(i18n.txtFailedToLoadNext));
}
},
"error": function() {
showError(getText(i18n.txtFailedToLoadNext));
}
});
}
// Loads a list of search results
function loadSearchItems(term) {
var params = {
"action": "query",
"format": "json",
"list": "search",
"srlimit": lcConfig.checkUserContribs,
"srnamespace": 0,
"srprop": "title",
"srsearch": term
};
loadItemList(term, params, lcGlobal.searchTerm, lcGlobal.srOffset, "search", i18n.statusLoadSearchResults, i18n.txtFailedToLoadSearchResults);
}
// Loads the list of the items the user most recently edited
function loadUsersEdits(user) {
var params = {
"action": "query",
"format": "json",
"list": "usercontribs",
"uclimit": lcConfig.checkUserContribs,
"ucnamespace": 0,
"ucprop": "title",
"ucuser": user
};
loadItemList(user, params, lcGlobal.usersContribs, lcGlobal.userUcContinue, "usercontribs", i18n.statusLoadUserEdits, i18n.txtFailedToLoadUsersItems);
}
// Load the introduction of an article as a preview, create suggestions from it (only if none is existing)
// Sequential loading: Load first defined project first, then recursively load second and so on, so the more important projects get a better chance to make suggestions
function loadWikiContent(language, projects, aliases, desc, wasDescriptionSuggested) {
var description = desc;
var introhtml = "";
var lang = language.lang;
var separator = language.separator;
var suggest = language.suggest;
var wasDescSuggested = wasDescriptionSuggested;
var actualAliases, actualNewLabel, db, extract, introtext, newAliases, newDescription, project, redirect, url;
// Skip if no project for this language
if (projects === undefined || projects.length === 0) {
return;
}
db = projects[0].db;
project = projects[0].code;
// Skip if no sitelink for this project
if (db === undefined || lcGlobal.entity.sitelinks === undefined || lcGlobal.entity.sitelinks[db] === undefined) {
if (projects.length > 1) {
loadWikiContent(language, projects.slice(1, projects.length), aliases, description, wasDescSuggested);
} else {
loadWikidataContent(lang, wasDescSuggested, description);
}
return;
}
getPreviewLine(db, lang);
$.createSpinner({
"size": "small",
"type": "block"
}).appendTo("#" + ids.preview + db + lang);
debug("loadWikiContent");
// Query API to get introduction
$.getJSON(wikibase.sites.getSite(db).getApi() + "?callback=?", {
"action": "query",
"exchars": 1000,
"exintro": true,
"format": "json",
"prop": "extracts|info",
"titles": lcGlobal.entity.sitelinks[db].title
},
function(data) {
// Get introduction
redirect = data.query.pages[Object.keys(data.query.pages)[0]].redirect;
extract = data.query.pages[Object.keys(data.query.pages)[0]].extract;
debug("loadWikiContent loaded", redirect, extract);
if (extract !== undefined) {
introhtml = extract;
// Remove content most likely not being a valuable part of the article text (disambiguation boxes etc.)
introhtml = XRegExp.replace(introhtml, XRegExp.cache("<(?<tag>dd|dl)[^>]*>(?<content>.*?)<\\/\\s?\\k<tag>>", "g"), " ");
// Remove tags (not their content) which would corrupt the preview
introhtml = XRegExp.replace(introhtml, XRegExp.cache("<\\/?\\s?(blockquote|br|center|dl|li|ol|p|ul)[^>]*>", "g"), " ");
// Remove line breaks, non-breaking spaces etc.
introhtml = XRegExp.replace(introhtml, XRegExp.cache("(&(nbsp|#160|#x00A0|#8239|#x202F|#xFEFF|#65279|#x2007|#8199);)|\\u00A0|\\u202F|\\uFEFF|\\u2007", "g"), " ");
// Remove multiple spaces
introhtml = XRegExp.replace(introhtml, XRegExp.cache("\\s{2,}", "g"), " ");
introhtml = introhtml.trim();
}
if (introhtml === "" || redirect !== undefined) {
introhtml = (redirect !== undefined) ? getText(i18n.txtRedirect) : getText(i18n.txtNothing);
introhtml = "<i>" + introhtml + "</i>";
setFieldValue(ids.descNew + lang, $getById(ids.descExisting + lang).val(), false);
wasDescSuggested = true;
}
// Put <span> around it again, to allow using jQuery here and the ellipsis to work
introhtml = "<span>" + introhtml + "</span>";
// Show preview text
url = wikibase.sites.getSite(lcGlobal.entity.sitelinks[db].site).getUrlTo(lcGlobal.entity.sitelinks[db].title);
$getById(ids.preview + db + lang).html($(introhtml).prepend(getText("[<a href='{url}'>{project}</a>]: ", {
"url": url,
"project": project
})));
// Remove stress marks from suggestions for aliases and description (only for Russion, only if string normalization supported by browser)
if (lcConfig.cleanDescription && lang === "ru" && String.prototype.normalize) {
introhtml = XRegExp.replace(introhtml.normalize("NFD"), XRegExp.cache("\\u0301", "g"), "");
}
// Strip all HTML tags
introtext = $(introhtml).text();
// Get what now really is in the new label and alias fields
actualNewLabel = $getById(ids.labelNew + lang).val();
actualAliases = $getById(ids.aliasesNew + lang).val();
actualAliases = (actualAliases === "") ? aliases : actualAliases;
if (lcConfig.autoFill) {
newAliases = parseAliases(introhtml, actualAliases, actualNewLabel, parseLabel(lcGlobal.entity.sitelinks[db].title));
setFieldValue(ids.aliasesNew + lang, newAliases, true);
} else {
setFieldValue(ids.aliasesNew + lang, actualAliases, true);
}
// Get suggestions
// - Descriptions: prefill existing if available
// - Aliases: always keep existing and add new ones as suggestion)
newDescription = parseDescription(introtext, db, suggest, separator);
if (newDescription !== description && newDescription !== "…") {
// Enable project-specific "Suggest" button even if there is already a suggestion
updateSuggestButton(ids.suggestDesc + db + lang, ids.descNew + lang, newDescription);
}
if (! wasDescSuggested) {
wasDescSuggested = setExistingOrSuggestion(lang, ids.descNew, description, newDescription, ids.suggestDesc);
}
// Load next project
if (projects.length > 1) {
loadWikiContent(language, projects.slice(1, projects.length), aliases, description, wasDescSuggested);
} else {
loadWikidataContent(lang, wasDescSuggested, description);
}
});
}
// Generate an automatic description from the Wikidata statements
function loadWikidataContent(lang, wasDescriptionSuggested, desc) {
var db = "wikidata";
var description = desc;
var newDescription = getText(i18n.txtNothing);
var project = "d";
var wasDescSuggested = wasDescriptionSuggested;
var newDesc;
if (lcGlobal.instances !== undefined) {
getPreviewLine(db, lang);
newDescription = generateDescription(false, lang);
if (description === undefined) {
description = "";
}
if (newDescription !== undefined && newDescription !== "…") {
if (newDescription !== description) {
// Enable project-specific "Suggest" button even if there is already a suggestion
updateSuggestButton(ids.suggestDesc + db + lang, ids.descNew + lang, newDescription);
}
if (! wasDescSuggested) {
wasDescSuggested = setExistingOrSuggestion(lang, ids.descNew, description, newDescription, ids.suggestDesc);
}
}
newDesc = ((newDescription === undefined) ? getText(i18n.txtNothing) : "<i>" + newDescription + "</i>");
$getById(ids.preview + db + lang).html(getText("[{project}]: ", { "project": project }) + newDesc);
}
}
// Load XRegExp library by AJAX call and go on
// This is a requirement for some core functions but also parts of the getText() function
function loadXRegExp() {
var url = "https://cdnjs.cloudflare.com/ajax/libs/xregexp/2.0.0/xregexp-all-min.js";
showStatus(getText(i18n.statusLoadXRegExp) + "<a href='" + url + "'>XRegExp</a>");
$.ajax({
"url": url,
"dataType": "script",
"cache": true,
"timeout": lcConfig.timeout,
"success": function() {
debug("loadXRegExp success");
initPage();
},
"error": function(jqXHR, textStatus) {
showError(getText(i18n.intFailedToLoad) + textStatus);
}
});
}
// Make suggestions for new aliases based on what's set bold in the introduction text
// Existing ones are always kept, new ones are only suggested if they're not in the exiting ones or in the label or in the (possibly theoretical) label proposal
function parseAliases(text, alias, label, labelSuggestion) {
var aliases = alias;
var existing, isLabelIdentical, isSuggestedLabelIdentical, isTooShort, newAlias, newAliasLc, thisAlias;
$(text).find("b").each(function() {
newAlias = XRegExp.replace($(this).text().trim(), XRegExp.cache("^\"(.*)\"$"), "${1}");
newAliasLc = newAlias.toLowerCase();
existing = false;
// Only add finding if it's not already in the aliases list, and it's not identical to the label, and it's not too short (e.g. bold letters in abbreviations)
isLabelIdentical = (label !== undefined && newAliasLc === label.toLowerCase());
isSuggestedLabelIdentical = (labelSuggestion !== undefined && newAliasLc === labelSuggestion.toLowerCase());
isTooShort = (newAlias.length <= 2);
if (isLabelIdentical || isSuggestedLabelIdentical || isTooShort) {
existing = true;
} else if (aliases !== "" && aliases !== undefined) {
$.each(aliases.split("|"), function() {
thisAlias = this;
if (thisAlias.trim().toLowerCase() === newAliasLc) {
existing = true;
return false; // break each
}
});
}
if (! existing) {
aliases += ((aliases === "") ? "" : "|") + newAlias;
}
});
return aliases.trim();
}
// Make a suggestion for the description based on the article introduction
// - The first sentence is scanned for "is a" definitions based on the regular expression defined for each language
// - If nothing found, the complete first introduction sentence is proposed as a description
function parseDescription(text, db, suggest, separator) {
var firstSentence, fullstop, result, sentences;
// Only examine the first sentence
sentences = XRegExp.exec(text, XRegExp.cache(separator));
if (sentences !== null && sentences !== undefined && sentences.sentence !== undefined && sentences.sentence !== text) {
firstSentence = sentences.sentence;
} else {
fullstop = Math.max(text.indexOf("."), text.indexOf("。"));
if (fullstop > -1) {
firstSentence = text.substr(0, fullstop);
}
}
// Try to find the relevant part in the text
try {
if (suggest !== undefined && firstSentence !== undefined) {
result = XRegExp.exec(firstSentence, XRegExp.cache(suggest));
if (result !== null && result !== undefined && result.desc !== undefined) {
result = result.desc.trim();
}
}
} catch(e) {
showMessage(getText(i18n.txtFailedToParseIntro, {
"db": db,
"txt": e.toString()
}));
}
if (result === undefined || result === null) {
// Fallback: Suggest the complete first introduction sentence (or all, if no dot)
result = (firstSentence === undefined) ? text : firstSentence;
}
return result.trim();
}
// Make a suggestion for the label based on the article title (strip of bracket parts, if existing)
function parseLabel(text) {
var result = XRegExp.replace(text, XRegExp.cache(" \\(.+\\)$"), "");
if (result.length > 0) {
return result.trim();
}
// Fallback: Suggest complete title as label
return text.trim();
}
// Action: Reset all
function resetAll() {
$.each(lcGlobal.languages, function() {
var lang = this.lang;
// Skip if collapsed
if (! $getById(ids.labelNew + lang).is(":visible")) {
return true; // continue each
}
debug("reset", lang);
setFieldValue(ids.labelNew + lang, $getById(ids.labelExisting + lang).val(), false);
setFieldValue(ids.aliasesNew + lang, $getById(ids.aliasesExisting + lang).val(), false);
setFieldValue(ids.descNew + lang, $getById(ids.descExisting + lang).val(), false);
});
}
// Save changes (afterwards, reload item page or load next item page, based on parameter)
function saveItem(loadNext) {
var aliases = {};
var aliasesSummary = "";
var clearAliasLanguages = [];
var descriptions = {};
var descriptionsCount = 0;
var descriptionsSummary = "";
var json = {};
var labels = {};
var labelsCount = 0;
var labelsSummary = "";
var summary = "";
var i, lang, len, splitValue, value;
try {
debug("saveItem");
// Collect changes
$.each(lcGlobal.languages, function() {
lang = this.lang;
var $fldLabel = $getById(ids.labelNew + lang);
// Skip if collapsed - only save expanded entries
if (! $fldLabel.is(":visible")) {
return true; // continue each
}
if (isChanged(ids.labelNew + lang)) {
value = cleanValue($fldLabel);
labels[lang] = {
"language": lang,
"value": value
};
labelsSummary += getText("{plural}[{lang}]: {txt}", {
"plural": ((labelsCount === 0) ? "" : ", "),
"lang": lang,
"txt": value
});
labelsCount++;
}
if (isChanged(ids.descNew + lang)) {
value = cleanValue($getById(ids.descNew + lang));
descriptions[lang] = {
"language": lang,
"value": value
};
descriptionsSummary += getText("{plural}[{lang}]: {txt}", {
"plural": ((descriptionsCount === 0) ? "" : ", "),
"lang": lang,
"txt": value
});
descriptionsCount++;
}
if (isChanged(ids.aliasesNew + lang)) {
// Split concatenated string into array
value = cleanValue($getById(ids.aliasesNew + lang));
splitValue = value.split("|");
aliases[lang] = [];
if (value.length === 0) {
// Separate API call needed to clear aliases
clearAliasLanguages.push(lang);
} else {
for (i = 0, len = splitValue.length; i < len; i++) {
aliases[lang].push({
"language": lang,
"value": splitValue[i]
});
}
aliasesSummary += getText("{plural}[{lang}]: {txt}", {
"plural": ((aliasesSummary.length === 0) ? "" : ", "),
"lang": lang,
"txt": splitValue.join(", ")
});
}
}
});
// Don't save if nothing changed
if (labelsCount === 0 && descriptionsCount === 0 && aliasesSummary.length === 0 && clearAliasLanguages.length === 0) {
showMessage(getText(i18n.txtNothingToSave));
if (loadNext) {
loadNextItem();
} else {
loadItemPage(lcGlobal.entity.id);
}
return;
}
lcGlobal.saveCounter++;
if (loadNext && ! lcGlobal.hasMessages && lcGlobal.saveCounter % lcConfig.checkUserTalk === 0) {
checkUserTalkPage();
}
// Create summary and JSON data structure
if (labelsCount > 0) {
json.labels = labels;
summary = getText(lcConfig.labelSummaryTmp, {
"plural": (labelsCount > 1) ? "s" : "",
"txt": labelsSummary
});
}
if (descriptionsCount > 0) {
json.descriptions = descriptions;
summary += ((labelsCount === 0) ? "" : ", ") + getText(lcConfig.descSummaryTmp, {
"plural": (descriptionsCount > 1) ? "s" : "",
"txt": descriptionsSummary
});
}
if (aliasesSummary.length > 0) {
json.aliases = aliases;
summary += ((labelsCount === 0 && descriptionsCount === 0) ? "" : ", ") + getText(lcConfig.aliasesSummaryTmp, { "plural": "es", "txt": aliasesSummary });
}
summary += lcConfig.summaryTmp;
if (labelsCount > 0 || descriptionsCount > 0 || aliasesSummary.length > 0) {
debug("saveItem", lcGlobal.entity.id, json, summary);
// Send main request to server
$.ajax({
"type": "POST",
"url": mw.util.wikiScript("api"),
"data": {
"action": "wbeditentity",
"assert": "user",
"format": "json",
"data": JSON.stringify(json),
"id": lcGlobal.entity.id,
"summary": summary,
"type": "item",
"token": mw.user.tokens.get("editToken")
},
"success": function(data) {
if (data.hasOwnProperty("error")) {
showMessage(getText(i18n.txtSavingFailed, { "txt": getAjaxErrorMessage(data) }));
enableButtons(true);
} else {
debug("saveItem success");
}
// Clear aliases (per language), if needed, then load next item
clearAliases(clearAliasLanguages, loadNext);
},
"error": function() {
showMessage(getText(i18n.txtSavingFailed, { "txt": i18n.txtApiError }));
enableButtons(true);
}
});
} else if (clearAliasLanguages.length > 0) {
clearAliases(clearAliasLanguages, loadNext);
}
} catch(e) {
showMessage(getText(i18n.intError, { "txt": e.toString() }));
enableButtons(true);
}
}
// Save changes and load next item page
function saveItemNext() {
saveItem(true);
}
// Save changes and reload item page
function saveItemReload() {
saveItem(false);
}
// Fill the "new value" field with either the existing value or a suggestion
// (if value exists but a different suggestion could be made, prefill existing value and enable "Suggest" button)
// Returns whether a suggestion was made
function setExistingOrSuggestion(lang, idFieldNew, existing, suggestion, idSuggestButton) {
if (! lcConfig.autoFill) {
// Don't suggest anything
setFieldValue(idFieldNew + lang, existing, true);
return false;
} else if (existing === "" && suggestion !== "" && suggestion !== "…") {
// No value existing -> suggest one
setFieldValue(idFieldNew + lang, suggestion, true);
return true;
} else if (existing !== "") {
// Value existing -> prefill existing
setFieldValue(idFieldNew + lang, existing, true);
if (existing.toLowerCase() !== suggestion.toLowerCase() && suggestion !== "" && suggestion !== "…") {
// Suggestion would be different -> enable "Suggest" button
updateSuggestButton(idSuggestButton + lang, idFieldNew + lang, suggestion);
return true;
}
} else {
$getById(idFieldNew + lang).attr("disabled", false);
}
return false;
}
// Sets a new value for the field and triggers change handler
// Enable the field (on startup, they're disabled to prevent the user filling them manually before the suggestions are loaded)
function setFieldValue(field, value, replaceHtmlEntities) {
var $field = $getById(field);
if (replaceHtmlEntities && lcConfig.cleanDescription) {
// Secure way to decode HTML entities: using an oldschool textarea
var textArea = document.createElement("textarea");
textArea.innerHTML = value;
value = textArea.value;
}
$field.attr("disabled", false);
$field.attr("oldval", $field.val());
$field.val(value);
$field.change();
}
// Clears the page, sets the title for the new qid, displays a throbber until content is loaded
function setTitle(qid) {
var $history;
if (qid !== undefined) {
$history = $("<sup />").append("[").append($createLink(lcConfig.pageHistory + qid, getText(i18n.lnkHistory))).append("]");
$getById(ids.pageTitle).html(getText("{title} – ", { "title": i18n.intTitleShort })).append($createLink(qid, qid)).append($history);
document.title = getText("{title} – {qid}", {
"title": i18n.intTitle,
"qid": (qid !== undefined) ? qid : i18n.txtLoading
});
} else {
$getById(ids.pageTitle).html(getText("{title} – {loading}", {
"title": i18n.intTitleShort,
"loading": i18n.txtLoading
}));
}
$getById(ids.mwBodyContent).replaceWith($createDiv(classes.center, ids.mwBodyContent).append($.createSpinner({ "size": "large" })));
}
// Display actions menu
function showActions() {
var $content = $("<div />").append($createButton(addLanguage, "a", getText(i18n.btnAdd)));
$content.append("<div />").append($createButton(toggleCollapseAll, "e", getText(i18n.btnExpandCollapseAll)));
$content.append("<div />").append($createButton(clearAll, "c", getText(i18n.btnClearAll)));
$content.append("<div />").append($createButton(resetAll, "r", getText(i18n.btnResetAll)));
$content.dialog({
"buttons": [{
"text": getText(i18n.btnClose),
"click": function() {
$( this ).dialog("close");
}
}],
"html": $content,
"modal": true,
"title": getText(i18n.btnActions)
});
}
// Show item (if filled, having a sitelink or is in user's babel languages)
function showEntry(language, aliases, forceShow, newLabel, sitelinks, $sitelinksList) {
var lang = language.lang;
var isInUserLanguages = inArray(lang, lcGlobal.userLanguages);
var babelClass = (isInUserLanguages || forceShow) ? classes.babel : classes.nonBabel;
var description = (lcGlobal.entity.descriptions === undefined || lcGlobal.entity.descriptions[lang] === undefined) ? "" : lcGlobal.entity.descriptions[lang].value;
var label = (lcGlobal.entity.labels === undefined || lcGlobal.entity.labels[lang] === undefined) ? "" : lcGlobal.entity.labels[lang].value;
var $entry, $field, $header, $headerDiv, $headerLine, $preview, $previewDiv, $sub, $table;
var aliasCount, changed, cleanDescription, hasOriginalSitelinks, headerLine, isFilled, sitelinksList, value;
// Only show items that are filled, have a sitelink or are in the user's babel languages
isFilled = (label !== "" || description !== "" || aliases !== "");
hasOriginalSitelinks = sitelinks.length > 0 && ! lcGlobal.languages[lang].ignoreSitelinks;
if (forceShow || isInUserLanguages || hasOriginalSitelinks || isFilled) {
cleanDescription = description;
if (lcConfig.cleanDescription) {
// Clean description (remove Wiki link syntax and multiple spaces)
cleanDescription = XRegExp.replace(cleanDescription, XRegExp.cache("\\[\\[([^\\]]*?\\|)?(?<linktext>.+?)\\]\\]", "g"), "${linktext}");
cleanDescription = XRegExp.replace(cleanDescription, XRegExp.cache("\\s{2,}", "g"), " ");
}
// Header (language code, titles, links; labels and description when collapsed)
$entry = $createDiv(classes.entry + " " + babelClass);
sitelinksList = (sitelinks.length > 0) ? $sitelinksList : $("<i />", { "html": getText(i18n.txtNoSitelink) });
$headerDiv = $createDiv(classes.header + " " + classes.collapsed);
$header = $("<table />");
$headerLine = $("<tr />");
$headerLine.append($("<td />").append($createDiv(classes.triangle + " " + classes.triangleRight)));
$headerLine.append($("<td />", { "html": getText("[{lang}]: ", { "lang": lang }) }).append(sitelinksList));
$headerLine.append($("<td />", { "class": classes.headerLabel }));
$header.append($headerLine);
$headerDiv.append($header);
$entry.append($headerDiv);
headerLine = label;
if (description !== "") {
headerLine += ((headerLine !== "") ? ", " : "") + description;
}
if (headerLine !== "") {
headerLine = getText("({headerline})", { "headerline": $("<div />").text(headerLine.trim()).html() });
}
if (aliases !== "") {
aliasCount = aliases.split("|").length;
headerLine += getText(" (<i>{connector}{count} {aliases}</i>)", {
"connector": ((headerLine !== "") ? "+" : ""),
"count": String(aliasCount),
"aliases": ((aliasCount !== 1) ? i18n.txtAliases : i18n.txtAlias)
});
}
$headerLine.find("." + classes.headerLabel).html(headerLine);
// Content (labels, descriptions, aliases, preview)
$sub = $("<div />").css("display", "none");
$table = $("<table />");
$sub.append($("<div />", { "html": $table }));
$entry.append($sub);
$table.append($getEditorLine(lang, label, ids.suggestLabel, classes.label, ids.labelExisting, ids.labelNew, getText(i18n.ttExistingLabel, { "txt": (label === "") ? i18n.txtNothing : label })));
$table.append($getEditorLine(lang, aliases, ids.suggestAlias, classes.alias, ids.aliasesExisting, ids.aliasesNew, getText(i18n.ttExistingAliases, { "txt": (aliases === "") ? i18n.txtNothing : aliases })));
$table.append($getEditorLine(lang, description, ids.suggestDesc, classes.desc, ids.descExisting, ids.descNew, getText(i18n.ttExistingDescription, { "txt": (description === "") ? i18n.txtNothing : description })));
// Article preview
$preview = $("<table />");
$previewDiv = $createDiv(classes.preview, ids.previewArea + lang);
$sub.append($previewDiv);
$previewDiv.append($preview);
// Collapsing/expanding
$headerDiv.click(function(event) {
toggleCollapseInitial(language, $headerLine, $headerDiv, true, event.target, sitelinks, aliases, cleanDescription);
});
$getByClass(classes.main).append($entry);
// Change handler - color changes, reset button gets enabled/disabled
$entry.find("." + classes.newValue + " input[type=text]").on("change keypress paste focus textInput input", function() {
$field = $(this);
value = $field.val();
if (value !== $field.attr("oldval")) {
$field.attr("oldval", value);
changed = isChanged($field.attr("id"));
$field.toggleClass(classes.modified, changed);
$field.parents("tr").find("input[type=button]").first().attr("disabled", ! changed);
}
});
setExistingOrSuggestion(lang, ids.labelNew, label, newLabel, ids.suggestLabel);
// Auto-expand Babel languages
// - with sitelink and no label or description OR
// - with sitelink and no description, but a description suggestion from Wikidata statements OR
// - if they are the only one OR
// - if the "always expand" option is used
if (forceShow || (isInUserLanguages && lcConfig.alwaysExpandEditorLangs) || (isInUserLanguages && sitelinks.length > 0 && (label === "" || description === "")) || (! isInUserLanguages && lcConfig.alwaysExpandOtherLangs)) {
toggleCollapseInitial(language, $headerLine, $headerDiv, false, null, sitelinks, aliases, cleanDescription);
}
}
}
// Displays a error message on the screen
function showError(text) {
showStatus(text, true);
}
// Display a message
function showMessage(text) {
if (text !== undefined) {
$("<div />").html(text).dialog({
"buttons": [{
"click": function() {
$(this).remove();
},
"text": getText(i18n.btnOkay)
}],
"title": getText(i18n.intTitle),
"width": "auto"
});
}
}
// Add an option checkbox
function $showOption(name, config) {
return $("<label />").html(getText(name)).prepend($("<input />", {
"change": function() {
lcConfig[config] = this.checked;
},
"checked": lcConfig[config],
"title": getText(name),
"type": "checkbox"
}));
}
// Display the configuration dialog
function showOptions() {
var $fldEditorLanguages = $("<label />").html(getText(i18n.txtEditorLanguages)).append($("<input />", {
"change": function() {
var inputLanguages;
var inputValidated = true;
if (this.value !== undefined && this.value !== "") {
inputLanguages = this.value.toLowerCase().split(",");
}
if (inputLanguages === undefined || inputLanguages.length === 0) {
return;
}
// Validate input
$.each(inputLanguages, function() {
var lang = this;
if (lcGlobal.languages[lang] === undefined) {
inputValidated = false;
showMessage(getText(i18n.txtInvalidLanguage, { "code": lang }));
return false;
}
});
if (inputValidated) {
lcGlobal.userLanguages = inputLanguages;
// Apply changes in UI
lcGlobal.languages = {};
initProjects();
}
},
"value": lcGlobal.userLanguages.toString(),
"title": getText(i18n.txtEditorLanguages),
"type": "text"
}));
var $content = $("<div />").append($fldEditorLanguages);
$content.append("<div />").append($showOption(i18n.txtSkipIfEditorLangsSetOrNoLinks, "skipIfEditorLangsSetOrNoLinks"));
$content.append("<div />").append($showOption(i18n.txtSkipIfEditorLangsSet, "skipIfEditorLangsSet"));
$content.append("<div />").append($showOption(i18n.txtAlwaysExpandEditorLangs, "alwaysExpandEditorLangs"));
$content.append("<div />").append($showOption(i18n.txtAlwaysExpandOtherLangs, "alwaysExpandOtherLangs"));
$content.append("<div />").append($showOption(i18n.txtCleanDescription, "cleanDescription"));
$content.append("<div />").append($showOption(i18n.txtAutoFill, "autoFill"));
$content.append("<div />").append($showOption(i18n.txtDebugMode, "debugMode"));
$content.dialog({
"buttons": [{
"text": getText(i18n.btnOkay),
"click": function() {
$( this ).dialog("close");
}
}],
"html": $content,
"modal": true,
"title": getText(i18n.btnOptions)
});
}
// Displays a status message on the screen
function showStatus(text, error) {
var $bodyContent = $getById(ids.mwBodyContent);
debug(text);
if (error) {
$getByClass(classes.main).empty();
$bodyContent.append("<p><b>" + text + "</b></p>");
$bodyContent.find("." + classes.mwSpinner).remove();
enableButtons(false);
$("form > input").prop("disabled", false);
} else {
$bodyContent.append("<p>" + text + "</p>");
}
}
// Collapse or expand an entry
function toggleCollapse($headerLine, $headerDiv, slide, target) {
// Don't toggle if the link is clicked
if (! $(target).is("a")) {
$headerLine.find("." + classes.triangle).toggleClass(classes.triangleRight);
$headerDiv.toggleClass(classes.collapsed);
if (slide) {
$headerDiv.next().slideToggle();
} else {
$headerDiv.next().toggle();
}
}
}
// Collapses all entries if all are expanded, expands all collapsed entries otherwise
function toggleCollapseAll() {
var $collapsed = $getByClass(classes.collapsed);
if ($collapsed.length === 0) {
$getByClass(classes.header).click();
} else {
$collapsed.click();
}
}
// Expand an entry for the first time, so additionally load preview (or fill existing values)
function toggleCollapseInitial(language, $headerLine, $headerDiv, slide, target, sitelinks, aliases, cleanDescription) {
var lang = language.lang;
var projects = language.projects;
// Fill fields with existing or suggested values
if (sitelinks.length === 0) {
setFieldValue(ids.aliasesNew + lang, aliases, true);
setFieldValue(ids.descNew + lang, cleanDescription, true);
loadWikidataContent(lang, false, cleanDescription);
} else {
loadWikiContent(language, projects, aliases, cleanDescription, false);
}
$headerDiv.unbind("click");
$headerDiv.click(function(event) {
toggleCollapse($headerLine, $headerDiv, true, event.target);
});
toggleCollapse($headerLine, $headerDiv, slide, target);
}
// Enable and activate the suggestion button
function updateSuggestButton(btnId, fldId, suggestion) {
var $btnSuggest = $getById(btnId);
$btnSuggest.attr("disabled", false);
$btnSuggest.attr("title", getText(i18n.ttSuggest, { "txt": suggestion }));
$btnSuggest.click(function() {
setFieldValue(fldId, suggestion, false);
});
}
}());