مدیاویکی:Gadget-asbox.js

از ویکی حقوق
پرش به ناوبری پرش به جستجو

نکته: پس از انتشار ممکن است برای دیدن تغییرات نیاز باشد که حافظهٔ نهانی مرورگر خود را پاک کنید.

  • فایرفاکس / سافاری: کلید Shift را نگه دارید و روی دکمهٔ Reload کلیک کنید، یا کلید‌های Ctrl-F5 یا Ctrl-R را با هم فشار دهید (در رایانه‌های اپل مکینتاش کلید‌های ⌘-R)
  • گوگل کروم: کلیدهای Ctrl+Shift+R را با هم فشار دهید (در رایانه‌های اپل مکینتاش کلید‌های ⌘-Shift-R)
  • اینترنت اکسپلورر/ Edge: کلید Ctrl را نگه‌دارید و روی دکمهٔ Refresh کلیک کنید، یا کلید‌های Ctrl-F5 را با هم فشار دهید
  • اپرا: Ctrl-F5 را بفشارید.
/**
 * 
 * Originally written by [[:en:User:SD0001]] at [[:en:User:SD0001/StubSorter.js]]
 * این نسخهٔ فارسی تنها برای سازگاری با ویکی‌پدیای فارسی بومی‌سازی شده و کدهای آن منتسب به نویسندهٔ ابزار در ویکی‌پدیای انگلیسی است
 * Ajax-based stub tag manager
 * 
 */

// <nowiki>
// jshint maxerr: 999

$.when(
	$.ready,
	mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.Title', 'jquery.chosen'])
).then(function() {

var API = new mw.Api({
	ajax: { headers: { 'Api-User-Agent': '[[w:en:User:SD0001/StubSorter.js]]' } }
});

var callback = function(e) {
	e.preventDefault();

	// if already present, don't duplicate
	if ($('#stub-sorter-wrapper').length !== 0) {
		return;
	}

	$('#mw-content-text').before(
		$('<div>').attr('id', 'stub-sorter-wrapper').css({
			'max-height': 'max-content',
			'background-color': '#c0ffec',
			'margin-bottom': '10px'
		}).append(
			$('<select>')
				.attr('id', 'stub-sorter-select')
				.attr('multiple', 'true')
				.change(handlePreview),

			$('<div>').attr('id', 'stub-sorter-previewbox').css({
				'background-color': '#cfd8eb' // '#98b685'
				// 'border-bottom': 'solid 0.5px #aaaaaa'
			})
		)
	);

	var $select = $('#stub-sorter-select');

	var selectExistingStubTags = function($html) {
		$html.find('.stub .hlist .nv-view a').each(function(_, e) {
			var template = e.title.slice('الگو:'.length);
			$select.append(
				$('<option>').text(template).val(template).attr('selected', 'true')
			);
		});
	};

	if (mw.config.get('wgCurRevisionId') === mw.config.get('wgRevisionId')) {
		// Viewing the current version of the page, no need for api call to get the page html
		selectExistingStubTags($('.mw-parser-output'));
	} else {
		// In edit/history/diff/oldrevision mode, get the page html by api call
		var pageNameToEncode = mw.config.get('wgPageName');
		API.parse(new mw.Title(encodeURIComponent(pageNameToEncode))).then(function(html) {
			selectExistingStubTags($(html));
			$select.trigger('chosen:updated');
			$select.trigger('click');
			$input.focus();
		});
	}

	$select.chosen({
		search_contains: true,
		placeholder_text_multiple: 'برای افزودن یک برچسب خرد، شروع به نوشتن کنید...',
		width: '100%',

		// somehow beacuse of the hacks below, the no_results_text shows up
		// when the search results are loading, and not when there are no results
		no_results_text: 'بارگذاری نتایج برای'
	});

	var $input = $('#stub_sorter_select_chosen input');

	var menuFrozen = false;
	var searchBy = getPref('searchBy', 'prefix');

	$('#stub_sorter_select_chosen .chosen-choices').after(

		$('<div>').append(

			// Freeze button
			$('<span>').append(
				$('<a>').text('منو باز بماند ').click(function() {
					menuFrozen = !menuFrozen;
					if (menuFrozen) {
						$(this).text('منو بسته شود ');
						$(this).parent().css('font-weight', 'bold');
					} else {
						$(this).text('منو باز بماند ');
						$(this).parent().css('font-weight', 'normal');
					}
					$input[0].focus();
					$input.trigger('keyup');
				}).css({
					'padding-right': '5px',
					'padding-left': '100px'
				})
			),

			// Search mode select
			$('<select>').append(
				$('<option>').text('اول پیشوندهای منطبق را فهرست کن').val('prefix'),
				$('<option>').text('اول موارد منطبق در عنوان‌ها را فهرست کن').val('intitle'),
				$('<option>').text('استفاده از جستجوی دقیق انطباق نویسه‌ها').val('regex')
			).change(function(e) {
				searchBy = e.target.value;
				$input.trigger('keyup');
			}),

			// help button after the search mode select
			$('<small>').append(
				' (',
				$('<a>', {
					text: 'راهنما',
					href: mw.util.getUrl('راهنما:ابزار/خردیاب'),
					target: '_blank'
				}),
				')'
			)
		).css({
			'border-bottom': 'solid 0.5px #aaaaaa',
			'border-left': 'solid 0.5px #aaaaaa',
			'border-right': 'solid 0.5px #aaaaaa'
		})

	);

	// Save button
	$('<button>')
		.text('ذخیره').css({
			'float': 'left'
		})
		.attr('id', 'stub-sorter-save')
		.attr('accesskey', 's')
		.click(handleSave)
		.insertAfter($('#stub_sorter_select_chosen .chosen-choices'));

	// hide selected items in dropdown
	mw.util.addCSS(
		'#stub_sorter_select_chosen .chosen-results .result-selected { display: none; }'
	);

	// Focus on the search box as soon as the the sorter menu loads
	// Add placeholder, because chosen's native placeholder doesn't work with a changing menu.
	// Reset the search box width to accomodate the placeholder text
	// Keep resetting whenever the input goes out of focus
	$input
		.focus()
		.attr('placeholder', 'برای افزودن یک برچسب خرد، شروع به نوشتن کنید...')
		.css('width', '200px')
		.blur(function() {
			$(this).css('width', '100%');
		});

	// also reset it when an option is selected by clicking on it
	// or when clicking on the search box after the $input has become narrow (despite our best efforts...)
	$('.chosen-container').click(function() {
		$input.css('width', '100%');
	});

	// Adapted from [[User:Enterprisey/afch-master.js/submissions.js]]'s category selection menu:
	// Offer dynamic suggestions!
	// Since jquery.chosen doesn't natively support dynamic results,
	// we sneakily inject some dynamic suggestions instead.
	// Consider upgrading to select2 or OOUI to avoid these hacks
	$input.keyup(function(e) {
		var searchStr = $input.val();

		// The worst hack. Because Chosen keeps messing with the
		// width of the text box, keep on resetting it to 100%
		$input.css('width', '100%');
		$input.parent().css('width', '100%');

		// Ignore arrow keys and home/end keys to allow users to navigate through the suggestions or through the search query
		// and don't show results when an empty string is provided
		if ((e.which >= 35 && e.which <= 40) ||
			(menuFrozen && e.which !== undefined) ||
			!searchStr) {
			return;
		}

		// true when fake keyup is produced by the Freeze button
		// in this case, api limit has to be raised to 500
		var extended = e.which === undefined;

		$.when(
			searchBy !== 'regex' ? getStubSearchResults('prefix', searchStr, extended) : undefined,
			searchBy !== 'regex' ? getStubSearchResults('intitle', searchStr, extended) : undefined,
			searchBy === 'regex' ? getStubSearchResults('regex', searchStr, extended) : undefined
		).then(function(stubsPrefix, stubsIntitle, stubsRegex) {

			var stubs;
			switch (searchBy) {
				case 'prefix': stubs = uniqElements(stubsPrefix, stubsIntitle); break;
				case 'intitle': stubs = uniqElements(stubsIntitle, stubsPrefix); break;
				case 'regex': stubs = stubsRegex; break;
			}

			// Reset the text box width again
			$input.css('width', '100%');
			$input.parent().css('width', '100%');

			// If the input has changed since we started searching,
			// don't show outdated results
			if ($input.val() !== searchStr) {
				return;
			}

			// Clear existing suggestions
			$select.children().not(':selected').remove();

			// Now, add the new suggestions
			stubs.forEach(function (stub) {

				// do not add if already selected
				if ($select.val().indexOf(stub) !== -1) {
					return;
				}
				$select.append(
					$('<option>').text(stub).val(stub)
				);
			});

			// We've changed the <select>, now tell Chosen to
			// rebuild the visible list
			$select.trigger('liszt:updated');
			$select.trigger('chosen:updated');
			$input.val(searchStr);
			$input.css('width', '100%');
			$input.parent().css('width', '100%');

		}).catch(function(e) {
			if ($input.val() !== searchStr) {
				return;
			}
			$select.children().not(':selected').remove();
			$select.append(
				$('<option>')
					.text('خطا در دریافت نتایج: ' + e)
					.attr('disabled', 'true')
			);
			$select.trigger('liszt:updated');
			$select.trigger('chosen:updated');
			$input.val(searchStr);
			$input.css('width', '100%');
			$input.parent().css('width', '100%');
		});

	});

};

var getStubSearchResults = function(searchType, searchStr, extended) {
	var query = {
		'action': 'query',
		'list': 'search',
		'srsearch': 'incategory:"الگو:مقاله‌های_خرد" ',
		'srnamespace': '10',
		'srlimit': extended ? '500' : '100',
		'srqiprofile': 'classic',
		'srprop': '',
		'srsort': 'relevance'
	};
	switch (searchType) {
		case 'prefix':
			query.srsearch += 'prefix:"الگو:' + searchStr + '"';
			break;
		case 'intitle':
			var searchStrWords = searchStr.split(' ').filter(function(e) {
				return !/^\s*$/.test(e);
			});
			query.srsearch += 'intitle:"' + searchStrWords.join('" intitle:"') + '"';
			break;
		case 'regex':
			query.srsearch += 'intitle:/' + mw.util.escapeRegExp(searchStr) + '/i';
			break;
	}

	return API.get(query).then(function(response) {
		if (response && response.query && response.query.search) {
			return response.query.search.map(function(e) {
				return e.title.slice(5);
			});
		} else {
			return $.Deferred().reject(JSON.stringify(response));
		}
	}, function(e) {
		return $.Deferred().reject(JSON.stringify(e));
	});
};

var handlePreview = function() {

	// Show preview
	var $this = $(this);
	var selectedTags = $this.val();
	if (selectedTags.length) {
		var tagsWikitext = '{{' + selectedTags.join('}}\n{{') + '}}';

		API.parse(tagsWikitext).then(function(parsedhtmldiv) {

			// Do nothing if tag selection has changed since we
			// sent the parse API call, comparing lengths is enough
			if (selectedTags.length !== $this.val().length) {
				return;
			}
			$('#stub-sorter-previewbox').html(parsedhtmldiv);
		});
	} else {
		$('#stub-sorter-previewbox').empty();
	}
	// $input.css('width', '100%');  // doesn't work
};


var handleSave = function submit() {
	$('#stub-sorter-error').remove();
	var $status = $('<div>').text('واکشی صفحه...')
		.attr('id', 'stub-sorter-status')
		.css({
			'float': 'left'
		});
	$(this).replaceWith($status);
	API.edit(mw.config.get('wgPageName'), function(revision) {
		$status.text('ذخیرهٔ صفحه...');
		var pageText = revision.content;

		var tagsBefore = (pageText.match(/\{\{[^{]*(?:خرد|ناقص)(?:\|.*?)?\}\}/g) || []).map(function(e) {
			// capitalise first char after {{
			return e[0] + e[1] + e[2].toUpperCase() + e.slice(3);
		});
		var tagsAfter = $('#stub-sorter-select').val().map(function(e) {
			return '{{' + e + '}}';
		});
		
		// Automatically remove {{Stub}} if accidentally left behind
		if (tagsAfter.length > 1) {
			var idx = tagsAfter.indexOf('{{خرد}}');
			if (idx !== -1) {
				tagsAfter.splice(idx, 1);	
			}
		}

		// remove all stub tags
		pageText = pageText.replace(/\{\{[^{]*(?:خرد|ناقص)(?:\|.*?)?\}\}\s*/g, '').trim();

		// add selected stub tags
		pageText += '\n\n' + tagsAfter.join('\n'); 	// per [[MOS:LAYOUT]]

		// For producing edit summary
		var summary = '';

		var tagsAdded = tagsAfter.filter(function(e) {
			return tagsBefore.indexOf(e) === -1;
		});
		var tagsRemoved = tagsBefore.filter(function(e) {
			return tagsAfter.indexOf(e) === -1;
		});

		tagsRemoved.forEach(function(e) {
			summary += '–' + e + '، ';
		});
		tagsAdded.forEach(function(e) {
			summary += '+' + e + '، ';
		});
		summary = summary.slice(0, -2); // remove the final ', '

		return {
			text: pageText,
			summary: summary + ' با کمک [[راهنما:ابزار/خردیاب|خردیاب]]',
			nocreate: 1,
			minor: getPref('minor', true),
			watchlist: getPref('watchlist', 'nochange')
		};
	}).then(function() {
		$status.text('انجام شد. بارگذاری مجدد صفحه...');
		setTimeout(function() {
			window.location.href = mw.util.getUrl(mw.config.get('wgPageName'));
		}, 500);
	}).fail(function(e) {
		$status.text('ذخیرهٔ ناموفق. لطفاً دوباره تلاش کنید.')
			.attr('id', 'stub-sorter-error')
			.css({
				'color': 'red',
				'font-weight': 'bold',
				'padding-left': '5px'
			});
		console.error(e); // eslint-disable-line no-console
		setTimeout(function() {
			$status.before($('#stub-sorter-save'));
			$('#stub-sorter-save').click(handleSave);
		}, 500);
	});
};

// utility function to get unique elements from 2 arrays
var uniqElements = function(arr1, arr2) {
	var obj = {}; var i;
	for (i = 0; i < arr1.length; i++) {
		obj[arr1[i]] = 0;
	}
	for (i = 0; i < arr2.length; i++) {
		obj[arr2[i]] = 0;
	}
	return Object.keys(obj);
};

// function to obtain a preference option from common.js
var getPref = function(name, defaultVal) {
	if (window['stubSorter_' + name] === undefined) {
		return defaultVal;
	} else {
		return window['stubSorter_' + name];
	}
};

/**
 ********************* SET UP *********************
 */

// auto start the script when navigating to an article from CAT:STUBS
if (mw.config.get('wgPageName') === 'رده:مقاله‌های خرد') {
	$('#mw-pages li a').each(function(_, e) {
		e.href += '?startstubsorter=y';
	});
}

// show only on existing articles, and my sandbox (for testing)
if (mw.config.get('wgNamespaceNumber') === 0 &&
	mw.config.get('wgCurRevisionId') !== 0
) {
	mw.util.addPortletLink(getPref('portlet', 'p-cactions'), '#', 'خرد',
	'ca-stub', 'افزودن یا حذف الگوهای خرد').addEventListener('click', callback);
}


if (mw.util.getParamValue('startstubsorter')) {
	setTimeout(function() {
		$('#ca-stub').click();
	}, 1000);
}

});

// </nowiki>