/**
* disambigAutoDesc.js
* <nowiki>
* Добавляет кнопку, по которой для выделенного текста со ссылкой в [[]] добавляется описание из преамбулы страницы.
* Для страниц значений заменяет выделенную ссылку на шаблон {{NL}}.
* Работает только на страницах значений, в перенаправлениях и при создании страниц.
*/
mw.loader.using( [
'mediawiki.util',
'jquery.textSelection',
'ext.gadget.registerTool',
], () => {
if ( mw.config.get( 'wgNamespaceNumber' ) !== 0 ) return;
// @ts-expect-error
if ( typeof registerTool === 'undefined' ) return;
const _linkRegex = /\[\[(.*?)[\|\]]+/;
let _textbox = $( '#wpTextbox1' );
const _templateNames = [
'неоднозначность',
'многозначность',
'disambig',
'disambiguation',
'дизамбиг',
'список значений',
'militarydis',
'военные части',
'воинские формирования',
'воинские части',
'нисба',
'одноимённые воинские части',
'омонимы систематиков',
'список однофамильцев',
'список однофамильцев-тезок',
'список однофамильцев-тёзок',
'список полных тёзок',
'список тёзок',
'список тёзок-однофамильцев',
];
const notDisambigable = () => {
// Для редактора-2017 проверяем HTML
if ( document.querySelector( '#disambig' ) !== null ) return false;
// Несуществующие страницы
if ( mw.config.get( 'wgArticleId' ) === 0 ) return false;
let text = _textbox.val()?.toString().toLowerCase();
if ( !text ) return false;
// Перенаправления
if ( text.match( /^#.* \[\[/ ) !== null ) return false;
return !_templateNames.some( t => text.includes( `{{${ t }` ) );
}
if ( notDisambigable() ) return;
/**
* @param {string} msg
* @param {mw.notification.NotificationOptions} options
*/
const showMessage = ( msg, options ) => {
let result = $( '<span>' ).html(
msg.replace( _linkRegex, ( _, $1 ) => {
var url = mw.util.getUrl( $1 );
return `<a href="${ url }">[[${ $1 }]]</a>`;
} )
);
return mw.notify( result, {
tag: 'disambigAutoDesc',
...options,
} );
}
/**
* @param {string} msg
*/
const showError = ( msg ) => {
return showMessage( msg, {
type: 'error',
autoHideSeconds: 'long',
} );
}
const runAutoDesc = () => {
_textbox = $( '#wpTextbox1' );
const selection = _textbox.textSelection( 'getSelection' );
if ( selection === null || selection === '' ) {
showError( 'Для создания автоопределения необходимо выделить текст.' );
return;
}
// Does not contain wikilink syntax
const wikilink = selection.match( _linkRegex );
if ( wikilink === null ) {
showError( 'Для создания автоопределения необходимо выделить [[вики-ссылку]] в квадратных скобках.' );
return;
}
const target = wikilink[ 1 ];
fetch( `/api/rest_v1/page/summary/${ target }` ).then( res => res.json() ).then( data => {
// Ошибки разного рода
if ( ![ 'standard', 'disambiguation' ].includes( data.type ) ) {
showError( `<b>Не вышло создать автоопределение [[${ target }]].</b> Скорее всего, страница не существует.` );
return;
}
const title = data.title;
let text = selection;
let desc = null;
if ( data.type === 'disambiguation' ) {
// Заменить ссылку на {{NL}}
text = text.replace( '[[', `{{NL|` )
.replace( target, title )
.replace( ']]', '}}' );
text = text.replace( /{{NL\|(.*?) \(значения\)\|\1}}/, '{{NL|$1 (значения)}}' );
} else {
const dashes = `[–−—―]`;
// Текст после тире вне (1900—2000)
const dashRegex = new RegExp( `[^\\d]${ dashes }\\s*([^\\.\\n]+)`, 'm' );
const endStopRegex = /\.*(\n?)$/;
desc = data.extract.match( dashRegex );
if ( desc !== null ) {
desc = desc[ 1 ].trim();
const newDesc = desc.trim();
let result = text.trim().replace( endStopRegex, '$1' );
if ( text.match( dashRegex ) === null ) {
result = `${ result } — ${ newDesc }`;
} else {
// Заменить часть после последнего тире, а не все
const textParts = text.split( new RegExp( `\\s+${ dashes }\\s+` ) );
result = textParts[ 0 ];
for (let i = 1; i < textParts.length; i++) {
let part = textParts[ i ];
if ( i === textParts.length - 1 ) {
part = newDesc;
}
result = `${ result } — ${ part }`;
}
}
// В выделении в Коудмирроре может быть перенос в конце
if ( text.match( /\n+$/ ) ) {
result = result + '\n';
}
text = result.replace( endStopRegex, '.$1' );
}
}
// Добавить годы жизни для статей в формате ФИО для страниц с элементами Викиданных
if ( data.wikibase_item && title.includes( ', ' ) && !text.includes( ']] (' ) ) {
text = text.replace( /\]\]\s*—/, `]] ({{подст:годы жизни|${ data.wikibase_item }}}) —` );
}
if ( text === selection ) {
showMessage( `<b>Не вышло создать автоопределение [[${ title }]].</b> Скорее всего, на странице отсутствует тире или текущее определение совпадает с новым.`, {
type: 'warn',
} );
return;
}
// Уточнить ссылку, если отличается
if ( data.type === 'standard' && target !== title ) {
text = text.replace( target, title );
}
// Скрыть уточнение при наличии
if ( title.includes( ' (' ) ) {
text = text.replace( title + ']]', `${ title }|]]` );
}
_textbox.textSelection( 'encapsulateSelection', {
peri: text,
replace: true,
} );
// Перейти в конец добавленной строки
let textboxVal = _textbox.val()?.toString();
let linkIndex = textboxVal?.indexOf( title );
const startPos = textboxVal ? textboxVal.indexOf( desc, linkIndex ) : -1;
if ( desc && startPos !== -1 ) {
_textbox.textSelection( 'setSelection', {
start: startPos + desc.length,
end: startPos + desc.length,
} );
}
showMessage( `<b>Создано автоопределение [[${ title }]].</b> Пожалуйста, отредактируйте его в соответствии с правилами проекта. Помните, что все определения должны быть краткими.`, {
type: 'success',
} );
} ).catch( err => {
showError( `<b>При запросе автоопределения произошла техническая ошибка:</b><br>${ err }` );
} );
}
// @ts-expect-error
registerTool( {
name: 'disambigAutoDesc',
position: 1312,
title: 'Автоопределение',
label: 'Выделите текст со [[ссылкой]], чтобы добавить ей автоопределение из преамбулы',
callback: runAutoDesc,
classic: {
icon: 'https://upload.wikimedia.org/wikipedia/commons/a/a9/Logo_disambig.svg',
},
visual: {
icon: 'https://upload.wikimedia.org/wikipedia/commons/a/a9/Logo_disambig.svg',
modes: [ 'source' ],
},
} );
} );
// </nowiki>