MediaWiki:Gadget-DragNDrop.js

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.
/*
(c) 2015 by Magnus Manske
Copy from [[User:Magnus Manske/dragref.js]]
To use, enable the gadget in your preferences
*/
	var mw_api = new mw.Api();
	var this_q = mw.config.get('wgTitle') ;
	var li_max_width = '400px' ;
	var wiki_lang = '' ;
	var wiki_project = '' ;

	var translations = require( './DragNDrop-i18n.json' );
	$.i18n().load( translations );

	function genericAPIaction ( json , callback ) {
		mw_api.postWithEditToken ( json ) .done ( callback ) .fail ( function () {
			console.log ( json ) ;
			alert( $.i18n( 'gadget-dragndrop-api-call-failed' ) );
		} ) ;
	}

	function addDraggedRef ( q , params ) {
		var json = {
			action:'wbsetreference',
			statement:params.statement,
			snaks:JSON.stringify(params.snaks),
			tags: 'gadget-dragndrop',
			format:'json'
		} ;
		genericAPIaction ( json , function ( d1 ) {
			var h = '<div>' + $.i18n( 'gadget-dragndrop-ref-added' ) + '</div>';
			if ( typeof d1.error != 'undefined' ) h = "<div>" + $.i18n('gadget-dragndrop-didnt-work') + "<br/><small>"+d1.error.info+"</small></div>" ;
			$(params.target).find('div.wikibase-statementview-references-container div.wikibase-statementview-references-heading').append(h);
		} ) ;
	}

	function copyReferenceToStatement ( q , statement , refhash , target ) {
		$.get ( '/w/api.php' , {
			action:'wbgetentities',
			format:'json',
			ids:q
		} , function ( d ) {
			var claims = d.entities[q].claims ;
			var snaks = '' ;

			$.each ( claims , function ( prop , v0 ) {
				if ( v0.id == statement ) return ; // No self-drag!
				$.each ( v0 , function ( k1 , v1 ) {
					if ( typeof v1.references == 'undefined' ) return ;
					$.each ( v1.references , function ( k2 , v2 ) {
						if ( v2.hash != refhash ) return ;
						snaks = v2.snaks ;
					} );
				} );
			} );

			if ( snaks === '' ) return ; // Reference hash not found


			var params = {
				statement : statement ,
				snaks : snaks ,
				target : target ,
				token : 'dummy'
			};

			addDraggedRef ( q , params ) ;

		} , 'json' );
	}


	function dropWikidataSource ( event , ui ,target , dragged ) {
		var q = '' ;
		var statement = '' ;
		$.each ( target.classList , function ( k , v ) {
			var m = v.match ( /^wikibase-statement-(Q\d+)(.+)$/i ) ;
			if ( m === null ) return ;
			q = m[1].replace(/^q/,'Q') ;
			statement = m[1]+m[2] ;
		} );
		if ( q === '' || statement === '' ) return ; // Something went wrong

		// Ignore when a reference is dropped onto the statement it belongs to
		if ( $( dragged ).closest( ".wikibase-statementview" ).attr( "id" ) === statement )
			return;

		var refhash = '' ;
		$.each ( dragged.classList , function ( k , v ) {
			var m = v.match ( /^wikibase-referenceview-(.+)$/ ) ;
			if ( m === null ) return ;
			refhash = m[1] ;
		} );
		if ( refhash === '' ) return ; // Something went wrong

		copyReferenceToStatement ( q , statement , refhash , target ) ;
	}

	function dropWikiSource ( event , ui ,target,dragged) {
		var rt = $(dragged).find ( 'span.reference-text' ) ;
		if ( rt.length === 0 ) return ; // Weird reference
		rt = $(rt[0]) ;

		var q = '' ;
		var statement = '' ;
		$.each ( target.classList , function ( k , v ) {
			var m = v.match ( /^wikibase-statement-(Q\d+)(.+)$/i ) ;
			if ( m === null ) return ;
			q = m[1].replace(/^q/,'Q') ;
			statement = m[1]+m[2] ;
		} );
		if ( q === '' || statement === '' ) return ; // Something went wrong

		var params = { statement : statement , snaks:{} , target:target , token:'dummy' } ;

		var a = $(rt.find('a.external.text')) ;
		if ( a.length == 1 ) { // External link
			var title = $(a[0]).text() ;
			title = title.replace ( /^"(.+)"$/ , "$1" ) ;

			params.snaks.P854 = [{ snaktype:'value',property:'P854',datavalue:{value:$(a[0]).attr('href'),type:'string'},datatype:'url' }] ;
			params.snaks.P1476 = [{ snaktype:'value',property:'P1476',datavalue:{value:{language:wiki_lang,text:title},type:'monolingualtext'},datatype:'monolingualtext' }] ;

			var retrieved = $(rt.find('span.reference-accessdate span.nowrap')) ;
			if ( retrieved.length == 1 ) {
				var s = $(retrieved[0]).text() ;
				var m = s.match(/^(\d\d\d\d-\d\d-\d\d)$/) ;
				if ( m !== null ) {
					var time = '+'+m[1]+'T00:00:00Z' ;
					params.snaks.P813 = [{ snaktype:'value',property:'P813',datavalue:{value:{time:time,timezone:0,before:0,after:0,precision:11,calendarmodel:'http://www.wikidata.org/entity/Q1985727'},type:'time'},datatype:'time' }] ;
				}
			}

		} else {
			alert( $.i18n('gadget-dragndrop-ref-not-supported') );
			return ;
		}

		addDraggedRef ( q , params ) ;
	}

	function dropWikiSourceISBN ( event , ui ,target,dragged) {
		var a = $(dragged).find ( 'a.mw-magiclink-isbn' ) ;
		if ( a.length != 1 ) return ; // Weird reference
		a = $(a[0]) ;
		var isbn = a.text() ;
		isbn = isbn.replace ( /[ -]/g , '' ) ;
		var prop = 'P212' ;
		var m = isbn.match ( /([0-9-]{13})/ ) ;
		if ( m === null ) {
			m = isbn.match ( /([0-9-]{10})/ ) ;
			prop = 'P957' ;
		}
		if ( m === null ) {
			alert( $.i18n('gadget-dragndrop-isbn-not-supported') );
			return ;
		}
		isbn = m[1] ;

		var q = '' ;
		var statement = '' ;
		$.each ( target.classList , function ( k , v ) {
			var m = v.match ( /^wikibase-statement-(Q\d+)(.+)$/i ) ;
			if ( m === null ) return ;
			q = m[1].replace(/^q/,'Q') ;
			statement = m[1]+m[2] ;
		} )
		if ( q === '' || statement === '' ) return ; // Something went wrong

		var params = { statement : statement , snaks:{} , target:target } ;
		params.snaks[prop] = [{ snaktype:'value',property:prop,datavalue:{value:isbn,type:'string'},datatype:'string' }] ;
		addDraggedRef ( q , params ) ;
	}

	function addStatementDialog ( o ) {

		$('#add_statement_dialog').remove() ;

		function addStatement () {
			var p = $('#add_statement_dialog_prop').val().toUpperCase() ;
			if ( !p.match(/^P\d+$/) ) {
				alert( $.i18n( 'gadget-dragndrop-property-id-required' ) );
				return false ;
			}

			var h = "<div class='wikibase-statementgroupview listview-item'>" ;

			var json = {} ;

			if ( o.type == 'item' ) {
				var q = o.items[0].q ;
				h += p + ': ' + q + ' (' + $.i18n( 'gadget-dragndrop-reload-page' ) + ')';
				json = { action:'wbcreateclaim' , entity:this_q , snaktype:'value' , property:p , value:{'entity-type':'item','numeric-id':q.replace(/Q/,'')}, tags: 'gadget-dragndrop' } ;
				json.value = JSON.stringify ( json.value ) ;
			}

			h += '</div>' ;

			genericAPIaction ( json , function ( d ) {
//				console.log ( d ) ;
				$('#mw-content-text div.wikibase-statementgrouplistview').append ( h ) ;
			} ) ;

			$( "#add_statement_dialog" ).dialog( "close" );
		}

		var h = '<div id="add_statement_dialog" title="' + $.i18n( 'gadget-dragndrop-add-statement' ) + '">';
		if ( o.type == 'item' ) {
			var q = o.items[0].q ;
			h += "<p><a target='_blank' href='/wiki/" + q + "'>" + q + "</a>: " + o.items[0].label + "</p>" ;
		}

		h += '<p>' + $.i18n( 'gadget-dragndrop-property' ) + ' <input type="text" id="add_statement_dialog_prop" /></p>';

		if ( o.type == 'item' ) {
			h += '<div id="add_statement_dialog_suggestions" style="max-height:300px;overflow:auto"><i>'
				+ $.i18n( 'gadget-dragndrop-loading-suggestions' ) + '</i></div>';
			var q = o.items[0].q ;
			var sparql = '' ;
			sparql += "PREFIX wd: <http://www.wikidata.org/entity/>\n" ;
			sparql += "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n" ;
			sparql += "SELECT ?p (count(?h) as ?cnt) WHERE { ?h ?p wd:"+q+" . OPTIONAL { ?h rdfs:label ?hl filter (lang(?hl) = 'en') } } group by ?p having(?cnt>1) order by desc(?cnt)" ;
			$.get ( '//query.wikidata.org/bigdata/namespace/wdq/sparql' , {
				format:'json',
				query:sparql
			} , function ( d ) {
				var patt = /^https{0,1}:\/\/www.wikidata.org\/prop\/direct\/(P\d+)$/ ;
				var props = [] ;
				$.each ( (d.results.bindings||[]) , function ( k , v ) {
					var m = patt.exec ( v.p.value ) ;
					if ( m == null ) return ;
					if ( props.length < 50 ) props.push ( m[1] ) ;
				} ) ;
				var pre = '' ;
				if ( props.length == 0 ) {
					pre = '<p>' + $.i18n( 'gadget-dragndrop-no-suggestions-found' ) + '</p>';
					props = ['P31','P279','P361','P131'] ;
				}
				$.get ( '/w/api.php' , {
					action:'wbgetentities',
					ids:props.join('|'),
					format:'json'
				} , function ( d2 ) {
					var prop2label = {} ;
					$.each ( (d2.entities||[]) , function ( p , v ) {
						$.each( ( v.labels || {} ), function ( lang , v2 ) {
							if ( typeof prop2label[ p ] === 'undefined' ) {
								prop2label[ p ] = v2.value;
							}
							if ( lang === 'en' ) {
								prop2label[ p ] = v2.value;
							}
						} );
					} );
					var h = pre ;
					$.each ( props , function ( dummy , p ) {
						h += "<p><a href='#' class='add_statement_dialog_suggestion' prop='"+p+"'>" ;
						h += (prop2label[p]||p) ;
						h += "</a> (" + p + ")</p>" ;
					} ) ;
					$('#add_statement_dialog_suggestions').html ( h ) ;
					$('#add_statement_dialog_suggestions a.add_statement_dialog_suggestion').click ( function () {
						var a = $(this) ;
						var p = a.attr('prop') ;
						$('#add_statement_dialog_prop').val ( p ) ;
						addStatement() ; // Click OK
					} ) ;
				} , 'json' ) ;
			} , 'json' ) ;
		}

		h += "</div>" ;

		$('body').append ( h ) ;
		$('#add_statement_dialog_prop').focus() ;
		var opt = {
			resizable: true,
			height: 300,
			modal: true,
			buttons: {},
		};
		opt.buttons[ $.i18n( 'gadget-dragndrop-add-statement-button' ) ] = addStatement;
		opt.buttons[ $.i18n( 'gadget-dragndrop-cancel-button' ) ] = function() {
			$( this ).dialog( 'close' );
		};
		$( '#add_statement_dialog' ).dialog( opt );
	}

	function dropImage ( image ) {
		var json = { action:'wbcreateclaim' , entity:this_q , snaktype:'value' , property:"P18" , value:image.replace(/_/g,' '), tags: 'gadget-dragndrop' } ;
		json.value = JSON.stringify ( json.value ) ;
		genericAPIaction ( json , function ( d ) {
			var h = "<div class='wikibase-statementgroupview listview-item'>" ;
			h += $.i18n( 'gadget-dragndrop-image-added' ) + '</div>';
			$('#mw-content-text div.wikibase-statementgrouplistview').append ( h ) ;
		} ) ;
	}

	function dropCommonsCat ( commonscat ) {
		var json = { action:'wbcreateclaim' , entity:this_q , snaktype:'value' , property:"P373" , value:commonscat.replace(/_/g,' '), tags: 'gadget-dragndrop' } ;
		json.value = JSON.stringify ( json.value ) ;
		genericAPIaction ( json , function ( d ) {
			var h = "<div class='wikibase-statementgroupview listview-item'>" ;
			h += $.i18n( 'gadget-dragndrop-commons-cat-added' ) + '</div>';
			$('#mw-content-text div.wikibase-statementgrouplistview').append ( h ) ;
		} ) ;
	}

	function dropCoord ( lat , lon ) {
		var json = { action:'wbcreateclaim' , entity:this_q , snaktype:'value' , property:"P625" , value:{"latitude":lat,"longitude":lon,"globe":"http://www.wikidata.org/entity/Q2","precision":0.000001}, tags: 'gadget-dragndrop' } ;
		json.value = JSON.stringify ( json.value ) ;
		genericAPIaction ( json , function ( d ) {
			var h = "<div class='wikibase-statementgroupview listview-item'>" ;
			h += $.i18n( 'gadget-dragndrop-coordinates-added' ) + '</div>';
			$('#mw-content-text div.wikibase-statementgrouplistview').append ( h ) ;
		} ) ;
	}

	function dropWikiLink(event,ui,target,dragged) {
		var page = $(dragged).attr('page') ;

		// Image
		if ( $(dragged).hasClass('image') && $(dragged).find('img').length > 0 ) {
			return dropImage ( page.replace ( /^.+?:/ , '' ) ) ;
		}

		// CommonsCat
		if ( $(dragged).attr('type') == 'commonscat' ) {
			var commonscat = $(dragged).attr('data') ;
			return dropCommonsCat ( decodeURIComponent(commonscat) ) ;
		}

		// Coord
		if ( $(dragged).attr('type') == 'coord' ) {
			var m = $(dragged).attr('data').split('/') ;
			return dropCoord ( m[0]*1 , m[1]*1 ) ;
		}

		// Follow redirect
		$.getJSON ( '//'+wiki_lang+'.'+wiki_project+'.org/w/api.php?callback=?' , {
			action:'query',
			redirects:1,
			prop:'pageprops',
			titles:page,
			format:'json'
		} , function ( d ) {
			if ( typeof d.query!='undefined' && typeof d.query.redirects!='undefined' && d.query.redirects.length>0 ) {
				$.each ( d.query.redirects , function ( k , v ) {
					if ( v.from.replace(/_/g,' ') != page ) return ;
					page = v.to ;
					return false ;
				} ) ;
			}

			// 'page' is now the redirected (if applicable) page title

			if ( typeof d.query == 'undefined' ) return ;
			if ( typeof d.query.pages == 'undefined' ) return ;
			var q = '' ;
			$.each ( d.query.pages , function ( k , v ) {
				if ( typeof v.pageprops == 'undefined' ) return ;
				if ( typeof v.pageprops.wikibase_item == 'undefined' ) return ;
				q = v.pageprops.wikibase_item ;
			} ) ;
			if ( !q.match(/^Q\d+$/) ) return ; // No Wikidata item

			addStatementDialog ( { items:[{q:q,label:page}] , type:'item' } ) ;

		} ) ;
	}

	function addDroppable () {

		// Drop statements
		$('#content').droppable ( {
			accept: function (dropped) {
				if ( $(dropped).hasClass('wd_dragref_wiki_link') ) return true ;
				return false ;
			} ,
			hoverClass: "wikibase-statementview-droptarget",
			drop: function( event, ui ) {
				var target = $(this)[0] ;
				var dragged = $(ui.draggable)[0] ;

				if ( $(dragged).hasClass('wd_dragref_wiki_link') ) dropWikiLink(event,ui,target,dragged) ;
			}
		} ) ;

		// Drop references
		$('div.wikibase-statementview').droppable({
			accept: function (dropped) {
				if ( $(dropped).hasClass('wikibase-referenceview') ) return true ;
				if ( $(dropped).hasClass('wd_dragref_wiki_ref') ) return true ;
				if ( $(dropped).hasClass('wd_dragref_wiki_isbn') ) return true ;
				return false ;
			} ,
			hoverClass: "wikibase-statementview-droptarget",
			drop: function( event, ui ) {
				var target = $(this)[0] ;
				var dragged = $(ui.draggable)[0] ;

				if ( $(dragged).hasClass('wikibase-referenceview') ) dropWikidataSource(event,ui,target,dragged) ;
				if ( $(dragged).hasClass('wd_dragref_wiki_ref') ) dropWikiSource(event,ui,target,dragged) ;
				if ( $(dragged).hasClass('wd_dragref_wiki_isbn') ) dropWikiSourceISBN(event,ui,target,dragged) ;
			}
		} );
	}

	function addDragDropWiki () {

		// Links
		$('#wb_dragref_mobileview a').each ( function () {
			var a = $(this) ;
			if ( a.parents('ol.references').length > 0 ) return ; // Don't do references
			var href = a.attr('href') ;
			if ( !href ) return;
			if ( a.hasClass('external') ) { // TODO drag URLs as references

				var data ;
				var m = href.replace(/_/g,' ').match ( /\bgeohack\.php.*params=([0-9\.+\-]+) ([NS]) ([0-9\.+\-]+) ([EW])/ ) ;
				if ( m !== null ) {
					var lat = m[1] * 1 ;
					var lon = m[3] * 1 ;
					if ( m[2] == 'S' ) lat = -lat ;
					if ( m[2] == 'W' ) lon = -lon ;
					data = lat + '/' + lon ;
				}

				if ( typeof data == 'undefined' ) {
					var m = href.match ( /https{0,1}:\/\/commons.wikimedia.org\/wiki\/Category:([^\?]+)/ ) ;
					if ( m == null ) return ;
					a.attr('type','commonscat') ;
					a.attr('data',m[1]) ;
				} else {
					a.attr('type','coord') ;
					a.attr('data',data) ;
				}

			} else { // Drag wiki links as statements
				if ( !href.match(/^\/wiki\//) ) return ; // No internal link
				if ( a.hasClass ( 'mw-magiclink-isbn' ) ) return ;
				var page = decodeURIComponent ( href.substr(6).replace(/_/g,' ') ) ;
				a.attr('page',page) ;
			}

			if ( typeof a.attr( 'title' ) == 'undefined' )
				a.attr( 'title', $.i18n( 'gadget-dragndrop-link-title' ) );
			else
				a.attr ( { title: a.attr( 'title' ) + $.i18n( 'gadget-dragndrop-appended-link-title' ) } );

			a.css({cursor:'grab',hover:'background-color:#6094DB'}) ;
			a.addClass('wd_dragref_wiki_link') ;
			a.draggable({
				zIndex: 1099,
				appendTo: "body",
				revert: false,
				cursor: "dragging",
				helper: "clone"
			});
		} ) ;

		// References
		$('#wb_dragref_mobileview ol.references li').css({'max-width':li_max_width}).each ( function () {
			var li = $(this) ;
			li.css({cursor:'grab'}) ;
			li.addClass('wd_dragref_wiki_ref') ;
			li.draggable({
				zIndex: 1099,
				appendTo: "body",
				revert: false,
				cursor: "dragging",
				helper: "clone"
			});
		} ) ;

		// Ref ISBNs
		$('#wb_dragref_mobileview li a.mw-magiclink-isbn').each ( function () {
			var li = $($(this).parents('li').get(0)) ;
			li.css({cursor:'grab','max-width':li_max_width}) ;
			li.addClass('wd_dragref_wiki_isbn') ;
			li.draggable({
				zIndex: 1099,
				appendTo: "body",
				revert: false,
				cursor: "dragging",
				helper: "clone"
			});
		} ) ;

	}

	function addWikiSourceLinks () {
		$.each ( [ 'wikipedia','wikibooks','wikinews','wikiquote','wikisource','wikivoyage' ] , function ( dummy , project ) {
			var group = 'div[data-wb-sitelinks-group="'+project+'"]' ;
			$(group+' span.wikibase-sitelinkview-page a').each ( function () {
				var a = $(this) ;
				var lang = a.attr('hreflang') ;
				var title = a.attr('href').replace(/^.*\/wiki\//,'').replace(/_/g,' ') ; //text() ;
				if ( typeof title == 'undefined' || title == '' ) return ;
//				console.log ( title ) ;
				var h = '<span style="display:table-cell;padding-left:10px;font-size:8pt;user-select:none;">'
					+ '[<a href="#" class="wb_dragref_wikilink" lang="' + lang + '">' + $.i18n( 'gadget-dragndrop-ref' ) + '</a>]</span>';
				a.parent().parent().parent().append(h) ;
				a.parent().parent().parent().find('a.wb_dragref_wikilink').attr('title',title) ;
			} ) ;
			$(group+' a.wb_dragref_wikilink').click ( function () {
				var o = $(this) ;
				var lang = o.attr('lang') ;
				var title = decodeURIComponent ( o.attr('title') ) ;
				wiki_lang = lang ;
				wiki_project = project ;

				var h = "<div id='wb_dragref_site_overlay' style='overflow:auto;position:fixed;right:0px;top:0px;bottom:0px;width:"+li_max_width+";z-index:1050;background-color:white;margin:5px;padding:2px;border-left:1px solid black'>" ;
				h += "<div style='text-align:right'><a href='#' onclick='$(\"#wb_dragref_site_overlay\").remove();return false'>X</a></div>" ;
				h += "<h2>" + title + "</h2>" ;
				h += '<div id="wb_dragref_mobileview"><i>' + $.i18n( 'gadget-dragndrop-loading' ) + '</i></div>';
				h += "</div>" ;
				$('#wb_dragref_site_overlay').remove() ;
				$('body').append(h) ;

				$.getJSON ( 'https://'+lang+'.'+project+'.org/w/api.php?callback=?' , {
					action:'parse',
					page:title,
					format:'json',
					prop:'text',
					mobileformat:1
				} , function ( d ) {
					if (d && d.parse && d.parse.text) {
						$('#wb_dragref_mobileview').html (d.parse.text['*'] ) ;
						addDragDropWiki() ;
					}


				} ) ;

			} ) ;
		} ) ;
	}

	function init () {

		addWikiSourceLinks() ;

		$("<style type='text/css'>div.wikibase-statementview-droptarget{ background-color:#FFFFC8;} </style>").appendTo("head");

		$('div.wikibase-referenceview').draggable({
			zIndex: 99,
			revert: false,
			helper: "clone"
		});

		addDroppable() ;
	}

	init();