MediaWiki:Gadget-wstaw-link-interwiki.js
Увага: Після публікування слід очистити кеш браузера, щоб побачити зміни.
- Firefox / Safari: тримайте Shift, коли натискаєте Оновити, або натисніть Ctrl-F5 чи Ctrl-Shift-R (⌘-R на Apple Mac)
- Google Chrome: натисніть Ctrl-Shift-R (⌘-Shift-R на Apple Mac)
- Internet Explorer / Edge: тримайте Ctrl, коли натискаєте Оновити, або натисніть Ctrl-F5
- Opera: натисніть Ctrl-F5
//@ts-check
/**
* @author [[w:pl:User:Msz2001]]
*
* Скрипт, що покращує заміну червоних посилань на шаблон {{link-interwiki}}.
*
* <nowiki>
*/
$(function(){
var LINK_TARGET_INDEX = 'data-target-index';
var LINK_FROM_TEMPLATE = 'data-from-template';
var TRIGGER_TEXT = '{{link-interwiki}}';
var TRIGGER_TOOLTIP = 'Заміни червоні посилання на шаблон {{link-interwiki}}';
var SAVE_BUTTON = 'Зберегти';
var DISCARD_BUTTON = 'Відхилити';
var SAVING_TEXT = 'Збереження...';
var COUNTER_TEXT = 'Замінено посилань: $1 / $2';
var LINK_TITLE = 'Заміни на {{link-interwiki}}, пов’язаний з Вікіданими';
var QID_POPUP_TITLE = 'Прив’язка елемента Вікіданих';
var PROMPT_TEXT = 'Надай ідентифікатор елементу Вікіданих:';
var PROMPT_PLACEHOLDER = 'напр. Q123456';
var INVALID_QID = 'Це не дійсний ідентифікатор. Коли ви натиснете "Вставити", буде вставлено просте посилання на Вікіпедію';
var ACCEPT_BTN = 'Підтвердити';
var NEXT_BTN = 'Далі';
var BACK_BTN = 'Назад';
var CANCEL_BTN = 'Скасувати';
var SUGGESTION_HEADER = 'Пропозиції';
var SUGGESTION_EXT_TITLE = 'Див. цей елемент у Вікіданих';
var SUGGESTION_LOADING = 'Завантаження...';
var SUGGESTION_NO_RESULTS = 'Пропозиції відсутні';
var SUGGESTION_NODESC = 'немає опису';
var SUGGESTION_ERROR = 'Виникла помилка: $1';
var CONFIRM_WIKITEXT_TEXT = '<div>Чи правильно вставлено шаблон? У рідкісних випадках скрипт може неправильно знайти посилання для заміни.<pre>$1</pre></div>';
var DISCARD_CONFIRM_TEXT = 'Ви впевнені, що хочете відхилити всі зміни?';
var ERROR_TEXT = 'Не вдалося знайти посилання у вікікоді. Внесіть зміни вручну.';
var EDIT_SUMMARY = 'Заміна $1 червоних посилань на шаблон {{[[Шаблон:Link-interwiki|Link-interwiki]]}}';
var ERROR_TITLE = 'Виникла помилка';
var API_CONFIG = {
parameters: {
format: 'json',
formatversion: 2,
errorformat: 'html',
errorlang: mw.config.get('wgUserLanguage'),
errorsuselocal: true
}
};
// These store the wikitext representing the page after any modifications
// The `actualWikitext` will actually be saved to the page
// The `sanitizedWikitext` is the original wikitext with some strings escaped
// Both variables should always have the same length so that index-based searches work
var actualWikitext;
var sanitizedWikitext;
/** The revision id of the wikitext being edited (used for edit conflicts discovery) */
var wikitextRevision;
/** A promise used for waiting for fetching the wikitext */
var wikitextPromise;
/** Number of changes already made */
var numberOfChanges = 0;
/** Total number of red links present on the page */
var totalRedLinks = 0;
/** The element with save and discard buttons */
var saveBox;
var counterBox;
/** Links that have been used but may need to be restored upon discard */
var hiddenLinks = [];
/** Prevents from closing the page if there are unsaved changes */
var windowCloseConfirm;
/**
* Traverse the DOM tree and find all redlinks.
* @returns {HTMLAnchorElement[]} An array of redlinks
*/
function getRedlinks() {
var navboxLinks = document.querySelectorAll('.mw-parser-output .NavFrame a.new, .mw-parser-output .navbox a.new');
navboxLinks.forEach(function(link) {
link.setAttribute(LINK_FROM_TEMPLATE, 'true');
});
/** @type {NodeListOf<HTMLAnchorElement>} */
var domLinks = document.querySelectorAll('.mw-parser-output a.new:not([' + LINK_FROM_TEMPLATE + '])');
var redlinks = [];
var targets = Object.create(null);
for (var i = 0; i < domLinks.length; i++) {
var link = domLinks[i];
var nextSibling = link.nextElementSibling;
if(nextSibling){
// If the next sibling has one of those classes, it's generated by {{link-interwiki}}
// Therefore skip it
if(nextSibling.classList.contains('link-interwiki') || nextSibling.classList.contains('extiw')){
continue;
}
}
redlinks.push(link);
// Set the normalized target page and link index as data attributes
var targetPage = extractPageTitle(link.href);
targetPage = normalizePageName(targetPage);
var targetIndex = targets[targetPage] || 0;
targets[targetPage] = targetIndex + 1;
link.setAttribute(LINK_TARGET_INDEX, targetIndex);
}
return redlinks;
}
/**
* Displays a link that invokes the script on a given link.
* @param {HTMLAnchorElement[]} redlinks An array of redlinks
* @returns {void}
*/
function displayLinks(redlinks) {
redlinks.forEach(function(redlink) {
var link = document.createElement('a');
link.href = 'javascript:void(0)';
link.innerHTML = '<sub class="insert-interwiki">Q</sub>';
link.title = LINK_TITLE;
link.addEventListener('click', function(e) {
invoke(redlink).then(function(){
numberOfChanges++;
updateSaveBox();
link.style.display = 'none';
hiddenLinks.push(link);
}).fail(function(error){
if(!error) return;
mw.notify(error, {autoHideSeconds: 'long', title: ERROR_TITLE, type: 'error'});
});
e.preventDefault();
});
if(redlink.parentNode){
redlink.parentNode.insertBefore(link, redlink.nextSibling);
}
});
}
/**
* Invokes the script and asks user for input.
* @param {HTMLAnchorElement} link The link to invoke the script on
* @returns {JQuery.Promise<void, JQuery | string | null>} A promise that resolves when the link has been added or failed
*/
function invoke(link){
var deferred = $.Deferred();
var page = mw.config.get('wgPageName');
// Download the wikitext if not already done
if(!wikitextPromise){
wikitextPromise = getPageWikitext(page).then(function(revision){
wikitextRevision = revision.revisionId;
actualWikitext = revision.wikitext;
sanitizedWikitext = sanitizeWikitext(actualWikitext);
});
wikitextPromise.fail(function (error){
deferred.reject(error);
});
}
var localTitle = extractPageTitle(link.href);
var targetIndex = parseInt(link.getAttribute(LINK_TARGET_INDEX) || '0');
var linkText = link.innerText;
mw.loader.using(['oojs-ui-core', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.api', 'mediawiki.confirmCloseWindow']).then(function(){
var promptPromise = displayQidPopup(link, function(qid){
var isWikidata = /^Q?\d+$/i.test(qid);
return createNewWikitext(localTitle, linkText, qid, isWikidata, targetIndex);
}, getItemSuggestions(localTitle));
promptPromise.done(function(data){
actualWikitext = data.actualWikitext;
sanitizedWikitext = data.sanitizedWikitext;
deferred.resolve();
}).fail(function(error){
deferred.reject(error);
});
});
return deferred.promise();
}
/**
* Creates a new wikitext and asks the user if it's correct.
* @param {string} localTitle The human-friendly local page name
* @param {string} linkText The visible link text
* @param {string} linkTarget The target page name
* @param {boolean} isWikidata Whether the link is to Wikidata (`false` creates ordinary local link)
* @param {number} targetIndex The index of the link to the target page
* @returns {{actualWikitext: string, sanitizedWikitext: string, excerpt: string} | null} Data about the new wikitext or null if the link could not be found
*/
function createNewWikitext(localTitle, linkText, linkTarget, isWikidata, targetIndex){
var template = prepareTemplate(localTitle, linkText, linkTarget, isWikidata);
var replaced = replaceLinkWithText(actualWikitext, sanitizedWikitext, localTitle, targetIndex, template);
if(!replaced) return null;
var excerptForUser = makeExcerpt(
replaced.newWikitext, replaced.indexFrom, replaced.indexTo, 30);
excerptForUser = excerptForUser.replace(/</g, '<');
return {
actualWikitext: replaced.newWikitext,
sanitizedWikitext: replaced.newSanitizedWikitext,
excerpt: excerptForUser
}
}
/**
* Returns a promise that resolves to the wikitext of the page.
* @param {string} page The page name
* @returns {JQuery.Promise<{wikitext: string, revisionId: number}, JQuery>} The page's wikitext
*/
function getPageWikitext(page){
var deferred = $.Deferred();
var api = new mw.Api(API_CONFIG);
api.get({
action: 'query',
prop: 'revisions',
titles: page,
rvprop: ['content', 'ids'],
rvslots: 'main'
}).then(function(data){
var page = data.query.pages[0];
var revision = page.revisions[0];
var slot = revision.slots.main;
var wikitext = slot.content;
var revid = revision.revid;
deferred.resolve({
wikitext: wikitext,
revisionId: revid
});
}).fail(function(code, result){
deferred.reject(api.getErrorMessage(result));
});
return deferred.promise();
}
/**
* Extracts the page title from link href.
* @param {string} href The link href
* @returns {string} The page title
*/
function extractPageTitle(href){
var match;
if(href.indexOf('/wiki/') !== -1){
match = href.match(/\/wiki\/([^?]+)/);
}else{
match = href.match(/[?&]title=([^&]+)(&|$)/);
}
if(match === null) return '';
var title = match[1]
title = decodeURIComponent(title);
title = title.replace(/_/g, ' ');
return title;
}
/**
* Replaces a link with the specified text.
* @param {string} actualWikitext The actual wikitext to replace the link in
* @param {string} sanitizedWikitext The sanitized wikitext to replace the link in
* @param {string} targetPage The link target page
* @param {number} linkIndex The ordinal number of the link among others with the same target
* @param {string} textToReplace A text to be inserted in place of the link
* @returns {LinkReplaceState | null} The modified wikitext or null if the replace failed
*/
function replaceLinkWithText(actualWikitext, sanitizedWikitext, targetPage, linkIndex, textToReplace){
// Link is a text in the form of [[targetPage|linkText]]trail
// where linkText and trail are optional,
// targetPage cannot contain a character from set: []{}<>|
// linkText cannot contain a ] character
// trail can only be letters
var matches = Array.from(
sanitizedWikitext.matchAll(/\[\[([^\[\]{}<>|]+)(\|[^\]]+)?\]\]([a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ]*)/g));
targetPage = normalizePageName(targetPage);
for(var i = 0; i < matches.length; i++){
var match = matches[i];
var matchIndex = match.index;
if(matchIndex === undefined) continue;
if(normalizePageName(match[1]) === targetPage){
if(linkIndex > 0){
linkIndex--;
continue;
}
if(textToReplace.length < match[0].length + 3) {
// 0x18 (CANCEL in ASCII) will be then stripped out
textToReplace = textToReplace.padEnd(match[0].length + 3, '\x18');
}
// Replace the link with the text
actualWikitext = actualWikitext.substring(0, matchIndex) +
textToReplace + actualWikitext.substring(matchIndex + match[0].length);
// Prepare a placeholder for sanitized wikitext: [[targetPage|linkText|<...>]]trail
var synchronizedPlaceholder = match[0].substring(0, match[0].length - match[3].length - 2);
synchronizedPlaceholder += '|<';
synchronizedPlaceholder += '.'.repeat(textToReplace.length - match[0].length - 3);
synchronizedPlaceholder += '>]]' + match[3];
// Synchronize the sanitized wikitext
sanitizedWikitext = sanitizedWikitext.substring(0, matchIndex) +
synchronizedPlaceholder + sanitizedWikitext.substring(matchIndex + match[0].length);
return {
newWikitext: actualWikitext,
newSanitizedWikitext: sanitizedWikitext,
indexFrom: matchIndex,
indexTo: matchIndex + textToReplace.length
};
}
}
return null;
}
/**
* Removes references, comments and nowiki tags from the wikitext and replaces them
* with placeholders of the same length.
* @param {string} wikitext The wikitext to sanitize
*/
function sanitizeWikitext(wikitext){
wikitext = wikitext.replace(/<nowiki *>.*?<\/nowiki *>/gi, function(match){
return '<' + 'X'.repeat(match.length - 2) + '>';
});
wikitext = wikitext.replace(/<!--.*?-->/gi, function(match){
return '<' + 'X'.repeat(match.length - 2) + '>';
});
// References may contain links that are displayed out-of-order - at the end of article
// That's why we need to ignore them as well
wikitext = wikitext.replace(/<ref[^\/>]*>.*?<\/ref *>/gi, function(match){
return '<' + 'X'.repeat(match.length - 2) + '>';
});
return wikitext;
}
/**
* Transforms the page name to uppercase with spaces replaced with underscores.
* @param {string} page The page name
*/
function normalizePageName(page){
return page.toUpperCase().replace(/ /g, '_');
}
/**
* Prepares the template for an interlanguage link.
* @param {string} localArticle The local article name
* @param {string} displayedText The text displayed in the link
* @param {string} linkTarget The linked article name
* @param {boolean} isWikidata Whether the link is to Wikidata (`false` creates ordinary local link)
* @returns {string}
*/
function prepareTemplate(localArticle, displayedText, linkTarget, isWikidata){
// If local article title and displayed text differ only in first letter case,
// use the display name as the local article title
if(localArticle.substring(1) === displayedText.substring(1)
&& localArticle[0].toUpperCase() === displayedText[0].toUpperCase()){
localArticle = displayedText;
}
if(!isWikidata){
if(linkTarget == displayedText) return '[[' + linkTarget + ']]';
return '[[' + linkTarget + '|' + displayedText + ']]';
}
// Wikidata link
// Capitalize the Q
linkTarget = linkTarget.toUpperCase();
if(linkTarget[0] !== 'Q'){
linkTarget = 'Q' + linkTarget;
}
var templateText = '{{li|';
// Just in case
if(localArticle.indexOf('=') !== -1) templateText += '1=';
templateText += localArticle;
if(displayedText !== localArticle){
templateText += '|text=' + displayedText;
}
templateText += '|Q=' + linkTarget + '}}';
return templateText;
}
/**
* Makes an excerpt focusing on the specified range.
* @param {string} text The original text
* @param {number} indexFrom The index of the first significant character
* @param {number} indexTo The index of the last significant character
* @param {number} margins The number of characters to include before and after the selection
* @returns {string} The excerpt with significant text and some margin
*/
function makeExcerpt(text, indexFrom, indexTo, margins){
var excerptStart = Math.max(0, indexFrom - margins);
var excerptEnd = Math.min(text.length, indexTo + margins);
var excerpt = text.substring(excerptStart, excerptEnd);
excerpt = excerpt.replace(/\x18/g, '');
if(excerptStart > 0) excerpt = '...' + excerpt;
if(excerptEnd < text.length) excerpt += '...';
return excerpt;
}
/**
* Updates the counter in the save box.
* If necessary, the box is created.
*/
function updateSaveBox(){
if(!saveBox){
saveBox = $('<div style="position:fixed; bottom:8px; right:12px; background:white; padding:8px;border:1px solid #ccc; border-radius:4px; box-shadow:0 1px 4px rgba(0, 0, 0, 0.15);"></div>');
saveBox.appendTo(document.body);
counterBox = $('<div style="margin-bottom:8px; text-align:center; font-size:0.9em;"></div>');
counterBox.appendTo(saveBox);
var buttonDiscard = new OO.ui.ButtonWidget({
label: DISCARD_BUTTON
});
buttonDiscard.on('click', discard);
saveBox.append(buttonDiscard.$element);
var buttonSave = new OO.ui.ButtonWidget({
label: SAVE_BUTTON,
flags: ['primary', 'progressive']
});
buttonSave.on('click', function(){
buttonDiscard.setDisabled(true);
buttonSave.setDisabled(true);
buttonSave.setLabel(SAVING_TEXT);
save().then(function(){
location.reload();
}).fail(function(error){
buttonDiscard.setDisabled(false);
buttonSave.setDisabled(false);
buttonSave.setLabel(SAVE_BUTTON);
mw.notify(error, {autoHideSeconds: 'long', title: ERROR_TITLE, type: 'error'});
});
});
saveBox.append(buttonSave.$element);
}
counterBox.text(mw.format(COUNTER_TEXT, numberOfChanges, totalRedLinks));
//@ts-ignore
if(!windowCloseConfirm) windowCloseConfirm = mw.confirmCloseWindow();
}
/**
* Saves the changes
* @returns {JQuery.Promise}
*/
function save(){
var deferred = $.Deferred();
var wikitext = actualWikitext;
wikitext = wikitext.replace(/\x18/g, '');
var page = mw.config.get('wgPageName');
var api = new mw.Api(API_CONFIG);
api.postWithEditToken({
action: 'edit',
title: page,
text: wikitext,
summary: mw.format(EDIT_SUMMARY, numberOfChanges),
minor: true,
bot: hasBotFlag(),
nocreate: true,
watchlist: 'nochange',
baserevid: wikitextRevision
}).then(function(){
if(windowCloseConfirm) windowCloseConfirm.release();
windowCloseConfirm = undefined;
deferred.resolve();
}).fail(function(code, result){
deferred.reject(api.getErrorMessage(result));
});
return deferred.promise();
}
/**
* Discards the changes and unsets all the variables.
*/
function discard(){
OO.ui.confirm(DISCARD_CONFIRM_TEXT).done(function(confirmed){
if(!confirmed) return;
wikitextPromise = undefined;
actualWikitext = undefined;
sanitizedWikitext = undefined;
wikitextRevision = undefined;
numberOfChanges = 0;
saveBox.remove();
saveBox = undefined;
if(windowCloseConfirm) windowCloseConfirm.release();
windowCloseConfirm = undefined;
hiddenLinks.forEach(function(link){
link.style.display = '';
});
hiddenLinks = [];
});
}
/**
* Asks the user to enter a QID and displays a popup with the wikitext.
* @param {HTMLElement} anchor The anchor element to display the popup next to
* @param {(qid: string) => ({actualWikitext: string, sanitizedWikitext: string, excerpt: string} | null)} makeWikitext A function that returns the wikitexts and excerpt for the specified QID
* @param {JQuery.Promise<WikidataSuggestion[], string> | null} [suggestionsPromise] A promise that resolves to an array of suggestions or rejects with an error message
* @returns {JQuery.Promise} A promise that resolves to the new wikitexts
*/
function displayQidPopup(anchor, makeWikitext, suggestionsPromise){
var deferred = $.Deferred();
// Ask for the QID step
var qidPanel = new OO.ui.PanelLayout({
expanded: false
});
var fieldset = new OO.ui.FieldsetLayout({});
qidPanel.$element.append(fieldset.$element);
var qidInput = new OO.ui.TextInputWidget({
placeholder: PROMPT_PLACEHOLDER
});
var qidLayout = new OO.ui.FieldLayout(qidInput, {
label: PROMPT_TEXT,
align: 'top'
});
fieldset.addItems([qidLayout]);
var qidNextButton = new OO.ui.ButtonWidget({
label: NEXT_BTN,
flags: ['primary', 'progressive'],
disabled: true
});
var qidCancelButton = new OO.ui.ButtonWidget({
label: CANCEL_BTN
});
var qidButtonLayout = $('<div style="text-align:right; margin-top:8px"></div>');
qidButtonLayout.append(qidCancelButton.$element);
qidButtonLayout.append(qidNextButton.$element);
qidPanel.$element.append(qidButtonLayout);
// Display suggestions if available
if(suggestionsPromise){
var $suggestionsContainer = displaySuggestionList(
suggestionsPromise,
function(qId){ qidInput.setValue(qId); }
);
$suggestionsContainer.insertBefore(qidButtonLayout);
}
// Confirm the wikitext step
var wikitextPanel = new OO.ui.PanelLayout({
expanded: false
});
var promptText = $('<div></div>');
wikitextPanel.$element.append(promptText);
var wikitextAcceptButton = new OO.ui.ButtonWidget({
label: ACCEPT_BTN,
flags: ['primary', 'progressive'],
});
var wikitextBackButton = new OO.ui.ButtonWidget({
label: BACK_BTN
});
var wikitextButtonLayout = $('<div style="text-align:right; margin-top:8px"></div>');
wikitextButtonLayout.append(wikitextBackButton.$element);
wikitextButtonLayout.append(wikitextAcceptButton.$element);
wikitextPanel.$element.append(wikitextButtonLayout);
var contentStack = new OO.ui.StackLayout({
expanded: false,
items: [qidPanel, wikitextPanel]
});
contentStack.setItem(qidPanel);
var $popupContainer = $('body');
var popup = new OO.ui.PopupWidget({
$container: $popupContainer,
$floatableContainer: $(anchor),
$content: contentStack.$element,
padded: true,
width: 350,
head: true,
label: QID_POPUP_TITLE,
hideCloseButton: true,
autoFlip: true,
classes: ['insert-link-interwiki-popup']
});
$popupContainer.append(popup.$element);
popup.toggle(true);
var newWikitext;
qidInput.on('change', function(){
var qid = qidInput.getValue();
qidNextButton.setDisabled(qid.length === 0);
if(!/^Q?\d+$/i.test(qid) && qid.length > 0){
qidLayout.setWarnings([INVALID_QID]);
}else{
qidLayout.setWarnings([]);
}
});
qidNextButton.on('click', function(){
newWikitext = makeWikitext(qidInput.getValue());
if(!newWikitext){
return deferred.reject(ERROR_TEXT);
}
var message = mw.format(CONFIRM_WIKITEXT_TEXT, newWikitext.excerpt);
promptText.empty();
promptText.append(
$(message)
);
contentStack.setItem(wikitextPanel);
});
qidCancelButton.on('click', function(){
popup.toggle(false);
deferred.reject(null);
});
wikitextAcceptButton.on('click', function(){
popup.toggle(false);
deferred.resolve(newWikitext);
});
wikitextBackButton.on('click', function(){
contentStack.setItem(qidPanel);
});
return deferred.promise();
}
/**
* Renders a list of item suggestions
* @param {JQuery.Promise<WikidataSuggestion[], string>} suggestionsPromise The promise that resolves to the suggestions array
* @param {(qId: string) => void} onSelect A function that is called when the user selects a suggestion (with QId as parameter)
* @returns {JQuery<HTMLElement>}
*/
function displaySuggestionList(suggestionsPromise, onSelect){
var $suggestionsContainer = $('<div class="suggestions"></div>');
$suggestionsContainer.append('<div class="header">' + SUGGESTION_HEADER + '</div>');
var $emptyState = $('<div class="empty-state">' + SUGGESTION_LOADING + '</div>');
$suggestionsContainer.append($emptyState);
suggestionsPromise.then(function(suggestions){
if(suggestions.length === 0){
$emptyState.text(SUGGESTION_NO_RESULTS);
return;
}
$emptyState.remove();
var $suggestionsList = $('<ul></ul>');
suggestions.forEach(function(suggestion){
// Prepare the list item
var $suggestionItem = $('<li></li>');
var $mainButton = $('<button type="button"></button>');
$('<span class="label"></span>')
.text(suggestion.label + ' (' + suggestion.id + ')')
.appendTo($mainButton);
$('<span class="description"></span>')
.text(suggestion.description || SUGGESTION_NODESC)
.appendTo($mainButton);
$mainButton.on('click', function(){
onSelect(suggestion.id);
});
$suggestionItem.append($mainButton);
// Add a link to Wikidata
var $wikidataLink = $('<a></a>');
$wikidataLink.attr('href', 'https://www.wikidata.org/wiki/' + suggestion.id);
$wikidataLink.attr('target', '_blank');
$wikidataLink.attr('title', SUGGESTION_EXT_TITLE);
$wikidataLink.append('<img src="https://upload.wikimedia.org/wikipedia/commons/6/67/OOjs_UI_icon_external-link-ltr.svg" />');
$suggestionItem.append($wikidataLink);
$suggestionsList.append($suggestionItem);
});
$suggestionsContainer.append($suggestionsList);
}).fail(function(error){
$emptyState.text(mw.format(SUGGESTION_ERROR, error));
});
return $suggestionsContainer;
}
/**
* Returns a list of suggestions for the specified title.
* @param {string} title The title of the item to search for
* @returns {JQuery.Promise<WikidataSuggestion[], string> | null} A promise that resolves to an array of suggestions or rejects with an error message (or null if unavailable)
*/
function getItemSuggestions(title){
var deferred = $.Deferred();
if(!mw.ForeignApi){
return null;
}
// Strip the disambiguation part from the title
title = title.replace(/([ _]*\([^)]+\))/g, '');
var api = new mw.ForeignApi('https://www.wikidata.org/w/api.php', API_CONFIG);
api.get({
action: 'wbsearchentities',
type: 'item',
search: title,
language: mw.config.get('wgContentLanguage'),
uselang: mw.config.get('wgContentLanguage'),
limit: 4,
formatversion: 2
}).then(function(data){
var results = data.search || [];
var suggestions = [];
for(var i = 0; i < results.length; i++){
var result = results[i];
suggestions.push({
label: result.label,
description: result.description,
id: result.id
});
}
deferred.resolve(suggestions);
}).fail(function(code, result){
deferred.reject(api.getErrorMessage(result));
});
return deferred.promise();
}
/**
* Checks if the user can mark their edits as bot.
* @returns {boolean}
*/
function hasBotFlag(){
var flagGroups = ['bot', 'flood'];
var userGroups = mw.config.get('wgUserGroups');
for(var i = 0; i < flagGroups.length; i++){
if(userGroups.indexOf(flagGroups[i]) !== -1){
return true;
}
}
return false;
}
// Initialize only on editable pages in read mode
if(!mw.config.get('wgIsProbablyEditable')) return;
if(mw.config.get('wgAction') !== 'view') return;
var ns = mw.config.get('wgNamespaceNumber');
var pageTitle = mw.config.get('wgPageName');
var isSubpage = pageTitle.indexOf('/') !== -1;
var redLinks = getRedlinks();
totalRedLinks = redLinks.length;
if(totalRedLinks === 0) return;
if(ns == 0 || (ns == 2 && isSubpage)){
displayLinks(redLinks);
} else {
var link = mw.util.addPortletLink('p-tb', 'javascript:void(0)', TRIGGER_TEXT, 't-link-interwiki', TRIGGER_TOOLTIP);
link.addEventListener('click', function(){
displayLinks(redLinks);
});
}
});
/**
* Describes the new wikitext and place where the modification was made.
* Indices refer to the new wikitext.
* @typedef {{
* newWikitext: string,
* newSanitizedWikitext: string,
* indexFrom: number,
* indexTo: number,
* }} LinkReplaceState
*
* Describes a suggestion for an item retrieved from Wikidata.
* @typedef {{
* id: string,
* label: string | undefined,
* description: string | undefined
* }} WikidataSuggestion
*/
// </nowiki>