# MediaWiki:Gadget-asbox.js

From faraghaib
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)
- Edge: Hold Ctrl while clicking Refresh, or press 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>

Retrieved from "https://en.faraghaib.ir/w/index.php?title=MediaWiki:Gadget-asbox.js&oldid=247"