User:Magnus Manske/wd edit.js

From Wikidata
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.
/*
	Allows for editing of Wikidata statements in a Listeria table from within Wikipedia
*/
mw.loader.load( ['jquery.ui'] );

$(document).ready ( function () {
	
	if ( mw.config.get('wgAction') != 'view' ) return ;
	
	
	var wd_edit = {
	
		t : {
			go2wd:"View or edit this property on Wikidata",
			set_value:"Set or edit this value",
			add_value:"Add a new value for this property",
			add:'add',
			edit:'edit',
			sticky_msg:'Your input has been added to Wikidata. It will become permanent in this table on the next update (within ~24h or manually).',
			no_commons_file:'No file with that name on Commons.',
			wd_api_fail:'Edit via Wikidata API has failed.'
		} ,
		
		init : function () {
			var me = this ;
			me.lang = mw.config.get('wgPageContentLanguage') ;
		} ,
		
		getItemString : function ( i , p ) {
			var me = this ;
			var key = p=='label' ? 'labels' : 'descriptions' ;
			if ( typeof i[key] == 'undefined' ) return '' ;
			if ( typeof i[key][me.lang] == 'undefined' ) return '' ;
			return i[key][me.lang].value ;
		} ,
		
		setDisplayString : function ( o , s , mode ) {
			var me = this ;
			$('#wd_hover').remove() ;
			var note = "<div style='background-color:#FFFF84;font-size:7pt;'>" + me.t.sticky_msg + "</div>" ;
			if ( mode == 'replace' ) {
				var a = o.find('a') ;
				if ( a.length === 0 ) $(o).text(s) ;
				else $(a[0]).text(s) ;
				$(o).append ( note ) ;
			} else if ( mode == 'append' ) {
				$(o).append(s+note) ;
			}
		} ,
		
		getWikidataEditToken : function ( callback ) {
			this.doAPI ( {
		        action: 'query',
		        meta: 'tokens'
		    } , function ( d ) {
		    	callback ( d.query.tokens.csrftoken ) ;
		    } ) ;
		} ,
		
		setLabelValue : function ( q , p , s , callback ) {
			var me = this ;
			var action = '' ;
			if ( p == 'label' ) action = 'wbsetlabel' ;
			if ( p == 'desc' ) action = 'wbsetdescription' ;
			me.editAPI ( {
				action:action,
				id:q,
				language:me.lang,
				value:s
			} , function ( d ) {
				callback ( d ) ;
			}) ;
		} ,

		editAPI : function ( data , callback ) {
			var me = this ;
			me.getWikidataEditToken ( function ( token ) {
				data.token = token ;
				me.doAPI ( data , callback ) ;
			}) ;
		} ,
		
		doAPI : function ( data , callback ) {
			data.format = 'json' ;
			data.origin = 'https:'+mw.config.get('wgServer') ;
			$.ajax( {
			    url: 'https://www.wikidata.org/w/api.php',
			    method:'POST',
			    data: data ,
			    xhrFields: {
			        withCredentials: true
			    },
			    dataType: 'json'
			} ).done( function ( d ) {
//					console.log('OK!',d) ;
					callback ( d ) ;
			} );
		} ,
		
		setItemString : function ( i , p , v , o ) {
			var me = this ;
			me.setLabelValue ( i.title , p , v , function ( d ) {
				if ( d.success ) me.setDisplayString ( o , v , 'replace' ) ;
				else alert ( me.t.wd_api_fail ) ;
			} ) ;
		} ,
		
		addPropValueWikidata : function ( q , p , v , o , proptype ) {
//			q = 'Q4115189' ; // SANDBOX ITEM FOR TESTING
//		console.log ( q , p , v , o , proptype ) ;
			var me = this ;
			var vv ;
			var vs ;
			if ( proptype == 'string' ) {
				vv = v ;
				vs = "<div>"+v+"</div>" ;
			} else if ( proptype == 'wikibase-item' ) {
				vv = { 'entity-type':'item' , 'numeric-id':v.replace(/\D/g,'') } ;
				vs = "<div><a href='//www.wikidata.org/wiki/"+v+"'>"+v+"</a></div>" ;
			} else if ( proptype == 'globe-coordinate' ) {
				vv = v ; //{"latitude":v.lat,"longitude":v.lon,"globe":"http://www.wikidata.org/entity/Q2","precision":v.prec} ;
				vs = "<div>"+v.latitude+"/"+v.longitude+"</div>" ;
			} else if ( proptype == 'time' ) {
				vv = v ;
				vs = "<div>" + v.time + "</div>" ;
			} else if ( proptype == 'external-id' ) {
				vv = v;
				vs = "<div>" + v + "</div>" ;
//			} else if ( proptype == 'quantity' ) {
			} else if ( proptype == 'commonsMedia' ) {
				vv = v ;
				vs = "<div>File:" + v + "</div>" ;
			} else {
				alert ( "addPropValueWikidata : Unknown proptype " + proptype ) ;
			}
			
			me.editAPI ( {
				action:'wbcreateclaim',
				entity:q.toUpperCase(),
				snaktype:'value',
				property:p.toUpperCase(),
				value:JSON.stringify(vv)
			} , function ( d ) {
				//console.log ( d ) ;
				if ( d.success ) me.setDisplayString ( o , vs , 'append' ) ;
				else alert ( me.t.wd_api_fail ) ;
			}) ;
		} ,
		
		// Loads the requested item and passes it as parameter to callback function
		loadItem : function ( q , callback ) {
			q = q.toUpperCase() ;
			$.getJSON ( 'https://www.wikidata.org/w/api.php?action=wbgetentities&ids='+q+'&format=json&callback=?' , function ( d ) {
				callback ( d.entities[q] ) ;
			} ) ;
		} ,
		
		showDialogSearchResults : function () {
			var me = this ;
			
			var h = '' ;
			$.each ( me.dialog_results , function ( k , v ) {
				h += "<div class='wb_dialog_option' value='" + k + "' style='margin-top:10px;'>" ;
				h += "<b>" + v.label + "</b>" ;
				if ( typeof v.desc != 'undefined' ) h += "<br/>" + v.desc ;
				h += "</div>" ;
			} ) ;
			$('#wd_dialog_select').html(h) ;
			$('div.wb_dialog_option').click ( function () {
				var o = $(this) ;
				me.dialog_selection = o.attr('value') ;
				$('#wd_dialog_ok').attr("disabled", true);
				$('div.wb_dialog_option').css({'background-color':''});
				if ( typeof me.dialog_results[me.dialog_selection].value == 'undefined' ) return ;
				o.css({'background-color':'#7BA7E1'});
				$('#wd_dialog_ok').attr("disabled", false);
			} ) ;

			if ( me.dialog_results.length == 1 && typeof me.dialog_results[0].value != 'undefined' ) {
				me.dialog_selection = 0 ;
				$('div.wb_dialog_option').css({'background-color':'#7BA7E1'});
				$('#wd_dialog_ok').attr("disabled", false);
			}
		} ,
		
		dialogValueProcess : function ( s , proptype ) {
			var me = this ;
			if ( me.last_dialog_search == s ) return ;
			me.last_dialog_search = s ;
			me.dialog_results = [] ;
			me.dialog_selection = undefined ;
			$('#wd_dialog_ok').attr("disabled", true);

			var rand = Math.random();
			me.last_rand = rand ;

			if ( proptype == 'wikibase-item' ) {
				$.getJSON('https://www.wikidata.org/w/api.php?callback=?',{
					action:'wbsearchentities',
					search:s,
					format:'json',
					language:me.lang,
					uselang:me.lang,
					type:'item',
					'continue':0
				} , function(d){
					if ( me.last_rand != rand ) return ;
					$.each ( (d.search||[]) , function ( k , v ) {
						me.dialog_results.push ( {label:v.label,desc:v.description,value:v.id} ) ;
					} ) ;
					me.showDialogSearchResults() ;
				} ) ;
			
			} else if ( proptype == 'commonsMedia' ) {
			
				$.getJSON ( 'https://commons.wikimedia.org/w/api.php?callback=?' , {
					action:'query',
					titles:'File:'+s,
					prop:'imageinfo',
					iiprop:'url',
					iiurlwidth:250,
					iiurlheight:250,
					format:'json'
				} , function ( d ) {
					if ( me.last_rand != rand ) return ;
					$.each ( d.query.pages , function ( k , v ) {
						if ( k == -1 ) {
							me.dialog_results.push ( {label:me.t.no_commons_file} ) ;
						} else {
							var name = v.title.replace(/^[^:]+:/,'') ;
							me.dialog_results.push ( {label:name,value:name,desc:"<img src='"+v.imageinfo[0].thumburl+"' />"} ) ;
						}
					} ) ;
					me.showDialogSearchResults() ;
				} ) ;
			
			} else {
				$.getJSON('https://www.wikidata.org/w/api.php?callback=?',{
					action:'wbparsevalue',
					values:s,
					format:'json',
					datatype:proptype,
					validate:1
				} , function(d){
					if ( me.last_rand != rand ) return ;
					var x = d.results[0] ;
					var out = {} ;
					if ( typeof x.valid !== 'undefined' ) {
						var l = "??" ;
						if ( proptype == 'globe-coordinate' ) l = x.value.latitude+'/'+x.value.longitude ;
						if ( proptype == 'time' ) l = x.value.time ;
						out = { value : x.value , label:l } ;
					} else {
						out = { label:x['error-info'] } ;
					}
					me.dialog_results.push ( out ) ;
					me.showDialogSearchResults() ;
				} ) ;
			}
		} ,
		
		getDialogValue : function ( p , proptype , callback ) {
			var me = this ;
			me.last_dialog_search = '' ;
			me.dialog_selection = undefined ;
			var h = "<div title='"+me.t.add_value+"' id='wd_dialog'>" ;
			h += "<div><form id='wd_dialog_form'><input type='text' id='wd_dialog_input'><input id='wd_dialog_ok' type='submit' value='OK' /></form></div>" ;
			h += "<div><div id='wd_dialog_select' style='width:100%;height:350px;overflow:auto;'></div></div>" ;
			h += "</div>" ;
			$('#wd_dialog').remove() ;
			$('body').append(h);
			$('#wd_dialog').dialog({ modal: true }) ;
			$('#wd_dialog_ok').attr("disabled", true);
			
			$('#wd_dialog_input').keyup ( function () {
				var s = $('#wd_dialog_input').val() ;
				me.dialogValueProcess ( s , proptype ) ;
			} ) ;
			
			$('#wd_dialog_form').submit ( function () {
				$('#wd_dialog').dialog( "close" );
				if ( typeof me.dialog_selection != 'undefined' ) {
					callback ( me.dialog_results[me.dialog_selection].value ) ;
				}
				return false ;
			} ) ;
			
			$('#wd_dialog_input').focus() ;
		} ,
		
		// Base function to set/add statement, label, description
		addPropValue : function ( o , q , p ) {
			var me = this ;
			me.loadItem ( q , function ( i ) {
				if ( p == 'label' || p == 'desc' ) {
					var s = me.getItemString(i,p) ;
					var v = prompt ( me.t.set_value , s ) ;
					if ( v !== null && v != s ) me.setItemString ( i , p , v , o ) ;
					return ;
				} else if ( p.match(/^[pP]\d+$/) ) {
					me.loadItem ( p , function ( pi ) {
//						console.log ( pi ) ;
						var proptype = pi.datatype ;
						if ( proptype == 'string' || proptype == 'external-id' ) {
							var v = prompt ( me.t.add_value , '' ) ;
							if ( v == null || v == '' ) return ;
							me.addPropValueWikidata ( q , p , v , o , proptype ) ;
						} else if ( proptype == 'wikibase-item' || proptype == 'globe-coordinate' || proptype == 'time' || proptype == 'commonsMedia' ) {
							var v = me.getDialogValue ( p , proptype , function ( v ) {
								me.addPropValueWikidata ( q , p , v , o , proptype ) ;
							} ) ;
//						} else if ( proptype == 'quantity' ) {
						} else {
							alert ( "Apologies, type " + proptype + " is not supported yet!" ) ;
						}
					} ) ;
				}
			} ) ;
			return false ;
		} ,
		
		prep4edit : function ( o_orig , q , p ) {
			var me = this ;
			$(o_orig).hover(function(){
				$('#wd_hover').remove() ;
				var o = $(this) ;
				var off = o.offset() ;
				var ry = parseInt(off.top) ;
				var h = "<div style='position:absolute;test-align:right;border-radius:3px;background-color:#EEE;border:1px solid #DDD;padding:2px;box-shadow: 10px 10px 5px #888888;z-index:5;top:"+ry+"px' id='wd_hover'>" ;
				var m = p.match(/^p(\d+)$/) ;

				var title = me.t.set_value ;
				if ( m !== null ) title = me.t.add_value ;
				var label = me.t.add ;
				if ( m === null ) label = me.t.edit ;
				h += "<div><a href='#' id='wd_hover_add' title='"+title+"'>["+label+"]</a></div>" ;

				var target = q ;
				if ( m !== null ) target += "#" + p.toUpperCase() ;
				h += "<div><a href='//www.wikidata.org/wiki/"+target+"' title='"+me.t.go2wd+"'>[WD]</a></div>" ;

				h += "</div>" ;
				$('body').append(h) ;
				$('#wd_hover').css ( { left:parseInt(off.left)+parseInt(o.width())-parseInt($('#wd_hover').width()) } ) ;
				
				$('#wd_hover_add').click ( function () {
					$('#wd_hover').remove() ;
					me.addPropValue ( o , q , p ) ;
					return false ;
				})
			},function(){
				$('#wd_hover').remove() ;
			}) ;
		}
	} ;
	
	
	
	// Prepare cells
	wd_edit.init() ;
	$('table.wd_can_edit').each ( function () {
		var table = $(this) ;
		table.find('tr').each ( function () {
			var classList = this.className.split(/\s+/);
			var q ;
			$.each ( classList , function (k,v) {
				var m = v.match(/^wd_q(\d+)/);
				if ( m === null ) return ;
				q = 'Q'+m[1];
			});
			if ( typeof q == 'undefined' ) return ;
			$(this).find('td').each ( function () {
				var classList = this.className.split(/\s+/);
				var me = this ;
				$.each ( classList , function (k,v) {
					var m = v.match(/^wd_(.+)/);
					if ( m === null ) return ;
					wd_edit.prep4edit ( me , q , m[1] ) ;
				} ) ;
			}) ;
		});
	});
});