User:Makyen/scripts/TemplateDataEditor.js
//
/**************************************************
Français
Gadget pour éditer les balises « templatedata » de l’extension « TemplateData » de MediaWiki sans avoir à manipuler le JSON.
Un lien « TDE » est ajouté dans la boîte à outils d’édition.
Il ouvre une fenêtre de modification permettant toutes les modifications autorisées.
- Auteurs : Ltrlg (TDE) & Salix alba (TDS)
- Dernière mise à jour : 19 août 2013
English
Gadget to edit “templatedata” tags for the MediaWiki extension TemplateData without having to edit JSON.
A “TDE” link is added in the edition toolbox when editing a template.
It opens a window allowing all modifications to the template data.
- Authors: Ltrlg (TDE) & Salix alba (TDS)
- Last update: 2013-08-19
Limitation
MediaWiki _allows_ using parameters like '{' (yes, really!)
But here, you can’t use it:
- TDS does not see these parameters
- The default name for a parameter matches /\{[0-9]+\}/ and TDE does not save if if there is any parameter containing '{'.
Of course, I don’t think anybody uses a '{' parameter. So this is not blocking in many cases.
Translations
- el: Xaris333, Geraki
- en: NicoV
- it: Jacopo Werther
- ja: Shirayuki
- ko: Kwj2772
- nl: Wolf Lambert
Source
**************************************************/
function TemplateDataEditor($) {
var
/* global objects */
ui, // The interface (instance of Interface)
td, // The current (or last) editor (instance of TemplateData)
/* unique identifiers (see trait UniqueElement) */
uniq = 0,
/* traits */
UniqueElement, DataForm,
/* tde regexps */
regExpTwoTags = /
regExpOneTag = /
matchType = 0,
/* indntation */
defaultIndent = '\t',
indent = defaultIndent,
/* languages */
userLanguage = mw.config.get('wgUserLanguage'),
contentLanguage = mw.config.get('wgContentLanguage'),
/* translations */
messages = {
"el": {
"apply": "Εφαρμογή",
"cancel": "Ακύρωση",
"close": "Κλείσιμο",
"collapse": "Κατάρρευση",
"colon": ": ",
"description": "Περιγραφή προτύπου",
"description-placeholder": "Τοποθετείστε μια περιγραφή του προτύπου",
"error-description": "Συνέβη κάποιο σφάλμα$2:\n\n$1",
"error-it-lang-inexistent": "Αυτή η γλώσσα ($1) δεν μπορεί να μετακινηθεί επειδή ο TDE δεν μπορεί να την εντοπίσει", // $1 είναι ο κωδικός της γλώσσας
"error-name-already-used": "Δεν μπορείτε να μετονομάσετε το στοιχείο επειδή το νέο όνομα χρησιμοποιείται ήδη",
"error-name-inexistent": "Αυτό το στοιχείο ($1) δεν μπορεί να μετακινηθεί επειδή ο TDE δεν μπορεί να το εντοπίσει.", // $1 είναι η ονομασία
"error-report": " (Αναφορά σφάλματος)",
"error-set-inexistent": "Αυτό το σύνολο ($1) δεν μπορεί να μετακινηθεί επειδή ο TDE δεν μπορεί να το εντοπίσει", // $1 είναι το id του συνόλου
"error-tds-not-loaded": "Η σελίδα δεν έχει φορτώσει",
"expand": "Ανάπτυξη",
"invalid-name": "“$1” δεν είναι κατάλληλο κλειδί",
"it-add": "Προσθέστε μια γλώσσα",
"it-otherlanguages-show": "Δείξτε τις γλώσσες ($1)", // $1 είναι η ονομασία της ξένης γλώσσας
"it-otherlanguages-hide": "Αποκρύψτε τις γλώσσες", // $1 είναι ο αριθμός των ξένων γλωσσών
"it-remove": "Αφαίρεση γλώσσας",
"param-add": "Προσθήκη παραμέτρου",
"param-aliases": "Άλλες ονομασίες",
"param-default": "Προεπιλεγμένη τιμή: ",
"param-deprecated": "Καταργημένη",
"param-deprecated-tooltip": "Λεπτομέρειες",
"param-description": "Περιγραφή",
"param-inherits": "Μεταβιβάζει",
"param-label": "Εμφανιζόμενο όνομα",
"param-name": "Πραγματικό όνομα",
"param-remove": "Αφαίρεση αυτής της παραμέτρου",
"param-required": "Απαιτείται",
"param-type": "Είδος: ",
"param-type-number": "Αριθμός",
"param-type-string": "Κείμενο",
"param-type-string/line": "Κείμενο (μία γραμμή)",
"param-type-string/wiki-page-name": "Τίτλος σελίδας",
"param-type-string/wiki-user-name": "Όνομα χρήστη",
"param-type-unknown": "Άγνωστο",
"parse-error": "Το δεδομένο δεν μπορεί να αναλυθεί. Αυτό προκαλείται σε γενικές γραμμές από ένα συντακτικό σφάλμα στην JSON ή από την παρουσία των δύο συνόλων δεδομένων.",
"preload-data": "Προσυμπληρώστε τα δεδομένα",
"preload-load": "Εκτέλεση",
"preload-none": "Μην προσυμπληρώσετε",
"preload-running": "Εκτελείται…",
"preload-select": "Προσυμπληρώστε από",
"no-data": "Δεν έχουν βρεθεί δεδομένα. Παρακαλώ προσθέστε μια ετικέτα
"section-description": "Περιγραφή",
"section-params": "Παράμετροι",
"section-sets": "Σύνολα",
"set-add": "Πρόσθεση συνόλου",
"set-label": "Όνομα: ",
"set-params": "Παράμετροι",
"set-remove": "Αφαίρεση αυτού του συνόλου",
"title": "Τροποποίηση δεδομένων προτύπου",
"title-documentation": "Τεκμηρίωση",
"start-tde": "Επεξεργασία δεδομένων προτύπου",
"use-pipes": " (να διαχωρίζονται με “|”)"
},
"en": {
"apply": "Apply",
"cancel": "Cancel",
"close": "Close",
"collapse": "Collapse",
"colon": ": ",
"description": "Description of this template",
"description-placeholder": "Enter a description of this template here",
"error-description": "An error happened$2:\n\n$1", // $2 is message(error-report) or '' ; $1 is the error
"error-it-lang-inexistent": "This language ($1) can’t be removed because TDE can’t find it anymore", // $1 is the language code
"error-name-already-used": "You can’t rename this element because the new name is already used",
"error-name-inexistent": "This element ($1) can’t be removed because TDE can’t find it anymore", // $1 is the name
"error-report": " (report it)",
"error-set-inexistent": "This set ($1) can’t be removed because TDE can’t find it anymore", // $1 is the id of the set
"error-tds-not-loaded": "The page hasn’t been loaded",
"expand": "Expand",
"invalid-name": "“$1” is not a valid key",
"it-add": "Add a language",
"it-otherlanguages-show": "Show the languages ($1)", // $1 is the number of foreign languages
"it-otherlanguages-hide": "Hide the languages", // $1 is the number of foreign languages
"it-remove": "Remove this language",
"param-add": "Add a parameter",
"param-aliases": "Other names",
"param-default": "Default value",
"param-deprecated": "Deprecated",
"param-description": "Description",
"param-description-placeholder": "Insert a description of this parameter here",
"param-inherits": "Documentation inherits from",
"param-label": "Displayed name",
"param-label-placeholder": "Enter name here",
"param-name": "Real name",
"param-remove": "Remove this parameter",
"param-required": "Required",
"param-type": "Type",
"param-type-number": "Number",
"param-type-string": "Text",
"param-type-string/line": "Text (one line)",
"param-type-string/wiki-page-name": "Page title",
"param-type-string/wiki-user-name": "User name",
"param-type-unknown": "Unknown",
"parse-error": "The data can’t be parsed. This is caused in general by a syntax error in the JSON or by the presence of two datasets.",
"preload-data": "Prefill the data",
"preload-load": "Run",
"preload-none": "Do not prefill",
"preload-running": "Running…",
"preload-select": "Prefill from",
"no-data": "No data has been found. Please, add a
"section-description": "Description",
"section-params": "Parameters",
"section-sets": "Sets",
"set-add": "Add a set",
"set-label": "Name",
"param-label-placeholder": "Enter name here",
"set-params": "Parameters",
"set-remove": "Remove this set",
"start-tde": "Modify template data",
"title": "Modify template data",
"title-documentation": "documentation",
"use-pipes": " (separated by pipes “|”)"
},
"fr": {
"apply": "Appliquer",
"cancel": "Annuler",
"close": "Fermer",
"collapse": "Fermer",
"colon": "\xA0: ",
"description": "Description de ce modèle",
"description-placeholder": "Entrez une description de ce modèle",
"error-description": "Une erreur est survenue$2\xA0:\n\n$1",
"error-invalid-name": "L’identifiant «\xA0$1\xA0» n’est pas valide",
"error-it-lang-inexistent": "Cette traduction ($1) ne peut être supprimée car TDE ne la trouve plus",
"error-name-already-used": "Vous ne pouvez pas renommer cet élément car le nom donné est déjà utilisé",
"error-name-inexistent": "Cet élément ($1) ne peut être supprimé car TDE ne le trouve plus",
"error-report": " (signalez-la)",
"error-set-inexistent": "Cet ensemble ($1) ne peut être supprimé car TDE ne le trouve plus",
"error-tds-not-loaded": "La page n’a pas pu être chargée",
"expand": "Ouvrir",
"it-add": "Ajouter une langue",
"it-no-language": "Pas de code de langue ($1)",
"it-otherlanguages-show": "Afficher les traductions ($1)",
"it-otherlanguages-hide": "Cacher les traductions",
"it-remove": "Retirer cette langue",
"param-add": "Ajouter un paramètre",
"param-aliases": "Autre noms",
"param-default": "Valeur par défaut",
"param-deprecated": "Obsolète",
"param-description": "Description",
"param-description-placeholder": "Entrez une description de ce paramètre",
"param-inherits": "Documentation héritée de",
"param-label": "Nom affiché",
"param-label-placeholder": "Ajoutez un nom",
"param-name": "Nom réel",
"param-remove": "Retirer ce paramètre",
"param-required": "Obligatoire",
"param-type": "Type",
"param-type-number": "Nombre",
"param-type-string": "Texte",
"param-type-string/line": "Texte (une ligne)",
"param-type-string/wiki-page-name": "Titre de page",
"param-type-string/wiki-user-name": "Nom d’utilisateur",
"param-type-unknown": "Inconnu",
"parse-error": "Les données ne peuvent pas être interprétées. Cela arrive généralement lorsqu’il y a une erreur de syntaxe dans le JSON ou lorsqu’il y a deux ensembles de données dans la page.",
"preload-data": "Pré-remplir les données du modèle",
"preload-load": "Exécuter",
"preload-none": "Ne pas pré-remplir",
"preload-running": "Chargement en cours…",
"preload-select": "Pré-remplir depuis",
"no-data": "Aucune donnée n’a été trouvée. Veuillez ajouter une balise
"section-description": "Description",
"section-params": "Paramètres",
"section-sets": "Ensembles",
"set-add": "Ajouter un ensemble",
"set-label": "Nom",
"set-label-placeholder": "Ajoutez un nom",
"set-params": "Paramètres",
"set-remove": "Retirer cet ensemble",
"start-tde": "Modifier les données du modèle",
"title": "Modifier les données du modèle",
"title-documentation": "documentation",
"use-pipes": " (séparés par des tubes «\xA0|\xA0»)"
},
"ja": {
"apply": "適用",
"close": "閉じる",
"colon": ": ",
"error-description": "エラーが発生しました$2:\n\n$1",
"param-add": "引数を追加",
"param-aliases": "その他の名前",
"param-default": "既定値",
"param-deprecated": "廃止予定",
"param-deprecated-tooltip": "詳細: ",
"param-description": "説明",
"param-inherits": "継承",
"param-label": "表示名",
"param-name": "名前",
"param-remove": "この引数を除去",
"param-required": "必須",
"param-type": "型: ",
"param-type-number": "数値",
"param-type-string": "文字列",
"param-type-string/line": "文字列 (1 行)",
"param-type-string/wiki-page-name": "ページ名",
"param-type-string/wiki-user-name": "利用者名",
"param-type-unknown": "不明",
"no-data": "データが見つかりませんでした。編集できるようにするには、
"section-description": "説明",
"section-params": "引数",
"section-sets": "集合",
"set-add": "集合を追加",
"set-label": "名前: ",
"set-params": "引数",
"set-remove": "この集合を除去",
"title": "TemplateData の変更",
"title-documentation": "説明文書",
"start-tde": "TemplateData の編集",
"use-pipe": " (パイプ記号「|」で区切る)"
},
"ko": {
"apply": "적용",
"close": "닫기",
"colon": ": ",
"error-description": "오류 발생$2:\n\n$1",
"param-add": "변수 추가하기",
"param-aliases": "다른 이름",
"param-default": "기본값",
"param-deprecated": "사용 중지",
"param-deprecated-tooltip": "자세한 정보",
"param-description": "설명",
"param-inherits": "상속받을 변수",
"param-label": "표시될 이름",
"param-name": "실제 이름",
"param-remove": "이 변수 제거",
"param-required": "필수",
"param-type": "Type: ",
"param-type-number": "숫자",
"param-type-string": "문자열",
"param-type-string/wiki-page-name": "문서 이름",
"param-type-string/wiki-user-name": "사용자 이름",
"param-type-unknown": "알 수 없음",
"no-data": "데이터가 없습니다. 편집을 가능하게 하려면
"section-description": "설명",
"section-params": "변수",
"section-sets": "집합",
"set-add": "집합 추가하기",
"set-label": "이름: ",
"set-params": "변수",
"set-remove": "이 집합 제거하기",
"title": "틀 데이터 수정하기",
"title-documentation": "설명 문서",
"start-tde": "틀 데이터 편집하기",
"use-pipes": " (파이프 “|”로 구분)"
},
"it": {
"apply": "Applica (a tuo rischio e pericolo, ogni abuso sarà segnalato)",
"colon": "\xA0: ",
"param-add": "Aggiungi un parametro",
"param-aliases": "Altri nomi",
"param-default": "Valore di default",
"param-deprecated": "Deprecato",
"param-deprecated-tooltip": "Dettagli",
"param-description": "Descrizione",
"param-inherits": "Eredità",
"param-label": "Nome visualizzato",
"param-name": "Nome effettivo",
"param-remove": "Rimuovi questo parametro",
"param-required": "Richiesto",
"no-data": "Spiacente. Nessun dato è stato trovato. Aggiungere un tag
"section-description": "Descrizione",
"section-params": "Parametri",
"set-add": "Aggiungi un set",
"title": "Benvenuto in Modifica TemplateData",
"title-documentation": "tutorial per negati",
"use-pipes": " (separati da pipes « | »)"
},
"nl": {
"apply": "Oké",
"cancel": "Annuleren",
"close": "Sluiten",
"collapse": "Inklappen",
"colon": ": ",
"description": "Beschrijving van dit sjabloon",
"description-placeholder": "Voer hier een beschrijving van dit sjabloon in",
"error-description": "Er deed zich een fout voor$2:\n\n$1", // $2 is message(error-report) or '' ; $1 is the error
"error-it-lang-inexistent": "De taal $1 kan niet worden verwijderd, omdat TDE hem niet meer kan vinden.", // $1 is the language code
"error-name-already-used": "Dit element kan niet van naam veranderd worden omdat de naam al in gebruik is.",
"error-name-inexistent": "Het element \"$1\" kan niet worden verwijderd, omdat TDE het niet meer kan vinden.", // $1 is the name
"error-report": " (rapporteer het)",
"error-set-inexistent": "De set \"$1\" kan niet worden verwijderd, omdat TDE hem niet meer kan vinden.", // $1 is the id of the set
"error-tds-not-loaded": "Deze pagina is niet geladen.",
"expand": "Uitklappen",
"invalid-name": "\"$1\" is geen valide sleutel",
"it-add": "Taal toevoegen",
"it-otherlanguages-show": "Toon de talen ($1)", // $1 is the number of foreign languages
"it-otherlanguages-hide": "Verberg de talen", // $1 is the number of foreign languages
"it-remove": "Verwijder deze taal",
"param-add": "Parameter toevoegen",
"param-aliases": "Andere namen",
"param-default": "Standaardwaarde",
"param-deprecated": "Verouderd",
"param-description": "Beschrijving",
"param-description-placeholder": "Voer hier een beschrijving van deze parameter in",
"param-inherits": "Documentatie wordt overgenomen van",
"param-label": "Getoonde naam",
"param-label-placeholder": "Voer hier een naam in",
"param-name": "Echte naam",
"param-remove": "Verwijder deze parameter",
"param-required": "Verplicht",
"param-type": "Type",
"param-type-number": "Nummer",
"param-type-string": "Tekst",
"param-type-string/line": "Tekst (één lijn)",
"param-type-string/wiki-page-name": "Paginatitel",
"param-type-string/wiki-user-name": "Gebruikersnaam",
"param-type-unknown": "Onbekend",
"parse-error": "De data kan niet geparsed worden. Dit is meestal de oorzaak van een fout in de JSON-syntax, of wanneer er twee datasets aanwezig zijn.",
"preload-data": "Vul de data vooraf in",
"preload-load": "Start",
"preload-none": "Vul de data niet vooraf in",
"preload-running": "Bezig…",
"preload-select": "Vul vooraf in van",
"no-data": "Er staat nog geen templatedata op dit sjabloon; plaats
"section-description": "Beschrijving",
"section-params": "Parameters",
"section-sets": "Sets",
"set-add": "Voeg een set toe",
"set-label": "Naam",
"param-label-placeholder": "Voer hier een naam in",
"set-params": "Parameters",
"set-remove": "Verwijder deze set",
"start-tde": "Pas de templatedata aan",
"title": "Templatedata aanpassen",
"title-documentation": "documentatie",
"use-pipes": " (gescheiden door pipes \"|\")"
}
},
documentations = { // Local pages (but full link for default)
"default": '//en.wikipedia.org/wiki/User:NicoV/TemplateDataEditor',
"enwiki": 'User:NicoV/TemplateDataEditor',
"frwiki": 'Utilisateur:Ltrlg/TemplateDataEditor',
"itwiki": 'Wikipedia:VisualEditor/TemplateData'
};
////////// Translation //////////
function messageLang(name, lang) {
var
res,
i,
T = [];
if( name == '' ) {
return '';
}
if( lang == 'qqx' ) {
res = '(-tde-' + name;
if( arguments.length > 2 ) {
res += ': ';
for(i=2; i T.push(arguments[i]); } res += T.join(', '); } return res + ')'; } else { if( messages[lang] && messages[lang][name] ) { res = messages[lang][name]; } else if( messages.en[name] ) { res = messages.en[name]; } else { arguments[1] = 'qqx'; return messageLang.apply(null, arguments) } // Replace vars for(i=arguments.length-2; i>0; i--) { res = res.replace(new RegExp('\\$'+i, 'g'), arguments[i+1]); } return res; } } function message(name) { var args = Array.prototype.slice.call(arguments); args.shift(); args.unshift(name, userLanguage); return messageLang.apply(null, args); } function documentationLink() { var wiki = mw.config.get('wgDBname'); if( documentations.hasOwnProperty( wiki ) ) { return mw.util.getUrl( documentations[wiki] ); } else { return documentations['default']; } } ////////// Getting & setting text (compatibility with WikEd) ////////// function getText() { if( window.wikEd && window.wikEd.useWikEd ) { WikEdUpdateTextarea(); } return $('#wpTextbox1').val(); } function setText(value) { $('#wpTextbox1').val(value); if( window.wikEd && window.wikEd.useWikEd ) { WikEdUpdateFrame(); } } ////////// Usefull functions ////////// function userError() { var e = new Error( message.apply(null, arguments) ); e.userError = true; return e; } function scriptError() { var e = new Error( message.apply(null, arguments) ); e.userError = false; return e; } function alertError(e) { alert(message( 'error-description', e.message, e.userError ? '' : message('error-report') )); console.error('“' + e.message + '”\nError thrown by ' + e.fileName + ' on line ' + e.lineNumber); } function ucfirst(str) { return str[0].toUpperCase() + str.substring(1, str.length); } function norm(str) { return ucfirst( str.replace('_', ' ') ); } function trim(str) { return str.replace(/^\s*(\S.*\S|\S)\s*$/, '$1'); } function trimArray(Arr) { var i = 0; for(; i Arr[i] = trim(Arr[i]); } return Arr; } function strToArr(str) { return trimArray( str.split('|') ); } function arrToStr(arr) { return arr.join(' | '); } function $clear() { return $(' } function selectValue(select) { // Select is a jQuery object $(' var res; $(select).children('option').each(function(){ if( $(this).prop('selected') ) { res = $(this).val(); return false; } }); return res; } function getIndent(text) { // Should work fine with any well-indented JSON var lines = text.split('\n'), i, maxLength = Infinity, indent = defaultIndent, localIndent; for(i=0; i try { localIndent = /^(\s*)\S/.exec(lines[i])[1]; if( localIndent.length < maxLength && localIndent.length != 0 ) { indent = localIndent; maxLength = localIndent.length; } } catch(e) { // Nothing to do, just a line without \S } } return indent; } function noCurlyBraceKey(object) { for(var i in object) { if( /[{}]/.test(i) ) { throw userError('error-invalid-name', i); } } } function arrayRemoveElement(array, i) { var newLength = array.length-1, j; for(j=i; j array[j] = array[j+1]; } array.length = newLength; } ////////// TemplateDataSkeleton (partial, adapted) ////////// function TemplateDataSkeletonFromText(text) { var pat = /\{\{\{([^\{\|\}\n<]+)(.)/g, // '{{{' then any char other than {|}\n< matches, name, newReq, oldReq, params = {}; while( (matches=pat.exec(text)) != null ) { name = trim(matches[1]); newReq = ( matches[2]== '}' ); oldReq = ( params[name] == null || params[name].required ); params[name] = { required: newReq && oldReq, label: norm(name) }; pat.lastIndex--; // need to backtrack one character } return { params: params }; } function TemplateDataSkeleton(page, cb) { function error() { alertError( scriptError('tds-not-loaded') ); cb({}); } $.ajax({ url: mw.util.wikiScript('api'), data: { action: 'query', prop: 'revisions', titles: page, rvprop: 'content', format: 'json' }, dataType: 'json', error: error, success: function( data ) { try { var pageId = Object.keys(data.query.pages)[0]; cb( TemplateDataSkeletonFromText(data.query.pages[pageId].revisions[0]['*'] || ) ); // if missing page } catch(e) { error(); } } }); } ////////// Actions ////////// function action(_options) { var options = $.extend({ type: null, desc: '', fn: function(){}, aClass: '', aId: '' }, _options), img = action.images[options.type]; return $('') .attr({ title: options.desc, href: '#', 'class': options.aClass, id: options.aId }) .click(function(){ options.fn.call(this); return false; }) .append($(' .attr({ alt: options.desc, src: '//upload.wikimedia.org/wikipedia/commons/thumb/' + img.hashStart[0] + '/' + img.hashStart + '/' + img.name + '/' + '24px-' + img.name + '.png' }) ); } action.images = { add: { hashStart: '8b', name: 'VisualEditor_-_Icon_-_Add-item.svg' }, remove: { hashStart: '0e', name: 'VisualEditor_-_Icon_-_Remove-item.svg' }, close: { hashStart: '8d', name: 'VisualEditor_-_Icon_-_Close.svg' }, expand: { hashStart: '2f', name: 'VisualEditor_-_Icon_-_Expand.svg' }, collapse: { hashStart: '32', name: 'VisualEditor_-_Icon_-_Collapse.svg' }, expand_inline: { hashStart: 'd3', name: 'VisualEditor_-_Icon_-_Move-ltr.svg' }, collapse_inline: { hashStart: '4e', name: 'VisualEditor_-_Icon_-_Move-rtl.svg' } }; ////////// class Interface ////////// function Interface() { var that = this; this.$title = $(' .text( message('title') ) .append(document.createTextNode(' (')) .append($('') .attr('href', documentationLink() ) .text( message('title-documentation') ) ) .append(document.createTextNode(')')); this.$body = $(' this.$buttonContainer = $(' this.$cont = $(' .attr('id', 'tde') .append($(' .append($(' .attr('id', 'tde-dialog') .append( this.$title ) .append( action({type: 'close', desc: message('close'), fn: function() { that.close(); }, aId: 'tde-close'}) ) .append( this.$body ) .append( this.$buttonContainer ) ) .hide(); $(document.body).append(this.$cont); } Interface.prototype = { clear: function() { this.$body.children().remove(); this.deleteButtons(); }, close: function() { this.$cont.fadeOut(); }, open: function() { this.$cont.fadeIn(); }, addCancelButton: function() { var that = this; this.addButton('cancel', function(){ that.close(); }); }, addButton: function(msg, fn) { this.$buttonContainer.append($('') .attr('type', 'button') .val( message(msg) ) .click(fn) ); }, deleteButtons: function() { this.$buttonContainer.children().remove(); }, replaceButton: function(fn) { this.deleteButtons(); this.$buttonContainer.append($('') .attr({ id: 'tde-apply', type: 'button' }) .val( message('apply') ) .click(fn) ); } }; ui = new Interface; ////////// class Renamer ////////// function Renamer(id, name, parent, readonly) { this.name = name; this.parent = parent; this.readonly = !! readonly; var that = this; this.$input = $('') .addClass('tde-renamer-name-input') .attr({ type: 'text', id: id }) .prop('readonly', this.readonly) .blur(function(){ that.exec(); }) .val(name); } Renamer.prototype = { getNode: function() { return this.$input; }, exec: function() { var newName = this.$input.val(); try { this.parent.renameElement(this.name, newName); this.name = newName; } catch(e) { alertError( e ); } this.$input.val(this.name); } }; ////////// trait UniqueElement ////////// UniqueElement = { defineUniq: function() { this.uniq = uniq; uniq++; } }; ////////// abstract class InterfaceText use UniqueElement ////////// function InterfaceText() { /* Call to this.construct from subclasses */ } InterfaceText.prototype = $.extend({}, UniqueElement, { construct: function(values, labelText, placeholderMessage, $cont) { this.defineUniq(); if( $.type(values) != 'object' ) { this.data = {}; this.data[contentLanguage] = values; } else { this.data = values; if( typeof this.data[contentLanguage] == 'undefined' ) { this.data[contentLanguage] = ''; } } this.numberOtherLanguages = Object.keys( this.data ).length - 1; this.label = labelText + (this.useColon ? message('colon') : ''); this.placeholder = placeholderMessage; this.$cont = $cont.addClass('tde-it');; this.createContent(); this.createInputs(); this.hideOther(); }, getClasses: function(lang) { return 'tde-it-lang tde-it-lang-' + (lang || contentLanguage); }, createInputs: function() { var i; for( i in this.data ) { this.createInput(i, i == contentLanguage); } }, onChange: function(domInput) { this.data[ /(^|\s)tde-it-lang-(\S+)(\s|$)/.exec( $(domInput).closest('.tde-it-lang').attr('class') )[2] ] = domInput.value; }, input: function($input, lang) { var that = this; return $input .val( this.data[lang] ) .addClass('tde-it-input') .attr('placeholder', messageLang(this.placeholder, lang)) .change(function(){ that.onChange(this); }); }, getPlaceholder: function(lang) { return messageLang(this.placeholder, lang); }, hideOther: function() { this.$expand.show(); this.$collapse.hide(); this.$cont.find('.tde-it-lang').each(function(){ if( ! $(this).hasClass('tde-it-lang-'+contentLanguage) && ! $(this).hasClass('tde-it-lang-'+userLanguage) ) { $(this).hide(); } }); this.$add.hide(); }, showOther: function() { this.$expand.hide(); this.$collapse.show(); this.$cont.find('.tde-it-lang').css('display', ''); // .show() does .css('display', 'inline'), but here we need inline-block, as declared in the stylesheet this.$add.show(); }, updateNumber: function() { this.$expand.attr('title', message('it-otherlanguages-show', this.numberOtherLanguages)); this.$expand.find('img').attr('alt', message('it-otherlanguages-show', this.numberOtherLanguages)); this.$collapse.attr('title', message('it-otherlanguages-hide', this.numberOtherLanguages)); this.$collapse.find('img').attr('alt', message('it-otherlanguages-hide', this.numberOtherLanguages)); }, actionSuffix: '', useColon: true, createToggleLinks: function(){ var that = this; this.$expand = action({ type: 'expand' + this.actionSuffix, aClass: 'tde-it-expand', fn: function(){ that.showOther(); return false; } }); this.$collapse = action({ type: 'collapse' + this.actionSuffix, aClass: 'tde-it-collapse', fn: function(){ that.hideOther(); return false; } }); this.updateNumber(); return $().add(this.$collapse).add(this.$expand); }, createAddLink: function() { var that = this; this.$add = action({ type: 'add', desc: message('it-add'), fn: function(){ that.addInput(); }, aClass: 'tde-add-language' }); return this.$add; }, getLangDiv: function(lang) { var res; this.$cont.find('.tde-it-lang').each(function(){ if( $(this).hasClass('tde-it-lang-'+lang) ) { res = $(this); return false; } }); return res; }, getValueInput: function(lang) { return this.getLangDiv(lang).find('.tde-it-input'); }, renameElement: function(from, to) { if( this.data.hasOwnProperty(to) ) { if( to != from ) { throw userError('error-name-already-used'); } } else if( this.data.hasOwnProperty(from) ) { this.getLangDiv(from).attr('class', this.getClasses(to)); this.getValueInput(to).attr('placeholder', this.getPlaceholder(to)); // "this.getValueInput(to)": "to" because the class have been modified by the line before this.data[to] = this.data[from]; delete this.data[from]; } else { throw scriptError('error-it-lang-inexistent', from); } }, removeElement: function(lang) { if( this.data.hasOwnProperty(lang) ) { delete this.data[lang]; this.getLangDiv(lang).remove(); } else { throw scriptError('error-it-lang-inexistent', lang); } }, $removeLang: function(readonly) { var that = this; if( readonly ) { return null; } else { return action({ type: 'remove', desc: message('it-remove'), fn: function(){ var $lang = $(this).closest('.tde-it-lang'); try { that.removeElement( $lang.find('.tde-renamer-name-input').val() ); $lang.remove(); } catch(e) { alertError(e); } } }); } }, untitledId: 0, addInput: function() { this.untitledId++; var lang = '{' + this.untitledId + '}'; this.data[lang] = ''; this.createInput(lang, false); }, getData: function() { noCurlyBraceKey( this.data ); if( Object.keys(this.data).length == 1 ) { return this.data[contentLanguage] || undefined; } else { // TODO delete empty strings return this.data; } }, /* abstract */ createContent: function() { }, /* abstract */ createInput: function(lang, readonly) { } }); ////////// class InterfaceTextBlock extends InterfaceText ////////// function InterfaceTextBlock() { this.construct.apply(this, arguments); this.$cont.addClass('tde-it-block'); } InterfaceTextBlock.prototype = $.extend(new InterfaceText(), { createContent: function() { this.$tbody = $(' var $caption = $(' .text(this.label) .append(this.createToggleLinks()); this.$cont .append($(' .append( $caption ) .append( this.$tbody ) ) .append( this.createAddLink() ) .append( $clear() ); }, useColon: false, createInput: function(lang, readonly) { this.$tbody.append($(' .attr('class', this.getClasses(lang)) .append($(' .attr('scope', 'row') .append( new Renamer(null, lang, this, readonly).getNode() ) ) .append($(' .append( this.input($(' ) .append($(' .append( this.$removeLang(readonly) ) ) ); } }); ////////// class InterfaceTextInline extends InterfaceText ////////// function InterfaceTextInline() { this.construct.apply(this, arguments); this.$cont.addClass('tde-it-inline'); } InterfaceTextInline.prototype = $.extend(new InterfaceText(), { createContent: function() { this.$span = $(''); this.$cont .text( this.label ) .append( this.$span ) .append( this.createAddLink() ) .append( this.createToggleLinks() ); }, actionSuffix: '_inline', createInput: function(lang, readonly) { this.$span.append($('') .attr('class', this.getClasses(lang)) .append( new Renamer(null, lang, this, readonly).getNode() ) .append( document.createTextNode( message('colon') ) ) .append( this.input($('').attr('type', 'text'), lang) ) .append( this.$removeLang(readonly) ) ); } }); ////////// trait DataForm ////////// DataForm = { getCont: function( name ) { return this['$'+name]; }, newline: function(cont) { this.getCont(cont).append(' return this; }, addInput: function(cont, type, key, label) { var $cont = this.getCont(cont), that = this, inputClass = 'tde-' + this.type + '-' + key, labelClass = inputClass + '-label', id = inputClass + '-' + this.uniq, $input; function addLabel(colon) { $cont .append($(' .addClass(labelClass) .attr('for', id) .text( label + ( type == 'array' ? message('use-pipes') : '' ) + ( colon ? message('colon') : '' ) ) ); } function addInput() { $cont .append( $input ); } switch(type) { case 'text': $input = $('') .addClass(inputClass) .attr({ type: 'text', id: id }) .val( this.data[key] || '' ) .change(function(){ that.change(key, this.value); }); addLabel(true); addInput(); break; case 'array': $input = $('') .addClass(inputClass) .attr({ type: 'text', id: id }) .val( arrToStr( this.data[key] || [] ) ) .change(function(){ that.changeArray(key, this.value); }); addLabel(true); addInput(); break; case 'checkbox': $input = $('') .addClass(inputClass) .attr({ type: 'checkbox', id: id }) .prop('checked', this.data[key]) .change(function(){ that.change(key, this.checked); }); addInput(); addLabel(false); break; case 'typeSelect': $input = $(' .addClass(inputClass) .attr('type', 'text') .attr('id', id) .append( Param.type('unknown', this.data.type) ) .append( Param.type('number', this.data.type) ) .append( Param.type('string', this.data.type) ) .append( Param.type('string/line', this.data.type) ) .append( Param.type('string/wiki-user-name', this.data.type) ) .append( Param.type('string/wiki-page-name', this.data.type) ) .change(function(){ that.changeType(this); }); addLabel(true); addInput(); break; } this['input-'+key] = $input; return this; }, addDescription: function(cont) { var $div = $(' this.getCont(cont).append($div); this.description = new InterfaceTextBlock( this.data.description || '', message(this.type + '-description'), this.type + '-description-placeholder', $div ); return this; }, addLabel: function(cont) { var $span = $(''); this.getCont(cont).append($span); this.label = new InterfaceTextInline( this.data.label || '', message(this.type + '-label'), this.type + '-label-placeholder', $span ); return this; }, change: function(key, newValue) { if( key == 'deprecated' ) { this.data[key] = newValue || false; } else { this.data[key] = newValue; } }, changeArray: function(key, str) { this.change(key, strToArr(str)); }, changeType: function(domSelect) { this.change('type', selectValue(domSelect)); } }; ////////// class Param uses UniqueElement, DataForm ////////// function Param(params, name, $list, data) { this.defineUniq(); var that = this; this.params = params; this.name = name; this.$cont = $(' this.data = data; this.$head = $(' this.$body = $(' this.$expand = action({type: 'expand', desc: message('expand'), fn: function() { that.expand(); }, aClass: 'tde-param-expand'}); this.$collapse = action({type: 'collapse', desc: message('collapse'), fn: function() { that.collapse(); }, aClass: 'tde-param-collapse'}); if( ( data.required || data.description == '' ) && ! data.inherits ) { this.expand(); } else { this.collapse(); } this.$cont .append( action({type: 'remove', desc: message('param-remove'), fn: function() { that.remove(); }, aClass: 'tde-remove-line'}) ) .append( this.$expand ) .append( this.$collapse ) .append( this.$head ) .append( this.$body ) .append( $clear() ); this.$head .append($(' .attr('for', 'tde-paramName-'+this.uniq) .text( message('param-name') + message('colon') ) ) .append( (new Renamer('tde-paramName-'+this.uniq, name, params)).getNode() ); this .addInput('head', 'checkbox', 'required', message('param-required')) .addInput('head', 'text', 'inherits', message('param-inherits')) .addLabel('body') .newline('body') .addInput('body', 'typeSelect', 'type', message('param-type')) .addInput('body', 'text', 'default', message('param-default')) .newline('body') .addInput('body', 'text', 'deprecated', message('param-deprecated')) .newline('body') .addInput('body', 'array', 'aliases', message('param-aliases')) .addDescription('body'); $list.append( this.$cont ); } Param.prototype = $.extend({}, UniqueElement, DataForm, { type: 'param', expand: function() { this.$collapse.show(); this.$expand.hide(); this.$body.show(); }, collapse: function() { this.$collapse.hide(); this.$expand.show(); this.$body.hide(); }, remove: function() { try { this.params.removeElement(this.name); this.$cont.remove(); } catch(e) { alertError( e ); } }, getCont: function( name ) { return this['$'+name]; }, getData: function() { this.data.label = this.label.getData(); this.data.description = this.description.getData(); return this.data; } }); Param.type = function(type, currentType) { return $(' .val(type) .text( message('param-type-' + type) ) .prop('selected', currentType == type); }; ////////// class Params ////////// function Params($list, data) { this.$list = $list; this.data = data; this.params = {}; var i, that = this; for( i in data ) { this.createLi(i); } this.$list.first().after( action({type: 'add', fn: function(){that.addItem();return false;}, desc: message('param-add'), aClass: 'tde-add-line'}) ); } Params.prototype = { untitledId: 0, addItem: function() { this.untitledId++; var name = '{' + this.untitledId + '}'; this.data[name] = {}; this.createLi(name); }, createLi: function(name) { this.params[name] = new Param(this, name, this.$list, this.data[name]); }, renameElement: function(from, to) { if( this.data.hasOwnProperty(to) ) { if( to != from ) { throw userError('error-name-already-used'); } } else if( this.data.hasOwnProperty(from) ) { this.data[to] = this.data[from]; this.params[to] = this.params[from]; delete this.data[from]; delete this.params[from]; } else { throw scriptError('error-name-inexistent', from); } }, removeElement: function(name) { if( this.data.hasOwnProperty(name) ) { delete this.data[name]; delete this.params[name]; } else { throw scriptError('error-name-inexistent', name); } }, getData: function() { for( var i in this.data ) { this.data[i] = this.params[i].getData(); } noCurlyBraceKey( this.data ); return this.data; } }; ////////// class Set uses UniqueElement, DataForm ////////// function Set(sets, $list, data) { this.defineUniq(); var that = this; this.sets = sets; this.$cont = $(' this.data = data; this.$body = $(' this.$cont .append( action({type: 'remove', desc: message('set-remove'), fn: function() { that.remove(); }, aClass: 'tde-remove-line'}) ) .append( this.$body ) .append( $clear() ); this .addLabel('body') .newline('body') .addInput('body', 'array', 'params', message('set-params')); $list.append( this.$cont ); } Set.prototype = $.extend({}, UniqueElement, DataForm, { type: 'set', remove: function() { try { this.sets.removeElement(this); this.$cont.remove(); } catch(e) { alertError( e ); } }, getData: function() { this.data.label = this.label.getData(); return this.data; } }); Param.type = function(type, currentType) { return $(' .val(type) .text( message('param-type-' + type) ) .prop('selected', currentType == type); }; ////////// class Sets ////////// function Sets($list, data) { this.$list = $list; this.data = data; this.sets = []; var i, that = this; for(i = 0; i this.createLi(i); } this.$list.first().after( action({type: 'add', fn: function(){that.addItem();return false;}, desc: message('set-add'), aClass: 'tde-add-line'}) ); } Sets.prototype = { addItem: function() { this.data.push({}); this.createLi(this.data.length-1); }, createLi: function(i) { this.sets[i] = new Set(this, this.$list, this.data[i]); }, removeElement: function(set) { var i = this.sets.indexOf(set); if( i >= 0 ) { arrayRemoveElement(this.data, i); arrayRemoveElement(this.sets, i); } else { throw scriptError('error-set-inexistent', name); } }, getData: function() { for(var i=0; i this.data[i] = this.sets[i].getData(); } return this.data; } }; ////////// class TemplateData ////////// function TemplateData(data) { this.data = data; this.cleanData(); ui.clear(); ui.$title .text( message('title') ) .append(document.createTextNode(' (')) .append($('') .attr('href', documentationLink() ) .text( message('title-documentation') ) ) .append(document.createTextNode(')')); this.dataToUi(); ui.open(); } TemplateData.regexpDouble = / TemplateData.regexpSimple = / TemplateData.prototype = { cleanData: function() { if( typeof this.data.description == 'undefined' ) this.data.description = ''; if( $.type(this.data.params) != 'object' ) this.data.params = {}; if( $.type(this.data.sets) != 'array' ) this.data.sets = []; }, dataToUi: function() { this.descriptionToUi(); this.paramsToUi(); this.setsToUi(); }, descriptionToUi: function() { var $div = $(' ui.$body .append($(' .text( message('section-description') ) ) .append($div); this.description = new InterfaceTextBlock(this.data.description, message('description'), 'description-placeholder', $div); }, paramsToUi: function() { var $list = $(' ui.$body.append($(' .text( message('section-params') ) ) .append($list); this.params = new Params($list, this.data.params); }, setsToUi: function() { var $list = $(' ui.$body.append($(' .text( message('section-sets') ) ) .append($list); this.sets = new Sets($list, this.data.sets); }, getData: function() { this.data.description = this.description.getData(); this.data.params = this.params.getData(); this.data.sets = this.sets.getData(); if( this.data.sets.length == 0 ) { delete this.data.sets; } return this.data; } }; ///////// Starting ///////// function write() { try { var newContent = ' text = getText(); switch(matchType) { case 1: text = text.replace(regExpOneTag, newContent); break; case 2: text = text.replace(regExpTwoTags, newContent); break; } setText(text); ui.close(); } catch(e) { alertError(e); } } function startWithData(data) { td = new TemplateData(data); ui.addCancelButton(); ui.addButton('apply', write); } function startWithTds(text) { var T = mw.config.get('wgPageName').replace('_', ' ').split('/'), i, page = '', $preload = $(' $select = $(' function $option(val, _label) { var label = _label || val; return $(' .val( val ) .text( label ); } for(i=0; i page += (page ? '/' : '') + T[i]; T[i] = page; } $select.append( $option('', message('preload-none')) ); for(i=T.length-1; i>-1; i-- ) { $select.append( $option( T[i] ) ); } ui.clear(); ui.$title.text( message('preload-data') ); $preload .append( $(' .attr('for', 'tde-preload-select') .text( message('preload-select') + message('colon') ) ) .append($select); ui.$body.html( $preload ); ui.addCancelButton(); ui.addButton('preload-load', function(){ var template = selectValue($select); if( ! template ) { startWithData({}); } else if( template == T[T.length-1] ) { // The current template startWithData( TemplateDataSkeletonFromText(text) ); } else { $preload .text( message('preload-running') ) .addClass('tde-preload-loading'); ui.deleteButtons(); TemplateDataSkeleton(template, startWithData); } }); ui.open(); } function startTDE() { var text = getText(), content, data = null; if( regExpTwoTags.test(text) ) { matchType = 2; content = regExpTwoTags.exec( text )[1]; indent = getIndent(content); if( /^\s*(\{\s*\})?\s*$/.test(content) ) { startWithTds(text); } else { try { data = JSON.parse( content ); } catch(e) { data = null; } if( data != null ) { startWithData(data); } else { alertError(userError('parse-error')); } } } else if( regExpOneTag.test(text) ) { matchType = 1; indent = defaultIndent; startWithTds(text); } else { matchType = 0; alertError(userError('no-data')); } } ////////// Add a link to start TDE ////////// function addTdeLink() { var $img = $(' $link = $('') .attr({ href: '#', title: message('start-tde') }) .append($img) .click(function(){ startTDE(); return false; }); if( mw.user.options.get('usebetatoolbar') ) { $img.attr('src', '//upload.wikimedia.org/wikipedia/commons/d/d8/TemplateData_-_Icon_-_Beta_toolbar.png'); mw.loader.using('ext.wikiEditor', function(){ $('#wikiEditor-ui-toolbar .section-main .group-insert').before($(' .addClass('group group-tde') .append($link) ); }); } else { $img.attr('src', '//upload.wikimedia.org/wikipedia/commons/6/63/TemplateData_-_Icon_-_Old_toolbar.png'); $('#toolbar').append($link); } } addTdeLink(); /* $( mw.util.addPortletLink('p-tb', '#', 'TemplateData', 'tde-toolbox', message('toolbox-label')) ).click(function(){ startTDE(); return false; }); */ } if( $.inArray( mw.config.get('wgNamespaceNumber'), [ 2, 10 ] ) !== -1 && $.inArray( mw.config.get('wgAction'), [ 'edit', 'submit' ] ) !== -1 ) { mw.loader.load( '//fr.wikipedia.org/w/index.php?title=Utilisateur:Ltrlg/styles/TemplateDataEditor.css&action=raw&ctype=text/css', 'text/css' ); $(document).ready(TemplateDataEditor); } // {{catégorisation JS|TemplateDataEditor}} // ')
')
')
')
')
')
')
');')
');
')
');
')
').attr('alt', message('start-tde')),