Медиавики:Gadget-wikidataInfoboxExport-2.js

Мавод аз Википедиа — донишномаи озод

Эзоҳ: Баъди захира намудан, Шумо метавонед тағйиротҳои худро аз хотираи браузер гузариш карда, бубинед. Дар браузерҳои Mozilla / Firefox / Safari: тугмаи Shift-ро пахш намуда бо мушак Reload-ро пахш кунед, ё Ctrl-Shift-R-ро пахш намоед (Cmd-Shift-R барои компютерҳои Apple Mac); дар браузери IE: тугмаи Ctrl-ро пахш намуда бо мушак Refresh-ро пахш намоед, ё Ctrl-F5-ро пахш намоед; дар браузери Konqueror:: бо мушак Reload-ро пахш кунед, ё тугмаи F5-ро пахш намоед; дар браузери Opera ба Шумо пурра тоза кардани хотираи браузер ба воситаи Tools→Preferences лозим аст.

/**
 * Быстрый экспорт информации из карточек в Викиданные.
 * Вызов окна экспорта по двойному клику.
 */
( function ( mw, $ ) {
	var wdeConfig = {
		version: '2.4.7',
		languages: [ 'en' ],
		references: {
			// гирифташуда аз = Википедияи тоҷикӣ
			P143: [ {
				snaktype: 'value',
				property: 'P143',
				datavalue: {
					type: 'wikibase-entityid',
					value: { id: 'Q206855' }
				}
			} ]
		},
		units: {
			Q531: { search: [ 'св(?:\\.|етовых)\\s*(?:лет|года?)' ] },
			Q4916: { search: [ '^€', '^EUR' ] },
			Q4917: { search: [ '^(?:-|US)?\\$', 'доллари(?:\\sИМА)?', 'долл.' ] },
			Q7727: { search: [ 'дақиқа' ] },
			Q8146: { search: [ 'иен' ] },
			Q11573: { search: [ 'метр(?:а|ов)' ] },
			Q25344: { search: [ '^CHF' ] },
			Q41044: { search: [ 'рубл' ] },
			Q81292: { search: [ 'акр' ] }
		},
		reCirca: '(?:~|тақ(?:рибан)?|тақр(?:ибан)?)\\.?'
	};

	var api;
	var wdApi;

	var wdeBgImages = {
		default: '//upload.wikimedia.org/wikipedia/commons/f/ff/Wikidata-logo.svg',
		loader: '//upload.wikimedia.org/wikipedia/commons/c/cf/SVG_animated_loading_icon2.svg',
		success: '//upload.wikimedia.org/wikipedia/commons/8/80/Wikidata_heart_logo.svg'
	};

	var wdeBaseRevId;
	var wdePropertyIds = [ 'P2076', 'P2077' ]; // температура и давление для квалификаторов
	var wdeTypesMapping = {
		'commonsMedia': 'string',
		'external-id': 'string',
		'url': 'string',
		'wikibase-item': 'wikibase-entityid'
	};
	var wdeAlreadyExistingItems = {};
	var wdeWindowManager;
	var centuries = [ 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII',
		'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV',
		'XVI', 'XVII', 'XVIII', 'XIX', 'XX', 'XXI', 'XXII' ];

	/**
	 * Сравнивает значение из карточки и в Викиданных
	 */
	var wdeCanExportValue = function ( $field, claims, callbackIfCan ) {
		if ( !claims || !( claims.length ) ) {
			// не можем экспортировать только если это крупное изображение из ru-wiki
			if ( !( $field.find( '.image img[src*="/wikipedia/' + wdeConfig.language + '/"]' ).width() >= 80 ) ) {
				callbackIfCan();
			}
			return;
		}

		switch ( claims[ 0 ].mainsnak.datatype ) {
			case 'quantity':
				for ( var i = 0; i < claims.length; i++ ) {
					var parsedTime = wdeCreateTimeSnak( ( $field.text().match( /\(([^)]*\d\d\d\d)[,)\s]/ ) || [] )[ 1 ] );
					if ( parsedTime && ( claims[ i ].qualifiers || {} ).P585 ) {
						var claimPrecision = claims[ i ].qualifiers.P585[ 0 ].datavalue.value.precision;
						if ( parsedTime.precision < claimPrecision ) {
							claims[ i ].qualifiers.P585[ 0 ].datavalue.value.precision = parsedTime.precision;
						} else if ( parsedTime.precision > claimPrecision ) { // FIXME: потом уточнить в wd дату
							parsedTime.precision = claimPrecision;
						}
						var p585 = parsedTime ? wdeFormatDataValue( {
							type: 'time',
							value: parsedTime
						} )[ 0 ].innerText : '';

						if ( wdeFormatDataValue( claims[ i ].qualifiers.P585[ 0 ].datavalue )[ 0 ].innerText !== p585 ) {
							claims[ i ].qualifiers.P585[ 0 ].datavalue.value.precision = claimPrecision;
							continue;
						}
					}
					return;
				}
				callbackIfCan();
				break;
			case 'wikibase-item':
				value = wdeParseItems( $field, function ( values ) {
					var duplicates = [];
					for ( var i = 0; i < values.length; i++ ) {
						for ( var j = 0; j < claims.length; j++ ) {
							if ( values[ i ].wd.value.id === claims[ j ].mainsnak.datavalue.value.id ) {
								duplicates.push( values[ i ].wd.value.id );
							}
						}
					}
					if ( duplicates.length < values.length ) {
						if ( duplicates.length > 0 ) {
							var propertyId = claims[ 0 ].mainsnak.property;
							wdeAlreadyExistingItems[ propertyId ] = duplicates;
							if ( propertyId === 'P166' && values.length === claims.length )
								return;
						} 
						if ( claims.length > 0 ) {
							if ( claims[ 0 ].mainsnak.property === 'P19' || claims[ 0 ].mainsnak.property === 'P20' )
								return;
						}
						callbackIfCan();
					}
				} );
		}
		// По умолчанию, если есть claims, то экспортировать нельзя
	};

	/**
	 * Генерация ID утверждения
	 */
	var wdeClaimGuid = function ( entityId ) {
		var getRandomHex = function ( min, max ) {
			return ( Math.floor( Math.random() * ( max - min + 1 ) ) + min ).toString( 16 );
		};

		var template = 'xx-x-x-x-xxx';
		var guid = '';
		for ( var i = 0; i < template.length; i++ ) {
			if ( template.charAt( i ) === '-' ) {
				guid += '-';
				continue;
			}

			var hex;
			if ( i === 3 ) {
				hex = getRandomHex( 16384, 20479 );
			} else if ( i === 4 ) {
				hex = getRandomHex( 32768, 49151 );
			} else {
				hex = getRandomHex( 0, 65535 );
			}

			while ( hex.length < 4 ) {
				hex = '0' + hex;
			}

			guid += hex;
		}

		return entityId + '$' + guid;
	};

	/**
	 * Форматирование дат в datavalue для викиданных
	 */
	var wdeCreateTimeSnak = function ( timestamp, forceJulian ) {
		if ( !timestamp ) {
			return;
		}
		var months = [ 'январ', 'феврал', 'март', 'апрел', 'май', 'июн',
			'июл', 'август', 'сентябр', 'октябр', 'ноябр', 'декабр' ];
		var monthsGen = [ 'январ', 'феврал', 'март', 'апрел', 'май', 'июн',
			'июл', 'август', 'сентябр', 'октябр', 'ноябр', 'декабр' ];
		var result = { timezone: 0, before: 0, after: 0 };
		var isoDate;
		var dateParts;

		if ( dateParts = timestamp.match( /^([ivx]{1,5})(\sвека?)?$/i ) ) {
			isoDate = new Date( 0 );
			isoDate.setFullYear( centuries.indexOf( dateParts[ 1 ].toUpperCase() ) * 100 + 1 );
			result.precision = 7;
		} else if ( dateParts = timestamp.match( new RegExp( '^(' + months.join( '|' ) + ')\\s+([12]\\d{3})$' ) ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 2 ], months.indexOf( dateParts[ 1 ] ) ) );
			result.precision = 10;
		} else if ( dateParts = timestamp.match( new RegExp( '(\\d{1,2})\\s+(' + monthsGen.join( '|' ) + ')[,\\s]+([12]\\d{3})(?:\\sгод|\.?$)' ) ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 3 ], monthsGen.indexOf( dateParts[ 2 ] ), dateParts[ 1 ] ) );
			result.precision = 11;
		} else if ( dateParts = timestamp.match( /^(\d{1,2})\.(\d{1,2})\.(\d{2,4})$/ ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 3 ] < 100 ? 1900 + parseInt( dateParts[ 3 ] ) : dateParts[ 3 ], dateParts[ 2 ] - 1, dateParts[ 1 ] ) );
			result.precision = 11;
		} else if ( dateParts = timestamp.match( /^(\d{4})-(\d{1,2})-(\d{1,2})/ ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 1 ] < 100 ? 1900 + parseInt( dateParts[ 1 ] ) : dateParts[ 1 ], dateParts[ 2 ] - 1, dateParts[ 3 ] ) );
			result.precision = 11;
		} else if ( dateParts = timestamp.match( /(\d{3,4})-е/ ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 1 ], 0 ) );
			result.precision = 8;
		} else if ( dateParts = timestamp.match( /(\d{3,4})/ ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 1 ], 0 ) );
			result.precision = 9;
		} else if ( timestamp.match( /^(наст\.|настоящее|наше)\s+время$/ ) ) {
			return 'novalue';
		} else if ( timestamp.match( /^(\?|номаълум)$/ ) ) {
			return 'somevalue';
		} else return;

		try {
			result.time = '+' + isoDate.toISOString().replace( /\.000Z/, 'Z' );
		} catch ( e ) {
			return;
		}
		if ( result.precision < 11 ) {
			result.time = result.time.replace( /-\d\dT/, '-00T' );
		}
		if ( result.precision < 10 ) {
			result.time = result.time.replace( /-\d\d-/, '-00-' );
		}
		result.calendarmodel = 'http://www.wikidata.org/entity/Q' +
			( forceJulian || isoDate < new Date( Date.UTC( 1582, 9, 15 ) ) ? '1985786' : '1985727' );
		return result;
	};

	/**
	 * Отображение ошибки
	 */
	var wdeErrorDialog = function ( title, message ) {
		var errorDialog = new OO.ui.MessageDialog();
		wdeWindowManager.addWindows( [ errorDialog ] );
		wdeWindowManager.openWindow( errorDialog, {
			title: title,
			message: message
		} );
	};

	/**
	 * Достаем url сноски
	 */
	var wdeGetReference = function ( $field ) {
		var references = [];
		var $notes = $field.find( 'sup.reference a' );
		for ( var i = 0; i < $notes.length; i++ ) {
			var $externalLinks = $( $notes[ i ].hash.replace( /[!"$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g, '\\$&' ) + ' a[rel="nofollow"]' );
			for ( var j = 0; j < $externalLinks.length; j++ ) {
				var $externalLink = $( $externalLinks.get( j ) );
				if ( !$externalLink.attr( 'href' ).match( /(wikipedia.org|webcitation.org|archive.is)/ ) ) {
					var source = {
						snaks: {
							P854: [ {
								property: 'P854',
								datatype: 'url',
								snaktype: 'value',
								datavalue: {
									type: 'string',
									value: $externalLink.attr( 'href' )
								}
							} ]
						}
					};

					// P813
					var $accessed = $externalLinks.parent().find( 'small:contains("Проверено")' );
					if ( $accessed.length ) {
						var accessDate = wdeCreateTimeSnak( $accessed.first().text() );
						if ( accessDate ) {
							source.snaks.P813 = [ {
								property: 'P813',
								datatype: 'time',
								snaktype: 'value',
								datavalue: {
									type: 'time',
									value: accessDate
								}
							} ];
						}
					}

					// P1065 + P2960
					var $archiveLinks = $externalLinks.filter( 'a:contains("Архивировано")' );
					if ( $archiveLinks.length ) {
						var $archiveLink = $archiveLinks.first();
						source.snaks.P1065 = [ {
							property: 'P1065',
							datatype: 'url',
							snaktype: 'value',
							datavalue: {
								type: 'string',
								value: $archiveLink.attr( 'href' )
							}
						} ];

						var archiveDate = wdeCreateTimeSnak( $archiveLink.parent().text().replace( 'Архивировано', '' ).trim() );
						if ( archiveDate ) {
							source.snaks.P2960 = [ {
								property: 'P2960',
								datatype: 'time',
								snaktype: 'value',
								datavalue: {
									type: 'time',
									value: archiveDate
								}
							} ];
						}
					}

					references.push( source );
					break;
				}
			}
		}
		references.push( { snaks: wdeConfig.references } );
		return references;
	};

	/**
	 * Отформатировать источники для показа
	 */
	var wdeFormatDomains = function ( references ) {
		var $result = $( '<sup>' );
		for ( var i = 0; i < references.length; i++ ) {
			var p854 = references[ i ].snaks.P854;
			if ( p854 ) {
				var domain = p854[ 0 ].datavalue.value.replace( 'http://', '' ).replace( 'https://', '' ).replace( 'www.', '' );
				if ( domain.indexOf( '/' ) > 0 ) {
					domain = domain.substr( 0, domain.indexOf( '/' ) );
				}
				$result.append( $( '<a>' ).attr( 'href', p854[ 0 ].datavalue.value ).text( '[' + domain + ']' ) );
			}
		}
		return $result;
	};

	/**
	 * Форматирование wikidata values для показа пользователю
	 */
	var wdeFormatDataValue = function ( datavalue ) {
		var $label = $( '<span>' );
		switch ( datavalue.type ) {
			case 'time':
				if ( datavalue.value.precision === 7 ) {
					$label.text( centuries[ Math.floor( (datavalue.value.time.substr( 1, 4 ) - 1) / 100 ) ] + ' век' );
					break;
				}
				var options = {};
				if ( datavalue.value.precision > 7 ) {
					options.year = 'numeric';
				}
				if ( datavalue.value.precision > 9 ) {
					options.month = 'long';
				}
				if ( datavalue.value.precision > 10 ) {
					options.day = 'numeric';
				}
				var parsedDate = new Date( Date.parse( datavalue.value.time.substring( 1 ).replace( /-00/g, '-01' ) ) );
				$label.text( parsedDate.toLocaleString( 'ru-ru', options ) + ( datavalue.value.precision === 8 ? '-е' : '' ) );
				break;
			case 'quantity':
				$label.append( $( '<strong>' ).text( datavalue.value.amount ) );
				if ( datavalue.value.bound ) {
					$label.append( $( '<span>' ).text( ' ± ' + datavalue.value.bound ) );
				}
				if ( datavalue.value.unit !== '1' ) {
					var unitId = datavalue.value.unit.substr( datavalue.value.unit.indexOf( 'Q' ) );
					var name = ( ( wdeConfig.units[ unitId ] || {} ).label || {} ).value || unitId;
					var description = ( ( wdeConfig.units[ unitId ] || {} ).description || {} ).value || 'Невозможно подгрузить описание';
					$label.append( '&nbsp;' ).append( $( '<abbr>' ).attr( 'title', description ).text( name ) );
				}
				break;
			case 'wikibase-entityid':
				$label.append( $( '<strong>' ).text( datavalue.value.label ? datavalue.value.label : datavalue.value.id ) )
					.append( datavalue.value.description ? ' — ' + datavalue.value.description : '' );
				break;
		}

		for ( var propertyId in datavalue.qualifiers ) {
			if ( !datavalue.qualifiers.hasOwnProperty( propertyId ) ) {
				continue;
			}
			if ( propertyId === 'P1480' && datavalue.qualifiers[ propertyId ][ 0 ].datavalue.value.id === 'Q5727902' ) {
				$label.prepend( $( '<abbr>' ).attr( 'title', 'обстоятельства источника = около' ).text( 'ок.' ), ' ' );
			}
			var formatted = wdeFormatDataValue( datavalue.qualifiers[ propertyId ][ 0 ].datavalue );
			if ( formatted && $( '<span>' ).append( formatted ).text() ) {
				$label.append( $( '<span>' ).text( ' (' ).append( formatted ).append( ')' ) );
			}
		}

		return $label;
	};

	var wdeGetWikidataIds = function ( titles, callback ) {
		var languages = titles.map( function ( item ) {
			return item.language;
		} );
		languages = $.merge( languages, wdeConfig.languages );
		languages = $.unique( languages );

		var sites = titles.map( function ( item ) {
			return item.project;
		} );

		wdApi.get( {
			action: 'wbgetentities',
			sites: sites,
			languages: languages,
			props: [ 'labels', 'descriptions', 'claims' ],
			titles: titles.map( function ( item ) {
				return item.label;
			} )
		} ).done( function ( data ) {
			if ( data.success ) {
				var valuesObj = {};
				var value;
	
				for ( var entityId in data.entities ) {
					if ( !data.entities.hasOwnProperty( entityId ) || !entityId.match( /^Q/ ) ) {
						continue;
					}
	
					var entity = data.entities[ entityId ];
					var label = entity.labels.ru || entity.labels.en || entity.labels[ Object.keys( entity.labels )[ 0 ] ] || '';
					var description = entity.descriptions.ru || entity.descriptions.en || entity.descriptions[ Object.keys( entity.descriptions )[ 0 ] ] || '';
	
					if ( ( ( ( ( ( ( ( entity || {} ).claims || {} ).P31 || [] )[ 0 ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id === 'Q4167410' ) {
						continue; //пропускаем неоднозначности
					}
	
					var subclassFound = false;
					var subclassEntity = null;
					for ( var candidateId in data.entities ) {
						if ( !data.entities.hasOwnProperty( candidateId ) || !candidateId.match( /^Q/ ) || entityId === candidateId ) {
							continue;
						}
	
						subclassFound = [ 'P17', 'P31', 'P131', 'P279', 'P361' ].find( function ( propertyId ) {
							var values = ( ( ( data.entities[ candidateId ] || {} ).claims || {} )[ propertyId ] || [] );
							return values.find( function ( statement ) {
								var result = ( ( ( statement.mainsnak || {} ).datavalue || {} ).value || {} ).id === entityId;
								if ( result ) {
									subclassEntity = data.entities[ candidateId ];
								}
								return result;
							} );
						} );
	
						if ( subclassFound ) {
							break;
						}
					}
	
					if ( subclassFound ) {
						if ( subclassEntity ) {
							var subclassLabel = subclassEntity.labels.ru || subclassEntity.labels.en || subclassEntity.labels[ Object.keys( subclassEntity.labels )[ 0 ] ];
							mw.notify( 'Значение «' + label.value + '» исключено, уже имеется более точное: «' + subclassLabel.value + '»', {
								type: 'warn',
								tag: 'wikidataInfoboxExport-warn-precise'
							} );
						}
						continue; //пропускаем записи для которых есть более точные
					}
	
					value = {
						wd: {
							type: 'wikibase-entityid',
							value: {
								id: entityId,
								label: label ? label.value : label,
								description: description ? description.value : description
							}
						}
					};
					if ( label ) {
						var results = titles.filter( function ( item ) {
							return item.label.toLowerCase() === label.value.toLowerCase();
						} );
						if ( results.length === 1 ) {
							value.wd.qualifiers = results[ 0 ].qualifiers;
						}
					}
					value.label = wdeFormatDataValue( value.wd );
					delete value.wd.value.label;
					delete value.wd.value.description;
					valuesObj[ entityId ] = value;
				}

				callback( valuesObj );
			};
		} );		
	};

	var wdeParseItems = function ( $content, callback ) {
		var titles = [];

		var wdeProcessWbGetItems = function ( valuesObj ) {
			var values = $.map( valuesObj, function( value, index ) {
			    return [ value ];
			} );
			if ( values.length === 1 ) {
				value = values.pop();
				wdeAddQualifiers( $content, value.wd, value.label, function( value ) {
					callback( [ value ] );
				} );
			} else if ( callback ) {
				callback( values );
			}
		};
		var fixedValues = [
			{ property: 'P21', regexp: /жен/, item: 'Q6581072', label: 'женский' },
			{ property: 'P21', regexp: /муж/, item: 'Q6581097', label: 'мужской' },
			{ property: 'P552', regexp: /прав/, item: 'Q3039938', label: 'правша' },
			{ property: 'P552', regexp: /лев/, item: 'Q789447', label: 'левша' },
			{ property: 'P423', regexp: /прав/, item: 'Q10927630', label: 'праворукий удар' },
			{ property: 'P423', regexp: /лев/, item: 'Q10927615', label: 'леворукий удар' }
		];
		for ( var k = 0; k < fixedValues.length; k++ ) {
			if ( $content.attr( 'data-wikidata-property-id' ) === fixedValues[ k ].property && $content.text().match( fixedValues[ k ].regexp ) ) {
				var result = { success: true, entities: {} };
				result.entities[ fixedValues[ k ].item ] = {
					labels: { ru: { value: fixedValues[ k ].label } },
					descriptions: {}
				};
				wdeProcessWbGetItems( result );
				return;
			}
		}

		var $links = $content.find( 'a[title][class!=image][class!=new]' );
		var redirects = [];

		if ( $links.length ) {
			for ( var j = 0; j < $links.length; j++ ) {
				var $link = $( $links[ j ] );
				if ( $link.parents( '[data-wikidata-qualifier-id]' ).length ) {
					continue;
				}
				var extractedUrl = decodeURIComponent( $link.attr( 'href' ) ).replace( /^.*\/wiki\//, '' );
				if ( extractedUrl ) {
					extractedUrl = extractedUrl.replace( /_/g, ' ' ).trim();
					var value = {
						label: extractedUrl.charAt( 0 ).toUpperCase() + extractedUrl.substr( 1, extractedUrl.length - 1 ),
						language: wdeConfig.language,
						project: wdeConfig.project,
						qualifiers: {}
					};
					var match = $links[ j ].innerHTML.match( /(\d\d\d\d)\s*(?:год|г\.?)/ );
					if ( !match ) {
						match = $links[ j ].innerHTML.match( / — (\d\d\d\d)/ );
					}
					var extractedYear = match ? wdeCreateTimeSnak( match[ 1 ] ) : wdeCreateTimeSnak( ( $links[ j ].nextSibling || {} ).textContent );
					if ( extractedYear ) {
						value.qualifiers.P585 = [ {
							datatype: 'time', snaktype: 'value', property: 'P585',
							datavalue: {
								type: 'time',
								value: extractedYear
							}
						} ];
					}
					if ( $link.hasClass( 'extiw' ) ) {
						var m = $links[ j ].getAttribute( 'href' ).match( /^https:\/\/([a-z\-]+)\.(wik[^\.]+)\./ );
						if ( m && m[ 2 ] !== 'wikimedia' ) {
							value.language = m[ 1 ];
							value.project = m[ 1 ] + m[ 2 ].replace( 'wikipedia', 'wiki' );
						}
					}
					if ( $link.hasClass( 'mw-redirect' ) ) {
						redirects.push( extractedUrl );
					}
					titles.push( value );
					if ( $( $links[ j ] ).find( 'img' ) ) {
						redirects.push( extractedUrl );
					}
				}
			}
		} else if ( $content.text().trim() ) {
			// Если ссылок не нашлось, то пробуем искать статью по тексту
			var parts = $content.text().split( /[\n,;]+/ );
			for ( var i in parts ) {
				var year = '';
				var articleTitle = parts[ i ].replace( /\([^)]*\)/, function ( match ) {
					year = match.replace( /\(\)/, '' );
					return '';
				} ).trim();
				if ( articleTitle ) {
					var value = {
						label: articleTitle.charAt( 0 ).toUpperCase() + articleTitle.substr( 1, articleTitle.length - 1 ),
						language: wdeConfig.language,
						project: wdeConfig.project,
						qualifiers: {}
					};
					if ( wdeCreateTimeSnak( year ) ) {
						value.qualifiers.P585 = [ {
							datatype: 'time', snaktype: 'value', property: 'P585',
							datavalue: {
								type: 'time',
								value: wdeCreateTimeSnak( year )
							}
						} ];
					}
					titles.push( value );
				}
			}
			titles = $.unique( titles );
		}
		if ( redirects.length ) {
			api.get( {
				action: 'query',
				redirects: 1,
				titles: redirects
			} ).done( function ( data ) {
				if ( data.query && data.query.redirects ) {
					for ( var i = 0; i < data.query.redirects.length; i++ ) {
						for ( var j = 0; j < titles.length; j++ ) {
							var lcTitle = titles[ j ].label.substr( 0, 1 ).toLowerCase() + titles[ j ].label.substr( 1 );
							var lcRedirect = data.query.redirects[ i ].from.substr( 0, 1 ).toLowerCase() + data.query.redirects[ i ].from.substr( 1 );
							if ( lcTitle === lcRedirect ) {
								titles.splice( j + 1, 0, {
									label: data.query.redirects[ i ].to,
									language: wdeConfig.language,
									project: wdeConfig.project,
									year: titles[ j ].year
								} );
								j++;
							}
						}
					}
				}

				wdeGetWikidataIds( titles, wdeProcessWbGetItems );
			} );
		} else {
			wdeGetWikidataIds( titles, wdeProcessWbGetItems );
		}
	};

	/**
	 * Распарсивание количества и (опционально) точности
	 */
	var wdeParseQuantity = function ( text, forceInteger ) {
		var out = {
			value: {},
		};
		text = text.replace( /,/g, '.' ).replace( /[−|–]/g, '-' );

		// Sourcing circumstances (P1480) = circa (Q5727902)
		var circaMatch = text.match( new RegExp( wdeConfig.reCirca ) );
		if ( circaMatch ) {
			out.qualifiers = {
				P1480: [ {
					snaktype: 'value',
					property: 'P1480',
					datavalue: {
						type: 'wikibase-entityid',
						value: { id: 'Q5727902' }
					}
				} ],
			};
			text = text.replace( circaMatch[ 0 ], '' );
		}

		var magnitude = 0;
		if ( text.match( /тыс/ ) ) {
			magnitude += 3;
		} else if ( text.match( /млн|\dM(?:\s|$)|миллион|million/ ) ) {
			magnitude += 6;
		} else if ( text.match( /млрд|billion/ ) ) {
			magnitude += 9;
		} else if ( text.match( /трлн/ ) ) {
			magnitude += 12;
		} else {
			var match = text.match( /[\*|·]10(-?\d+)/ );
			if ( match ) {
				text = text.replace( /[\*|·]10(-?\d+)/, '' );
				magnitude += parseInt( match[ 1 ] );
			}
		}
		var decimals = text.split( '±' );
		if ( magnitude === 0 && forceInteger ) {
			decimals[ 0 ] = decimals[ 0 ].replace( /\./g, '' );
		}

		var amount = parseFloat( decimals[ 0 ].replace( /[^0-9.+-]/g, '' ) );
		if ( isNaN( amount ) ) {
			return;
		}

		var parts = amount.toString().match( /(\d+)\.(\d+)/ );
		var integral = parts ? parts[ 1 ].length : amount.toString().length;
		var fractional = parts ? parts[ 2 ].length : 0;
		if ( magnitude >= 0 ) {
			if ( magnitude <= fractional ) {
				out.value.amount = ( ( '1e' + magnitude ) * amount ).toFixed( fractional - magnitude );
			} else {
				out.value.amount = ( ( '1e' + fractional ) * amount ).toFixed( 0 ).replace( /$/, new Array( magnitude - fractional + 1 ).join( '0' ) );
			}
		} else {
			if ( magnitude >= -integral ) {
				out.value.amount = ( ( '1e' + magnitude ) * amount ).toFixed( fractional - magnitude );
			} else {
				out.value.amount = ( ( '1e-' + integral ) * amount ).toFixed( integral + fractional ).replace( /0\./, '0.' + new Array( -magnitude - integral + 1 ).join( '0' ) );
			}
		}

		if ( decimals.length > 1 ) {
			var bound = parseFloat( decimals[ 1 ].replace( /[^0-9.+-]/g, '' ) );

			if ( !isNaN( bound ) ) {
				if ( decimals[ 1 ].indexOf( '%' ) > 0 ) {
					bound = amount * bound / 100;
				} else {
					parts = bound.toString().match( /(\d+)\.(\d+)/ );
					integral = parts ? parts[ 1 ].length : amount.toString().length;
					fractional = parts ? parts[ 2 ].length : 0;
				}
				if ( magnitude >= 0 ) {
					if ( magnitude <= fractional ) {
						out.value.lowerBound = ( ( '1e' + magnitude ) * ( amount - bound ) ).toFixed( fractional - magnitude );
						out.value.upperBound = ( ( '1e' + magnitude ) * ( amount + bound ) ).toFixed( fractional - magnitude );
						out.value.bound = ( ( '1e' + magnitude ) * bound ).toFixed( fractional - magnitude ); // нужно для показа пользователю
					} else {
						out.value.lowerBound = ( ( '1e' + fractional) * ( amount - bound ) ).toFixed( 0 ).replace( /$/, new Array( magnitude - fractional + 1 ).join( '0' ) );
						out.value.upperBound = ( ( '1e' + fractional) * ( amount + bound ) ).toFixed( 0 ).replace( /$/, new Array( magnitude - fractional + 1 ).join( '0' ) );
						out.value.bound = ( ( '1e' + fractional ) * bound ).toFixed( 0 ).replace( /$/, new Array( magnitude - fractional + 1 ).join( '0' ) );
					}
				} else {
					if ( magnitude >= -integral ) {
						out.value.lowerBound = ( ( '1e' + magnitude ) * ( amount - bound ) ).toFixed( fractional - magnitude );
						out.value.upperBound = ( ( '1e' + magnitude ) * ( amount + bound ) ).toFixed( fractional - magnitude );
						out.value.bound = ( ( '1e' + magnitude ) * bound ).toFixed( fractional - magnitude );
					} else {
						out.value.lowerBound = ( ( '1e-' + integral ) * ( amount - bound ) ).toFixed( integral + fractional ).replace( /0\./, '0.' + new Array( -magnitude - integral + 1 ).join( '0' ) );
						out.value.upperBound = ( ( '1e-' + integral ) * ( amount + bound ) ).toFixed( integral + fractional ).replace( /0\./, '0.' + new Array( -magnitude - integral + 1 ).join( '0' ) );
						out.value.bound = ( ( '1e-' + integral ) * bound ).toFixed( integral + fractional ).replace( /0\./, '0.' + new Array( -magnitude - integral + 1 ).join( '0' ) );
					}
				}
			}
		}
		return out;
	};

	/**
	 * Распознавание единиц измерения в параметре карточки и его заголовке
	 */
	var wdeRecognizeUnits = function ( text, units, label ) {
		if ( Array.isArray( units ) && units.length === 0 ) {
			return [ '1' ];
		}
		var result = [];
		for ( var idx in units ) {
			if ( !units.hasOwnProperty( idx ) ) {
				continue;
			}
			var item = parseInt( idx ) >= 0 ? units[ idx ] : idx;
			var search = wdeConfig.units[ item ].search;
			for ( var j = 0; j < search.length; j++ ) {
				var expr = search[ j ];
				if ( search[ j ].charAt( 0 ) !== '^' ) {
					expr = '[\\d\\s\\.]' + expr;
					if ( search[ j ].length < 5 ) {
						expr = expr + '\\.?$';
					}
				}
				if ( text.match( new RegExp( expr, 'i' ) ) ) {
					result.push( item );
					break;
				} else if ( search[ j ].charAt( 0 ) !== '^' && label && label.match( new RegExp( '\\s' + search[ j ] + ':?$', 'i' ) ) ) {
					result.push( item );
					break;
				}
			}
		}
		return result;
	};

	/**
	 * Создание всех утверждений в Викиданных и отметка свойства экcпортированным
	 */
	var wdeCreateClaims = function ( propertyId, values, refUrl, revIds ) {
		var value = values.shift();
		revIds = revIds || [];
		if ( !value ) {
			// Все утверждения добавлены - переходим к установке меток
			wdeAddTags( propertyId, revIds );
			return;
		} else {
			value = JSON.parse( value );
		}
		var datatype = wdeConfig.properties[ propertyId ].datatype;
		var mainsnak = value.value.toString().match( /^(novalue|somevalue)$/ ) ? {
			snaktype: value.value,
			property: propertyId
		} : {
			snaktype: 'value',
			property: propertyId,
			datavalue: {
				type: wdeTypesMapping[ datatype ] ? wdeTypesMapping[ datatype ] : datatype,
				value: value.value
			}
		};
		var claim = {
			type: 'statement',
			mainsnak: mainsnak,
			id: wdeClaimGuid( mw.config.get( 'wgWikibaseItemId' ) ),
			references: refUrl,
			rank: 'normal'
		};
		if ( value.qualifiers ) {
			claim.qualifiers = value.qualifiers;
		}

		wdApi.postWithToken( 'csrf', {
			action: 'wbsetclaim',
			claim: JSON.stringify( claim ),
			baserevid: wdeBaseRevId
		} ).done( function ( claimData ) {
			if ( claimData.success ) {
				$( '#wd-drop-panel' )
					.css( {
						backgroundImage: 'url(' + wdeBgImages.success + ')',
						backgroundPositionY: '65%',
						backgroundSize: '80%'
					} )
					.hide( 'scale' );
				var valuesLeftStr = values.length ? ' (осталось ' + values.length + ')' : '';
				mw.notify( 'Значение свойства ' + propertyId + ' успешно добавлено в Викиданные' + valuesLeftStr, {
					tag: 'wikidataInfoboxExport-success'
				} );

				wdeBaseRevId = claimData.pageinfo.lastrevid;
				revIds.push( wdeBaseRevId );
				wdeCreateClaims( propertyId, values, refUrl, revIds );
			} else {
				wdeErrorDialog( 'Ошибка при сохранении', JSON.stringify( claimData ) );
			}
		} );
	};

	/**
	 * Установка меток гаджета для сделанных правок
	 * FIXME: Добавлять метки сразу, когда будет исправлен [[phab:T155109]]
	 */
	var wdeAddTags = function ( propertyId, revIds ) {
		wdApi.postWithToken( 'csrf', {
			action: 'tag',
			add: 'InfoboxExport gadget',
			revid: revIds
		} ).done( function ( data ) {
			var success = false;
			if ( data.tag ) {
				success = true;
				for ( var i = 0; i < data.tag.length; i++ ) {
					if ( data.tag[ i ].status !== 'success' ) {
						success = false;
						break;
					}
				}
			}
			if ( success ) {
				mw.notify( 'Метки правок в Викиданных успешно установлены', {
					tag: 'wikidataInfoboxExport-tags-success'
				} );

				$( '.no-wikidata[data-wikidata-property-id=' + propertyId + ']' )
					.removeClass( 'no-wikidata' )
					.off( 'dblclick' )
					.draggable( 'destroy' );
			} else {
				mw.notify( 'Ошибка при установке метки правок в Викиданных', {
					type: 'warn',
					tag: 'wikidataInfoboxExport-tags-error'
				} );
			}
		} );
	};

	/**
	 * Обёртка для предзагрузки информации о свойствах, исключающая уже загруженные.
	 */
	var wdeLoadProperties = function ( propertyIds ) {
		if ( !propertyIds || !propertyIds.length ) {
			return;
		}
		
		var realPropertyIds = [];
		for ( var i in propertyIds ) {
			var propertyId = propertyIds[ i ];
			if ( wdeConfig.properties[ propertyId ] === undefined ) {
				realPropertyIds.push( propertyId );
			}
		}
		
		if ( realPropertyIds.length ) {
			wdeRealLoadProperties( realPropertyIds );
		}
	};

	/**
	 * Предзагрузка информации о всех свойствах.
	 */
	var wdeRealLoadProperties = function ( propertyIds ) {
		if ( !propertyIds || !propertyIds.length ) {
			return;
		}

		var units = [];
		wdApi.get( {
			action: 'wbgetentities',
			languages: wdeConfig.languages,
			props: [ 'labels', 'datatype', 'claims' ],
			ids: propertyIds
		} ).done( function ( data ) {
			if ( !data.success ) {
				return;
			}

			for ( var propertyId in data.entities ) {
				if ( !data.entities.hasOwnProperty( propertyId ) ) {
					continue;
				}
				var entity = data.entities[ propertyId ];
				var label = entity.labels[ wdeConfig.language ] ? entity.labels[ wdeConfig.language ].value : entity.labels.en.value;
				wdeConfig.properties[ propertyId ] = {
					datatype: entity.datatype,
					label: label.charAt( 0 ).toUpperCase() + label.slice( 1 ),
					constraints: { qualifier: [] },
					units: []
				};
				if ( propertyId === 'P1128' || propertyId === 'P2196' ) {
					wdeConfig.properties[ propertyId ].constraints.integer = 1;
				}
				if ( entity.claims ) {
					// Ограничения свойства
					if ( entity.claims.P2302 ) {
						for ( var i in entity.claims.P2302 ) {
							switch ( ( ( ( ( entity.claims.P2302[ i ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id ) {
								case 'Q19474404':
								case 'Q21502410':
									wdeConfig.properties[ propertyId ].constraints.unique = 1;
									break;
							}
						}
					}
					// "Обязательный" квалификатор
					if ( entity.claims.P1646 ) {
						for ( var i in entity.claims.P1646 ) {
							var qualifierId = ( ( ( ( entity.claims.P1646[ i ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id;
							if ( qualifierId ) {
								wdeConfig.properties[ propertyId ].constraints.qualifier.push( qualifierId.toString() );
							}
						}
					}
					// Единицы измерения
					if ( entity.claims.P2237 ) {
						for ( var i in entity.claims.P2237 ) {
							var unitId = ( ( ( ( entity.claims.P2237[ i ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id;
							if ( unitId && unitId != 'Q21027105' ) {
								wdeConfig.properties[ propertyId ].units.push( unitId );
								units.push( unitId );
							}
						}
					}
				}
			}

			wdApi.get( {
				action: 'wbgetentities',
				languages: wdeConfig.languages,
				props: [ 'labels', 'descriptions', 'aliases', 'claims' ],
				ids: $.unique( units )
			} ).done( function ( unitData ) {
				if ( !unitData.success ) {
					return;
				}

				for ( var unitId in unitData.entities ) {
					var unit = unitData.entities[ unitId ];
					var unitSearch = wdeConfig.units[ unitId ] ? wdeConfig.units[ unitId ].search : [];
					if ( !wdeConfig.units[ unitId ] ) {
						wdeConfig.units[ unitId ] = {};
					}

					// Метка
					if ( unit.labels ) {
						wdeConfig.units[ unitId ].label = unit.labels[ wdeConfig.language ] ||
							unit.labels.en ||
							unit.labels[ Object.keys( unit.labels )[ 0 ] ];

						if ( unit.labels[ wdeConfig.language ] ) {
							unitSearch.push( unit.labels[ wdeConfig.language ].value.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' ) );
						}
					}

					// Описание
					if ( unit.descriptions ) {
						wdeConfig.units[ unitId ].description = unit.descriptions[ wdeConfig.language ] ||
							unit.descriptions.en ||
							unit.descriptions[ Object.keys( unit.labels )[ 0 ] ];
					}

					// Алиасы
					if ( unit.aliases && unit.aliases[ wdeConfig.language ] ) {
						for ( var i in unit.aliases[ wdeConfig.language ] ) {
							unitSearch.push( unit.aliases[ wdeConfig.language ][ i ].value.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' ) );
						}
					}

					// Обозначение единиц (P558)
					if ( unit.claims && unit.claims.P558 ) {
						for ( var i in unit.claims.P558 ) {
							var claim = unit.claims.P558[ i ];
							if ( claim.mainsnak &&
								claim.mainsnak.datavalue &&
								claim.mainsnak.datavalue.value
							) {
								unitSearch.push( claim.mainsnak.datavalue.value.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' ) );
							}
						}
					}
					wdeConfig.units[ unitId ].search = unitSearch;
					
					localStorage.setItem( 'wdeConfig2', JSON.stringify( wdeConfig ) );
				}
			} );
		} );
	};

	var wdeAddQualifiers = function ( $field, value, $label, callback ) {
		var $qualifiers = $field.find( '[data-wikidata-qualifier-id]' );
		if ( $qualifiers.length ) {
			$label = $( '<div>' ).append( $label );
		}

		var addQualifierValue = function ( qualifierId, qualifierValue, qualifierLabel ) {
			if ( value.qualifiers === undefined ) {
				value.qualifiers = {};
			}
			if ( value.qualifiers[ qualifierId ] === undefined ) {
				value.qualifiers[ qualifierId ] = [];
			}
			var datatype = wdeConfig.properties[ qualifierId ].datatype;
			value.qualifiers[ qualifierId ].push( {
				snaktype: 'value',
				property: qualifierId,
				datavalue: {
					type: wdeTypesMapping[ datatype ] ? wdeTypesMapping[ datatype ] : datatype,
					value: qualifierValue
				}
			} );
			$label.append( $( '<p>' )
				.append( $( '<a>' )
					.attr( 'href', '//www.wikidata.org/wiki/Property:' + qualifierId )
					.text( wdeConfig.properties[ qualifierId ].label )
				)
				.append( $( '<span>' ).text( ': ' ) )
				.append( qualifierLabel )
			);
		};

		var qualifierTitles = {};
		for ( var q = 0; q < $qualifiers.length; q++ ) {
			var $qualifier = $( $qualifiers[ q ] );
			var qualifierId = $qualifier.data( 'wikidata-qualifier-id' );
			var qualifierValue = $qualifier.text();
			if ( wdeConfig.properties[ qualifierId ].datatype === 'monolingualtext' ) {
				qualifierValue = {
					text: $qualifier.text(),
					language: $qualifier.attr( 'lang' ) || wdeConfig.language
				};
				addQualifierValue( qualifierId, qualifierValue, $qualifier.text() );
			} else if ( wdeConfig.properties[ qualifierId ].datatype === 'wikibase-item' ) {
				if ( qualifierTitles[ qualifierId ] === undefined ) {
					qualifierTitles[ qualifierId ] = [];
				}
				
				var $links = $qualifier.find( 'a[title][class!=image][class!=new]' );
				if ( $links.length ) {
					for ( var l = 0; l < $links.length; l++ ) {
						var $link = $( $links[ l ] );
						var extractedUrl = decodeURIComponent( $link.attr( 'href' ) ).replace( /^.*\/wiki\//, '' );
						if ( extractedUrl ) {
							extractedUrl = extractedUrl.replace( /_/g, ' ' ).trim();
							var title = {
								label: extractedUrl.charAt( 0 ).toUpperCase() + extractedUrl.substr( 1, extractedUrl.length - 1 ),
								language: wdeConfig.language,
								project: wdeConfig.project,
								qualifiers: {}
							};
							if ( $link.hasClass( 'extiw' ) ) {
								var m = $link.attr( 'href' ).match( /^https:\/\/([a-z\-]+)\.(wik[^\.]+)\./ );
								if ( m && m[ 2 ] !== 'wikimedia' ) {
									title.language = m[ 1 ];
									title.project = m[ 1 ] + m[ 2 ].replace( 'wikipedia', 'wiki' );
								}
							}
							qualifierTitles[ qualifierId ].push( title );
						}
					}
				} else {
					qualifierTitles[ qualifierId ].push( {
						label: qualifierValue.charAt( 0 ).toUpperCase() + qualifierValue.substr( 1, qualifierValue.length - 1 ),
						language: wdeConfig.language,
						project: wdeConfig.project,
						qualifiers: {}
					} );
				}
			}
		};

		var processItemTitles = function ( itemTitles, callback ) {
			if ( Object.keys( itemTitles ).length ) {
			    var qualifierId = Object.keys( itemTitles ).shift();
				var qualifierItemTitles = itemTitles[ qualifierId ];
				delete itemTitles[ qualifierId ];
				wdeGetWikidataIds( qualifierItemTitles, function ( valuesObj ) {
					for ( var entityId in valuesObj ) {
						var valueObj = valuesObj[ entityId ];
						addQualifierValue( qualifierId, valueObj.wd.value, valueObj.label );
					}
					processItemTitles( itemTitles, callback );
				} );
			} else {
				callback( {
					wd: value,
					label: $label
				} );
			}
		};
		processItemTitles( qualifierTitles, callback );
	};

	/**
	 * Парсинг значений из параметров перед выводом диалога
	 */
	var wdePrepareDialog = function ( $field, propertyId ) {
		var values = [];
		var datatype = wdeConfig.properties[ propertyId ].datatype;

		var $content = $field.clone();
		$content.find( 'sup.reference' ).remove();
		$content.find( '[style*="display:none"]' ).remove();

		switch ( datatype ) {
			case 'commonsMedia':
				var $imgs = $content.find( 'img' );
				$imgs.each( function () {
					var $img = $( this );
					var src = $img.attr( 'src' );
					if ( !src.match( /upload.wikimedia.org\/wikipedia\/commons/ ) ) {
						return;
					}
					var srcParts = src.split( '/' );
					var fileName = srcParts.pop();
					if ( fileName.match( /^\d+px-/ ) ) {
						fileName = srcParts.pop();
					}
					fileName = decodeURIComponent( fileName );
					fileName = fileName.replace( /_/g, ' ' );
					var value = { value: fileName };
					var $label = $img.clone()
						.attr( 'title', fileName )
						.css( 'border', '1px dashed #a2a9b1' );

					wdeAddQualifiers( $field, value, $label, function( valueObj ) {
						values.push( valueObj );
					} );
				} );
				break;

			case 'external-id':
				var externalId = $content.data( 'wikidata-external-id' ) || $content.text();
				if ( propertyId === 'P345' ) { // IMDB
					externalId = $content.find( 'a' ).first().attr( 'href' );
					externalId = externalId.substr( externalId.lastIndexOf( '/', externalId.length - 2 ) ).replace( /\//g, '' );
				} else {
					externalId = externalId.toString().replace( /^ID\s/, '' ).replace( /\s/g, '' );
				}
				var sparql = 'SELECT * WHERE { ?item wdt:' + propertyId + ' "' + externalId + '" }';

				$.ajax( {
					url: 'https://query.wikidata.org/sparql?format=json&query=' + sparql,
					success: function ( data ) {
						var $label = $( '<code>' ).text( externalId );
						if ( data.results.bindings.length ) {
							var url = data.results.bindings[ 0 ].item.value;
							$label = $( '<span>' ).append( $( '<code>' ).text( externalId ) )
								.append( $( '<strong>' ).css( { 'color': 'red' } ).text( ' уже используется в ' ) )
								.append( $( '<a>' ).attr( 'href', url ).attr( 'target', '_blank' ).text( url.replace( /[^Q]*Q/, 'Q' ) ) );
						}
						wdeDialog( $field, propertyId, [ {
							wd: { value: externalId.toString() },
							label: $label
						} ], wdeGetReference( $content ) );
					}
				} );
				return;

			case 'string':
				var text = $content.data( 'wikidata-external-id' );
				if ( !text ) {
					text = $content.text();
				}
				var strings = text.toString().trim().split( /[\n,;]+/ );

				// Commons category
				if ( propertyId === 'P373' ) {
					var $link = $content.find( 'a[class="extiw"]' ).first();
					if ( $link.length ) {
						var url = $link.attr( 'href' );
						var value = url.substr( url.lastIndexOf( '/' ) + 1 )
							.replace( /_/g, ' ' )
							.replace( /^[Cc]ategory:/, '' )
							.replace( /\?.*$/, '' );
						value = decodeURIComponent( value );
						strings = [ value ];
					}
				}

				for ( var i in strings ) {
					var s = strings[ i ].replace( /\n/g, ' ' ).trim();
					if ( s ) {
						values.push( {
							wd: { value: s },
							label: $( '<code>' + s + '</code>' )
						} );
					}
				}
				break;

			case 'monolingualtext':
				var $items = $content.find( '[lang]' );
				$items.each( function () {
					var $item = $( this );
					values.push( {
						wd: {
							value: {
								text: $item.text().trim(),
								language: $item.attr( 'lang' ).trim()
							}
						}
					} );
				} );
				if ( !values.length ) {
					var text = $content.text().trim();
					if ( text ) {
						var $items = mw.util.$content.find( '[lang]' );
						$items.each( function () {
							$item = $( this );
							if ( $item.text().trim().startsWith( text ) ) {
								values.push( {
									wd: {
										value: {
											text: text,
											language: $item.attr( 'lang' ).trim()
										}
									}
								} );
							}
						} );
					}
				}
				for ( var i in values ) {
					values[ i ].label = $( '<span>' )
						.append( $( '<span>' ).css( 'color', '#666' ).text( '(' + values[ i ].wd.value.language + ') ' ) )
						.append( $( '<strong>' ).text( values[ i ].wd.value.text ) );
				}
				break;

			case 'quantity':
				var text = $content.text().replace( /[\u00a0\u25bc\u25b2]/g, ' ' ).replace( /\s\(([^)]*\))/g, '' ).trim();
				var result = { wd: wdeParseQuantity( text, wdeConfig.properties[ propertyId ].constraints.integer ) };
				if ( !result.wd.value ) {
					break;
				}

				if ( wdeConfig.properties[ propertyId ].constraints.qualifier.indexOf( 'P585' ) !== -1 ) {
					var yearMatch = $content.text().match( /\(([^)]*[12]\d\d\d)[,)\s]/ );
					if ( !yearMatch ) {
						yearMatch = $field.closest( 'tr' ).find( 'th' ).first().text().match( /\(([^)]*[12]\d\d\d)[,)\s]/ );
					}
					if ( yearMatch ) {
						if ( extractedDate = wdeCreateTimeSnak( yearMatch[ 1 ] ) ) {
							result.wd.qualifiers = {
								P585: [ {
									snaktype: 'value', property: 'P585',
									datavalue: {
										type: 'time',
										value: extractedDate
									}
								} ]
							};
						}
					}
				}

				var qualMatch = $content.text().match( /\(([^\)]*)/ );
				if ( qualMatch ) {
					qualQuantity = wdeParseQuantity( qualMatch[ 1 ] );
					if ( qualQuantity ) {
						var supportedProperties = [ 'P2076', 'P2077' ];
						for ( var j = 0; j < supportedProperties.length; j++ ) {
							var units = wdeRecognizeUnits( qualMatch[ 1 ], wdeConfig.properties[ supportedProperties[ j ] ].units );
							if ( units.length === 1 ) {
								qualQuantity.value.unit = 'http://www.wikidata.org/entity/' + units[ 0 ];
								if ( !result.wd.qualifiers ) {
									result.wd.qualifiers = {};
								}
								result.wd.qualifiers[ supportedProperties[ j ] ] = [ {
									snaktype: 'value', property: supportedProperties[ j ],
									datavalue: {
										type: 'quantity',
										value: qualQuantity.value
									}
								} ];
							}
						}
					}
				}

				var founded = wdeRecognizeUnits( text, wdeConfig.properties[ propertyId ].units, $field.closest( 'tr' ).find( 'th' ).first().text() );
				for ( var u = 0; u < founded.length; u++ ) {
					result.wd.value.unit = '1';
					if ( founded[ u ] !== '1' ) {
						result.wd.value.unit = 'http://www.wikidata.org/entity/' + founded[ u ];
						var item = wdeConfig.units[ founded[ u ] ];
					}
					result.wd.type = 'quantity';
					result.label = wdeFormatDataValue( result.wd );
					values.push( result );
				}
				break;

			case 'time':
				var value = wdeCreateTimeSnak( $content.text().replace( /\([^\)]*\)/, '' ).toLowerCase().trim().replace( /\s+(года?|г\.?)$/, '' ),
					$content[ 0 ].outerHTML.includes( 'по юлианскому' ) );
				if ( value.toString().match( /^(novalue|somevalue)$/ ) ) {
					values.push( {
						wd: { value: value },
						label: $( '<span>' )
							.append( $( '<span>' ).css( 'color', '#666' ).text( 'Значение ' ) )
							.append( $( '<strong>' ).text( value.toString() === 'novalue' ? 'отсутствует' : 'неизвестно' ) )
					} );
				} else {
					values.push( {
						wd: { value: value },
						label: $( '<span>' )
							.append( $( '<strong>' ).append( wdeFormatDataValue( {
								type: 'time',
								value: value
							} ) ) )
							.append( $( '<span>' ).css( 'color', '#666' ).text( ' (' +
								( value.calendarmodel.includes( '1985727' ) ? 'Григорианский' : 'Юлианский' ) + ') ' ) )
					} );
				}
				break;

			case 'wikibase-item':
				value = wdeParseItems( $content, function ( values ) {
					wdeDialog( $field, propertyId, values, wdeGetReference( $content ) );
				} );
				return;

			case 'url':
				var $links = $content.find( 'a' );
				$links.each( function () {
					var $link = $( this );
					var url = $link.attr( 'href' );
					values.push( {
						wd: { value: url },
						label: $( '<code>' + url + '</code>' )
					} );
				} );
				break;

			default:
				mw.notify( 'Неизвестный тип данных свойства: ' + datatype, {
					type: 'error',
					tag: 'wikidataInfoboxExport-error'
				} );
				console.log( datatype, $content ); // тут надо
		}

		values = $.unique( values );
		wdeDialog( $field, propertyId, values, wdeGetReference( $field ) );
	};

	/**
	 * Событие двойного клика по полю карточки
	 */
	var wdeClickEvent = function ( e ) {
		var $field = $( this );
		var propertyId = $field.attr( 'data-wikidata-property-id' );
		// wdePrepareDialog( $field, propertyId );
	};

	/**
	 * Вывод диалога для подтверждения экспорта
	 */
	var wdeDialog = function ( $field, propertyId, values, refUrl ) {
		var fieldset;

		if ( !values || !values.length ) {
			mw.notify( 'Не удалось определить значение свойства', {
				type: 'error',
				tag: 'wikidataInfoboxExport-error'
			} );
			return;
		}

		// Создание диалога
		function ProcessDialog( config ) {
			ProcessDialog.super.call( this, config );
		}

		OO.inheritClass( ProcessDialog, OO.ui.ProcessDialog );
		ProcessDialog.static.name = 'Экспорт в Викиданные';
		ProcessDialog.static.title = $( '<span>' )
			.attr( 'title', 'Версия ' + wdeConfig.version )
			.text( ProcessDialog.static.name );
		ProcessDialog.static.actions = [
			{ action: 'export', label: 'Экспорт', flags: [ 'primary', 'constructive' ] },
			{ label: 'Отмена', flags: 'safe' }
		];
		ProcessDialog.prototype.initialize = function () {
			ProcessDialog.super.prototype.initialize.apply( this, arguments );
			this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );

			fieldset = new OO.ui.FieldsetLayout();
			var firstSelected = false;
			for ( var i = 0; i < values.length; i++ ) {
				var alreadyInWikidata = ( wdeAlreadyExistingItems[ propertyId ] || [] ).includes( ( ( values[ i ].wd || {} ).value || {} ).id );
				var checkbox = new OO.ui.CheckboxInputWidget( {
					value: JSON.stringify( values[ i ].wd ),
					selected: alreadyInWikidata,
					disabled: alreadyInWikidata
				} );
				if ( !checkbox.isDisabled() ) {
					if ( !firstSelected || !wdeConfig.properties[ propertyId ].constraints.unique ) {
						firstSelected = true;
						checkbox.setSelected( true );
					}

					if ( values[ i ].label[ 0 ].innerText.match( /уже используется/ ) &&
						wdeConfig.properties[ propertyId ].constraints.unique &&
						wdeConfig.properties[ propertyId ].datatype === 'external-id' ) {
						checkbox.setSelected( false );
					}
				}
				if ( refUrl ) {
					values[ i ].label.append( wdeFormatDomains( refUrl ) );
				}
				fieldset.addItems( [
					new OO.ui.FieldLayout( checkbox, {
						label: values[ i ].label,
						align: 'inline'
					} )
				] );
			}

			this.content.$element
				.append( $( '<p>' ).append( $( '<strong>' )
					.append( $( '<a>' ).attr( 'href', 'https://wikidata.org/wiki/Property:' + propertyId ).attr( 'target', '_blank' ).text( wdeConfig.properties[ propertyId ].label ) )
					.append( $( '<span>' ).text( ':' ) )
				) )
				.append( fieldset.$element )
				.append( $( '<hr>' ).css( 'margin-top', '1.5em' ) )
				.append( $( '<p>' ).text( 'Экспортировать значение свойства из карточки в Викиданные?' ) )
				.append( $( '<p>' ).css( 'font-size', 'smaller' ).html( 'Нажав на кнопку «Экспорт», вы соглашаетесь с <a href="https://foundation.wikimedia.org/wiki/Terms_of_Use" class="extiw" title="wikimedia:Terms of Use">условиями использования</a> и безотзывно соглашаетесь публиковать свой вклад на условиях <a rel="nofollow" class="external text" href="https://creativecommons.org/publicdomain/zero/1.0/">Creative Commons CC0 License</a>.' ) );

			this.$body.append( this.content.$element );
		};
		ProcessDialog.prototype.getActionProcess = function ( action ) {
			var dialog = this;
			if ( action === 'export' ) {
				return new OO.ui.Process( function () {
					var values = [];
					var fields = fieldset.getItems();
					for ( var i in fields ) {
						var checkbox = fields[ i ].getField();
						if ( checkbox.isSelected() && !checkbox.isDisabled() ) {
							values.push( checkbox.getValue() );
						}
					}

					wdeCreateClaims( propertyId, values, refUrl );
					dialog.close( { action: action } );
				}, this );
			}
			return ProcessDialog.super.prototype.getActionProcess.call( this, action );
		};
		var windowManager = new OO.ui.WindowManager();
		$( 'body' ).append( windowManager.$element );
		var processDialog = new ProcessDialog();
		windowManager.addWindows( [ processDialog ] );
		windowManager.openWindow( processDialog );
	};

	var wdeInitDragging = function ( $field ) {
		// Panel
		if ( !$( '#wd-drop-panel' ).length ) {
			$( '<div>' )
				.attr( 'id', 'wd-drop-panel' )
				.css( {
					position: 'fixed',
					width: '150px',
					height: '150px',
					margin: '-75px 0 0 -75px',
					border: '5px solid #59a',
					borderRadius: '50%',
					background: 'rgba(136, 204, 238, 0.5) 50% 50% no-repeat',
					display: 'none'
				} )
				.appendTo( 'body' )
				.droppable( {
					tolerance: 'pointer',
					classes: {
						'ui-droppable-hover': 'wd-drop-panel-hover',
					},
					hoverClass: 'wd-drop-panel-hover',
					drop: function( event, ui ) {
						$( this ).css( {
							backgroundImage: 'url(' + wdeBgImages.loader + ')',
							backgroundPositionY: '50%',
							backgroundSize: '100%'
						} );
						var $field = $( ui.draggable );
						var propertyId = $field.attr( 'data-wikidata-property-id' );
						wdePrepareDialog( $field, propertyId );
					}
				} );
				mw.util.addCSS( '\
					.wd-drop-panel-hover {\
						opacity: .85 !important;\
						border-color: #5ab;\
					}\
					' );
		}
	
		// Global CSS
		mw.util.addCSS( '\
			.no-wikidata {\
				background: #8ce !important;\
				border: 1px solid #59a;\
				border-left-width: 0;\
				border-right-width: 0;\
				padding: 3px;\
				margin: -4px -3px;\
			}\
			.infobox .no-wikidata {\
				display: block !important;\
				padding: 5px !important;\
				margin: 0;\
				border-width: 1px;\
				cursor: default !important;\
			}\
			.no-wikidata:hover {\
				background: #8df !important;\
				border-color: #5ab;\
			}\
			.no-wikidata:active {\
				background: #aff !important;\
				border-color: #5bc;\
				cursor: move !important;\
			}\
			.infobox .no-wikidata .no-wikidata {\
				margin: -6px;\
			}\
			' );

		wdeInitFields();
	};

	var wdeInitFields = function ( $field ) {
		var $selector = $field || $( '.no-wikidata' );
		$selector
			.off( 'dblclick' )
			.draggable( {
				revert: 'invalid',
				helper: 'clone',
				cursor: 'move',
				cursorAt: {
					left: 12,
					top: 12
				},
				stack: '#wd-drop-panel',
				start: function( event, ui ) {
					$( '#wd-drop-panel' )
						.css( {
							backgroundImage: 'url(' + wdeBgImages.default + ')',
							backgroundPositionY: '50%',
							backgroundSize: '80%',
							left: event.clientX - 150,
							top: event.clientY - 25,
						} )
						.show( 'scale' );
				},
				stop: function() {
					$( '#wd-drop-panel' ).hide();
				}
			} );

		if ( $field ) {
			$field.addClass( 'no-wikidata' );
		}
	};

	/**
	 * Инициализация гаджета
	 */
	var wdeInit = function () {
		if ( mw.config.get( 'wgWikibaseItemId' ) === null ||
			mw.config.get( 'wgAction' ) !== 'view' ||
			( window.ve && window.ve.init ) ||
			mw.config.get( 'wgNamespaceNumber' )
		) {
			return;
		}

		// Загрузка конфига из localStorage
		var storedConfig;
		try {
			storedConfig = JSON.parse( localStorage.getItem( 'wdeConfig2' ) );
		} catch ( e ) {}
		if ( storedConfig && storedConfig.version == wdeConfig.version ) {
			wdeConfig = storedConfig;
		}
		if ( wdeConfig.properties === undefined ) {
			wdeConfig.properties = {};
		}

		// Установка сайта и языка пользователя
		wdeConfig.project = mw.config.get( 'wgDBname' );
		wdeConfig.language = mw.user.options.get( 'language' ) || mw.config.get( 'wgContentLanguage' );
		wdeConfig.languages.unshift( wdeConfig.language );
		wdeConfig.languages = $.unique( wdeConfig.languages );
		localStorage.setItem( 'wdeConfig2', JSON.stringify( wdeConfig ) );

		// Инициализация API
		api = new mw.Api();
		wdApi = new mw.ForeignApi( '//www.wikidata.org/w/api.php' );

		// Инициализация диалогов
		wdeWindowManager = new OO.ui.WindowManager();
		$( 'body' ).append( wdeWindowManager.$element );

		// Запрос данных элемента
		wdApi.get( {
			action: 'wbgetentities',
			props: [ 'info', 'claims' ],
			ids: mw.config.get( 'wgWikibaseItemId' )
		} ).done( function ( data ) {
			if ( data.success ) {
				var claims;
				for ( var i in data.entities ) {
					if ( i == -1 ) {
						return;
					}

					claims = data.entities[ i ].claims;
					wdeBaseRevId = data.entities[ i ].lastrevid;
					break;
				}
				if ( !claims ) {
					return;
				}

				var $fields = $( '.no-wikidata' );
				$fields.each( function () {
					var $field = $( this );
					var propertyId = $field.attr( 'data-wikidata-property-id' );
					
					var $qualifiers = $field.find( '[data-wikidata-qualifier-id]' );
					$qualifiers.each( function () {
						wdePropertyIds.push( $( this ).data( 'wikidata-qualifier-id' ) );
					} );

					$field.removeClass( 'no-wikidata' );
					wdePropertyIds.push( propertyId );
					wdeCanExportValue( $field, claims[ propertyId ], function () {
						wdeInitFields( $field );
					} );
				} );

				// Drag & drop
				wdeInitDragging();

				// TODO: Загружать при первом открытии окна
				wdeLoadProperties( wdePropertyIds );
			}
		} );
	};

	$.when(
		$.ready,
		mw.loader.using( [
			'jquery.ui',
			'jquery.ui',
			'mediawiki.api',
			'mediawiki.ForeignApi',
			'mediawiki.util',
			'oojs-ui-core',
			'oojs-ui-widgets',
			'oojs-ui-windows'
		] )
	).done( wdeInit );
}( mediaWiki, jQuery ) );