MediaWiki:Gadget-oldNoteTA.js:修订间差异

来自植物大战僵尸百科
跳转到导航 跳转到搜索
添加的内容 删除的内容
優枰(留言 | 贡献
无编辑摘要
優枰(留言 | 贡献
标签已被回退
第1行: 第1行:
// 复制自<https://zh.wikipedia.org/w/index.php?title=MediaWiki:Gadget-noteTA.js&oldid=82085204>,按CC BY-SA 4.0
/**

* NoteTAViewer
* @description Viewer for NoteTA-related templates and modules, original code from https://zh.wikipedia.org/wiki/MediaWiki:Gadget-noteTA.js .
* @modifier Winston Sung
* @license CC BY-SA 3.0
* @external "jQuery 1.12.4"
* @external "jQuery UI 1.12.1"
* @external "I18n-js"
*/
// <nowiki>
// <nowiki>
/**!
/* mw.loader.load('https://code.jquery.com/jquery-1.12.4.js'); */
* _________________________________________________________________________________
mw.loader.load('https://code.jquery.com/ui/1.12.1/jquery-ui.js');
* | |
mw.loader.load('/zh/wiki/MediaWiki:Gadget-NoteTA.css?action=raw&ctype=text/css', 'text/css' );
* | === WARNING: GLOBAL GADGET FILE === |
* | Changes to this page affect many users. |
* | Please discuss changes on the talk page, [[WP:VPT]] before editing. |
* |_________________________________________________________________________________|
*
* Covert From https://zh.wikipedia.org/w/index.php?title=MediaWiki:Gadget-noteTA.js&oldid=63601886
* @author [[User:SunAfterRain]]
*/
$(() => {
const HanAssist = require('ext.gadget.HanAssist');


const api = new mw.Api();
noteTAViewer = ( function() { $( function() {
var api = null;
var init = function( hash ) {
var $dialog = $( '<div class="noteTA-dialog" />' );
$dialog.html( '<div class="mw-ajax-loader" style="margin-top: 48px;" />' );
$dialog.dialog( {
title: wgULS( '字词转换', '字詞轉換' )
} );
api = new mw.Api();
run( $dialog, hash );
return $dialog;
}, run = function( $dialog, hash ) {
var wikitext = '';
var $dom = $( '#noteTA-' + hash );
var collapse = true;
var actualTitle = mw.config.get( 'wgPageName' ).replace( /_/g, ' ' );


/** @type {Map<string, OO.ui.ProcessDialog>} */
var parse = function() {
const viewerMap = new Map();
api.post( {
const windowManager = new OO.ui.WindowManager();
action: 'parse',
windowManager.$element.appendTo(document.body);
title: 'Template:CGroup/-',
text: wikitext,
prop: 'text',
variant: mw.config.get( 'wgUserVariant' )
} ).done( function( results ) {
$dialog.html( results.parse.text['*'] );
if ( collapse ) {
/* $dialog.find( '.mw-collapsible' ).makeCollapsible(); */
$dialog.find( '.mw-collapsible-toggle' ).on( 'click.mw-collapse', function( e ) {
var $collapsibleContent = $( this ).parent( '.mw-collapsible' ).find( '.mw-collapsible-content' );
setTimeout( function() {
$collapsibleContent.promise().done( function() {
$dialog.dialog( 'option', 'position', 'center' );
} );
}, 0 );
} );
}
$dialog.dialog( 'option', 'width', Math.round( $( window ).width() * 0.8 ) );
$dialog.css( 'max-height', Math.round( $( window ).height() * 0.8 ) + 'px' );
$dialog.dialog( 'option', 'position', 'center' );
} ).fail( parse );
}, maybeTitle = parse;


/**
var $noteTAtitle = $dom.find( '.noteTA-title' );
* @param {any} value
if ( $noteTAtitle.length ) {
* @param {string} valueName
var titleConv = $noteTAtitle.attr( 'data-noteta-code' );
* @return {asserts value}
var titleDesc = $noteTAtitle.attr( 'data-noteta-desc' );
*/
if ( titleDesc ) {
function assert(value, valueName) {
titleDesc = '(' + titleDesc + ')';
if (!value) {
throw new Error(`Assert Fail, ${valueName} == false.`);
}
}

class ApiRetryFailError extends Error {
/**
* @param {string[]} errors
*/
constructor(errors) {
super(`Api calls failed ${errors.length} time(s) in a row.`);
this.name = 'ApiRetryFailError';
this.errors = errors;
}

toJQuery() {
const errorCount = this.errors.length;
return $('<div>')
.attr({
class: 'error'
})
.append(
$('<p>')
.text(HanAssist.conv({
hans: `Api 调用连续失败 ${errorCount} 次,${errorCount} 次调用的错误分别为:`,
hant: `Api 調用連續失敗 ${errorCount} 次,${errorCount} 次調用的錯誤分別為:`,
other: `Api calls failed ${errorCount} time(s) in a row. Errors: `
})),
$('<ol>')
.append(this.errors.map(v => $('<li>').append(v.split('\n').map(v => $('<p>').text(v)))))
);
}
}

/**
* @typedef {{ [K in keyof C]: C[K] extends (...args: any[]) => any ? K : never; }[keyof C]} GetClassMethods
* @template C
*/

/**
* @template {GetClassMethods<mw.Api>} M
* @param {M} method
* @param {Parameters<mw.Api[M]>} args
* @param {number} count
* @param {string[]} previousErrors
* @return {Promise<Awaited<ReturnType<mw.Api[M]>>>}
*/
function retryApiRequestES6Warp(method, args, count = 3, previousErrors = []) {
if (!count) {
return $.Deferred().reject(new ApiRetryFailError(previousErrors));
}
const deferred = $.Deferred();
api[method](...args).then(deferred.resolve, error => {
console.error(error);
if (error && typeof error === 'object' && 'stack' in error) {
previousErrors.push(error.stack);
} else {
} else {
previousErrors.push(String(error));
titleDesc = '';
}
}
retryApiRequestES6Warp(method, args, --count, previousErrors)
wikitext += '<span class="plainlinks" style="float: right;">[{{fullurl:' + actualTitle + '|action=edit&section=0}} {{int:edit}}]</span>\n';
.then(deferred.resolve, deferred.reject);
wikitext += '; 本文使用标题手工转换\n';
});
wikitext += '* 转换标题为:-{D|' + titleConv + '}-' + titleDesc + '\n';
return deferred;
wikitext += '* 实际标题为:-{R|' + actualTitle + '}-\n';
}
wikitext += '* 当前显示为:-{|' + titleConv + '}-\n';

} else {
/**
maybeTitle = function() {
* @template {GetClassMethods<mw.Api>} M
api.post( {
* @param {M} method
action: 'parse',
* @param {Parameters<mw.Api[M]>} args
title: actualTitle,
* @return {Promise<Awaited<ReturnType<mw.Api[M]>>>}
text: '{{noteTA/multititle|' + actualTitle + '}}',
*/
prop: 'text',
function retryApiRequest(method, ...args) {
variant: 'zh'
return retryApiRequestES6Warp(method, args);
} ).done( function( results ) {
}
var $multititle = $( results.parse.text['*'] ).find( '.noteTA-multititle' );

if ( $multititle.length ) {
/**
var textVariant = {}, variantText = {}, multititleText = '';
* @param {string} hash
$multititle.children().each( function() {
*/
var $li = $( this );
function getViewer(hash) {
var variant = $li.attr( 'data-noteta-multititle-variant' );
if (viewerMap.has(hash)) {
var text = $li.text();
const viewer = viewerMap.get(hash);
variantText[variant] = text;
assert(viewer, 'viewer');
if ( textVariant[text] ) {
return viewer;
textVariant[text].push( variant );
}
} else {

textVariant[text] = [ variant ];
const dom = document.getElementById(`noteTA-${hash}`);
if (!dom) {
throw new Error(`Can\'t get Element "#noteTA-${hash}".`);
}
const $dom = $(dom);

class NoteTAViewer extends OO.ui.ProcessDialog {
constructor() {
super({
size: 'larger'
});
this.hash = hash;
this.dataIsLoaded = false;
this.collapse = true;

this.$realContent = $('<div>');
this.mutationObserver = new MutationObserver(() => {
this.updateSize();
});
this.mutationObserver.observe(this.$realContent.get(0), {
subtree: true,
childList: true
});
}

initialize() {
super.initialize();

this.content = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.$realContent.appendTo(this.content.$element);

this.$body.append(this.content.$element);

return this;
}

destroy() {
this.mutationObserver.disconnect();
}

getNoteTAParseText() {
if (this.noteTAParseText) {
return $.Deferred().resolve(this.noteTAParseText);
}

const $noteTAtitle = $dom.find('.noteTA-title');
const actualTitle = mw.config.get('wgPageName').replace(/_/g, ' ');
let wikitext = '';

const titleDeferred = $.Deferred();
if ($noteTAtitle.length) {
const titleConv = $noteTAtitle.attr('data-noteta-code');
assert(titleConv, 'titleConv');
let titleDesc = $noteTAtitle.attr('data-noteta-desc');
if (titleDesc) {
titleDesc = '(' + titleDesc + ')';
} else {
titleDesc = '';
}
wikitext += '<span style="float: right;">{{edit|' + actualTitle + '|section=0}}</span>\n';
wikitext += '; 本文使用[[Help:中文维基百科的繁简、地区词处理#條目標題|标题手工转换]]\n';
wikitext += '* 转换标题为:-{D|' + titleConv + '}-' + titleDesc + '\n';
wikitext += '* 实际标题为:-{R|' + actualTitle + '}-;当前显示为:-{|' + titleConv + '}-\n';
titleDeferred.resolve();
} else {
retryApiRequest('parse', '{{noteTA/multititle|' + actualTitle + '}}', {
title: actualTitle,
variant: 'zh'
}).then(resultHtml => {
const $multiTitle = $($.parseHTML(resultHtml)).find('.noteTA-multititle');

if ($multiTitle.length) {
/** @type {Record<string, string[]>} */
const textVariant = {};
/** @type {Record<string, string|null>} */
const variantText = {};
wikitext += '; 本文[[Help:中文维基百科的繁简、地区词处理#條目標題|标题可能经过转换]]\n* 转换标题为:';
for (const li of $multiTitle.children()) {
const $li = $(li);
const variant = $li.attr('data-noteta-multititle-variant');
assert(variant, 'variant');
const text = $li.text().trim();
variantText[variant] = text;
if (textVariant[text]) {
textVariant[text].push(variant);
} else {
textVariant[text] = [variant];
}
}
}

} );
const multiTitle = [];
multititleText += '; 本文标题可能经过转换\n';
const titleConverted = variantText[mw.config.get('wgUserVariant')];
multititleText += '* 转换标题为:';
var multititle = [], titleConverted = variantText[mw.config.get( 'wgUserVariant' )];
for (const variant in variantText) {
for ( var variant in variantText ) {
const text = variantText[variant];
var text = variantText[variant];
if (text === null) {
if ( text === null ) {
continue;
continue;
}

const variants = textVariant[text];
if (!variants) {
continue;
}

for (const variant of variants) {
variantText[variant] = null;
}

const variantsName = variants.map((variant) => `-{R|{{MediaWiki:Variantname-${variant}}}}-`).join('、');
multiTitle.push(variantsName + ':-{R|' + text + '}-');
}
}
var variants = textVariant[text];
wikitext += multiTitle.join(';');
wikitext += '\n* 实际标题为:-{R|' + actualTitle + '}-;当前显示为:-{R|' + titleConverted + '}-\n';
$.each( variants, function() {
}
variantText[this] = null;
} );
titleDeferred.resolve();
}).catch(titleDeferred.reject);
var variantsName = $.map( variants, function( variant ) {
}
return '-{R|{{MediaWiki:Variantname-' + variant + '}}}-';

} ).join( '、' );
const deferred = $.Deferred();
multititle.push( variantsName + ':-{R|' + text + '}-' );
titleDeferred.then(() => {
const $noteTAgroups = $dom.find('.noteTA-group > *[data-noteta-group]');
if ($noteTAgroups.length > 1) {
this.collapse = true;
}
for (const ele of $noteTAgroups) {
const $ele = $(ele);
switch ($ele.attr('data-noteta-group-source')) {
case 'template':
wikitext += '{{CGroup/' + $ele.attr('data-noteta-group') + '}}\n';
break;
case 'module':
wikitext += '{{#invoke:CGroupViewer|dialog|' + $ele.attr('data-noteta-group') + '}}\n';
break;
case 'none':
wikitext += '; 本文使用的公共转换组“' + $ele.attr('data-noteta-group') + '”尚未创建\n';
wikitext += '* {{edit|Module:CGroup/' + $ele.attr('data-noteta-group') + '|创建公共转换组“' + $ele.attr('data-noteta-group') + '”}}\n';
break;
default:
wikitext += '; 未知公共转换组“' + $ele.attr('data-noteta-group') + '”来源“' + $ele.attr('data-noteta-group-source') + '”\n';
}
}
multititleText += multititle.join( ';' );
multititleText += '\n* 实际标题为:-{R|' + actualTitle + '}-\n* 当前显示为:-{R|' + titleConverted + '}-\n';
wikitext = multititleText + wikitext;
}
}
parse();
} ).fail( maybeTitle );
};
}


var $noteTAgroups = $dom.find( '.noteTA-group > *[data-noteta-group]' );
const $noteTAlocal = $dom.find('.noteTA-local');
if ( $noteTAgroups.length > 1 ) {
if ($noteTAlocal.length) {
collapse = true;
this.collapse = true;
wikitext += '<span style="float: right;">{{edit|' + actualTitle + '|section=0}}</span>\n';
}
wikitext += '; 本文使用[[Help:中文维基百科的繁简、地区词处理#控制自动转换的代碼|全文手工转换]]\n';
$noteTAgroups.each( function() {
const $noteTAlocals = $noteTAlocal.children('*[data-noteta-code]');
var $this = $( this ), text = '';
for (const that of $noteTAlocals) {
switch ( $this.attr( 'data-noteta-group-source' ) ) {
const $this = $(that);
case 'template':
wikitext += '{{CGroup/' + $this.attr( 'data-noteta-group' ) + '}}\n';
const localConv = $this.attr('data-noteta-code');
let localDesc = $this.attr('data-noteta-desc');
break;
if (localDesc) {
case 'module':
localDesc = '(' + localDesc + ')';
wikitext += '{{#invoke:CGroupViewer|dialog|' + $this.attr( 'data-noteta-group' ) + '}}\n';
break;
} else {
case 'none':
localDesc = '';
}
wikitext += '; 本文使用的公共转换组「' + $this.attr( 'data-noteta-group' ) + '」尚未创建\n';
wikitext += '* -{D|' + localConv + '}-' + localDesc + '当前显示为:-{' + localConv + '}-\n';
wikitext += '* <span class="plainlinks">[{{fullurl:Module:CGroup/' + $this.attr( 'data-noteta-group' ) + '|action=edit}} 创建公共转换组「' + $this.attr( 'data-noteta-group' ) + '」]</span>\n';
break;
}
default:
}

wikitext += '; 未知公共转换组「' + $this.attr( 'data-noteta-group' ) + '」来源「' + $this.attr( 'data-noteta-group-source' ) + '」\n';
wikitext += '{{noteTA/footer}}\n';

this.noteTAParseText = wikitext;

deferred.resolve(wikitext);
}).catch(deferred.reject);
return deferred;
}
}
} );


doExecute() {
var $noteTAlocal = $dom.find( '.noteTA-local' );
if ( $noteTAlocal.length ) {
if (this.dataIsLoaded) {
return $.Deferred().resolve();
collapse = true;
wikitext += '<span style="float: right;"><span class="noprint" style="white-space:nowrap; font-size:smaller">[{{fullurl:' + actualTitle + '|action=edit&section=0}} {{int:edit}}]</span></span>\n';
wikitext += '; 本文使用全文手工转换\n';
var $noteTAlocals = $noteTAlocal.children( '*[data-noteta-code]' );
$noteTAlocals.each( function() {
var $this = $( this );
var localConv = $this.attr( 'data-noteta-code' );
var localDesc = $this.attr( 'data-noteta-desc' );
if ( localDesc ) {
localDesc = '(' + localDesc + ')';
} else {
localDesc = '';
}
}
wikitext += '* -{D|' + localConv + '}-' + localDesc + '当前显示为:-{' + localConv + '}-\n';
} );
}


this.$realContent.empty().append(
/* wikitext += '{{转换/末尾}}\n'; */
$('<p>').text(HanAssist.conv({ hans: '正在加载...', hant: '正在載入...' }))
maybeTitle();
);
};


return this.getNoteTAParseText()
$( '.mw-indicator[id^=mw-indicator-noteTA-]' )
.then(wikitext => retryApiRequest('parse', wikitext, {
.css( 'cursor', 'pointer' )
title: 'Template:CGroup/-',
.each( function() {
variant: mw.config.get('wgUserVariant')
var $dialog = null;
}))
var $this = $( this );
.then(parsedHtml => {
var hash = $this.attr( 'id' ).replace( /^mw-indicator-noteTA-/, '' );
$this.click( function() {
this.$realContent.empty().html(parsedHtml);
this.$realContent.find('.mw-collapsible').makeCollapsible();
if ( $dialog === null ) {
$dialog = init( hash );
this.updateSize();

this.dataIsLoaded = true;
})
.catch(error => {
if (error instanceof ApiRetryFailError) {
throw new OO.ui.Error(error.toJQuery(), { recoverable: true });
} else {
throw new OO.ui.Error(String(error), { recoverable: false });
}
});
}

doExecuteWrap() {
if (!this.executePromise) {
this.executePromise = this.doExecute();
delete this.lastError;

const deferred = $.Deferred();
this.executePromise
.then(deferred.resolve)
.catch(error => {
if (error instanceof OO.ui.Error) {
this.lastError = error;
} else {
deferred.reject(error);
}
})
.always(() => {
delete this.executePromise;
});
return deferred;
} else {
} else {
$dialog.dialog( 'open' );
const deferred = $.Deferred();
this.executePromise
.then(deferred.resolve)
.catch(error => {
if (!(error instanceof OO.ui.Error)) {
deferred.reject(error);
} else {
deferred.resolve();
}
})
.always(() => {
delete this.executePromise;
});
return deferred;
}
}
} );
}

} );
getSetupProcess(data) {
return super.getSetupProcess(data).next(() => {
this.doExecuteWrap();
this.executeAction('main');
});
}

getActionProcess(action) {
return super.getActionProcess(action)
.next(() => {
if (action === 'main') {
return this.doExecuteWrap();
}
})
.next(() => {
if (action === 'main' && this.lastError) {
return this.lastError;
}

return super.getActionProcess(action).execute();
});
}
}

NoteTAViewer.static = Object.create( OO.ui.ProcessDialog.static );
NoteTAViewer.static.name = 'NoteTALoader-' + hash;
NoteTAViewer.static.title = HanAssist.conv({ hans: '字词转换', hant: '字詞轉換' });
NoteTAViewer.static.actions = [
{
label: mw.msg('ooui-dialog-process-dismiss'),
flags: 'primary'
}
];

const viewer = new NoteTAViewer();
windowManager.addWindows([viewer]);
viewerMap.set(hash, viewer);

return viewer;
}

function resetAllViewer() {
for (const viewer of viewerMap.values()) {
viewer.destroy();
}
viewerMap.clear();
windowManager.clearWindows();
}

const skin = mw.config.get('skin');
let portletId = null;
const xNoteTAViewer = 'x-noteTA-viewer';
let globalInit = () => {};
let globalDeinit = () => {};
if (skin === 'vector') {
portletId = 'p-noteTA';
let $vectorNoteTATab;
globalInit = () => {
if ($vectorNoteTATab) {
return;
}
$vectorNoteTATab = $(mw.util.addPortlet(portletId));
$('#p-variants').after(
$vectorNoteTATab
.removeClass(['mw-portlet-p-noteTA'])
.addClass(['mw-portlet-noteTA', 'vector-menu-tabs', 'vector-menu-tabs-legacy'])
);
};
globalDeinit = () => {
if (!$vectorNoteTATab) {
return;
}
$vectorNoteTATab.find('ul').empty();
mw.util.hidePortlet(portletId);
};
} else if (skin === 'vector-2022') {
portletId = 'p-associated-pages';
globalDeinit = () => {
$(document.getElementsByClassName(xNoteTAViewer)).remove();
};
}

function addPortletItem(hash) {
const $portlet = $(mw.util.addPortletLink(
portletId,
'#',
'汉/漢',
`ca-noteTA-${hash}`
));
$portlet
.addClass(xNoteTAViewer)
.find('a')
.empty()
.append(
$('<div>')
.append(
$('<span>')
.css({
padding: '1px 3px',
background: '#d3e3f4',
color: '#000000',
height: '85%'
}).text('汉'),
$('<span>')
.css({
padding: '1px 3px',
background: '#e9e9e9',
color: '#434343',
height: '85%'
}).text('漢')
)
);
return $portlet;
}

function noteTAViewer() {
resetAllViewer();

globalDeinit();
globalInit();

for (const ele of $('.mw-indicator[id^=mw-indicator-noteTA-]')) {
const hash = ele.id.replace(/^mw-indicator-noteTA-/, '');
let $ele = $(ele);
if (portletId) {
$ele.hide();
$ele = addPortletItem(hash);
} else {
$ele.css('cursor', 'pointer');
}
$ele.on('click', (e) => {
e.preventDefault();
getViewer(hash).open();
});
}
}

noteTAViewer.get = getViewer;
noteTAViewer.reset = resetAllViewer;
noteTAViewer.globalInit = globalInit;
noteTAViewer.globalDeinit = globalDeinit;
noteTAViewer.addPortletItem = addPortletItem;

mw.libs.noteTAViewer = noteTAViewer;


mw.hook('wikipage.content').add(function ($content) {
} ); } );
noteTAViewer();
mw.hook('wikipage.content').add( function ( $content ) {
});
setTimeout("noteTAViewer();", 0);
});
});
// </nowiki>
// </nowiki>

2024年6月22日 (六) 04:46的版本

// 复制自<https://zh.wikipedia.org/w/index.php?title=MediaWiki:Gadget-noteTA.js&oldid=82085204>,按CC BY-SA 4.0

// <nowiki>
/**!
 *  _________________________________________________________________________________
 * |                                                                                 |
 * |                      === WARNING: GLOBAL GADGET FILE ===                        |
 * |                    Changes to this page affect many users.                      |
 * |       Please discuss changes on the talk page, [[WP:VPT]] before editing.       |
 * |_________________________________________________________________________________|
 *
 * Covert From https://zh.wikipedia.org/w/index.php?title=MediaWiki:Gadget-noteTA.js&oldid=63601886
 * @author [[User:SunAfterRain]]
 */
$(() => {
	const HanAssist = require('ext.gadget.HanAssist');

	const api = new mw.Api();

	/** @type {Map<string, OO.ui.ProcessDialog>} */
	const viewerMap = new Map();
	const windowManager = new OO.ui.WindowManager();
	windowManager.$element.appendTo(document.body);

	/**
	 * @param {any} value
	 * @param {string} valueName 
	 * @return {asserts value} 
	 */
	function assert(value, valueName) {
		if (!value) {
			throw new Error(`Assert Fail, ${valueName} == false.`);
		}
	}

	class ApiRetryFailError extends Error {
		/**
		 * @param {string[]} errors
		 */
		constructor(errors) {
			super(`Api calls failed ${errors.length} time(s) in a row.`);
			this.name = 'ApiRetryFailError';
			this.errors = errors;
		}

		toJQuery() {
			const errorCount = this.errors.length;
			return $('<div>')
				.attr({
					class: 'error'
				})
				.append(
					$('<p>')
						.text(HanAssist.conv({
							hans: `Api 调用连续失败 ${errorCount} 次,${errorCount} 次调用的错误分别为:`,
							hant: `Api 調用連續失敗 ${errorCount} 次,${errorCount} 次調用的錯誤分別為:`,
							other: `Api calls failed ${errorCount} time(s) in a row. Errors: `
						})),
					$('<ol>')
						.append(this.errors.map(v => $('<li>').append(v.split('\n').map(v => $('<p>').text(v)))))
				);
		}
	}

	/**
	 * @typedef {{ [K in keyof C]: C[K] extends (...args: any[]) => any ? K : never; }[keyof C]} GetClassMethods
	 * @template C
	 */

	/**
	 * @template {GetClassMethods<mw.Api>} M
	 * @param {M} method
	 * @param {Parameters<mw.Api[M]>} args
	 * @param {number} count
	 * @param {string[]} previousErrors
	 * @return {Promise<Awaited<ReturnType<mw.Api[M]>>>}
	 */
	function retryApiRequestES6Warp(method, args, count = 3, previousErrors = []) {
		if (!count) {
			return $.Deferred().reject(new ApiRetryFailError(previousErrors));
		}
		const deferred = $.Deferred();
		api[method](...args).then(deferred.resolve, error => {
			console.error(error);
			if (error && typeof error === 'object' && 'stack' in error) {
				previousErrors.push(error.stack);
			} else {
				previousErrors.push(String(error));
			}
			retryApiRequestES6Warp(method, args, --count, previousErrors)
				.then(deferred.resolve, deferred.reject);
		});
		return deferred;
	}

	/**
	 * @template {GetClassMethods<mw.Api>} M
	 * @param {M} method
	 * @param {Parameters<mw.Api[M]>} args
	 * @return {Promise<Awaited<ReturnType<mw.Api[M]>>>}
	 */
	function retryApiRequest(method, ...args) {
		return retryApiRequestES6Warp(method, args);
	}

	/**
	 * @param {string} hash
	 */
	function getViewer(hash) {
		if (viewerMap.has(hash)) {
			const viewer = viewerMap.get(hash);
			assert(viewer, 'viewer');
			return viewer;
		}

		const dom = document.getElementById(`noteTA-${hash}`);
		if (!dom) {
			throw new Error(`Can\'t get Element "#noteTA-${hash}".`);
		}
		const $dom = $(dom);

		class NoteTAViewer extends OO.ui.ProcessDialog {
			constructor() {
				super({
					size: 'larger'
				});
				this.hash = hash;
				this.dataIsLoaded = false;
				this.collapse = true;

				this.$realContent = $('<div>');
              
	
				this.mutationObserver = new MutationObserver(() => {
					this.updateSize();
				});
				this.mutationObserver.observe(this.$realContent.get(0), {
					subtree: true,
					childList: true
				});
			}

			initialize() {
				super.initialize();

				this.content = new OO.ui.PanelLayout({
					padded: true,
					expanded: false
				});
				this.$realContent.appendTo(this.content.$element);

				this.$body.append(this.content.$element);

				return this;
			}

			destroy() {
				this.mutationObserver.disconnect();
            }

			getNoteTAParseText() {
				if (this.noteTAParseText) {
					return $.Deferred().resolve(this.noteTAParseText);
				}

				const $noteTAtitle = $dom.find('.noteTA-title');
				const actualTitle = mw.config.get('wgPageName').replace(/_/g, ' ');
				let wikitext = '';

				const titleDeferred = $.Deferred();
				if ($noteTAtitle.length) {
					const titleConv = $noteTAtitle.attr('data-noteta-code');
					assert(titleConv, 'titleConv');
					let titleDesc = $noteTAtitle.attr('data-noteta-desc');
					if (titleDesc) {
						titleDesc = '(' + titleDesc + ')';
					} else {
						titleDesc = '';
					}
					wikitext += '<span style="float: right;">{{edit|' + actualTitle + '|section=0}}</span>\n';
					wikitext += '; 本文使用[[Help:中文维基百科的繁简、地区词处理#條目標題|标题手工转换]]\n';
					wikitext += '* 转换标题为:-{D|' + titleConv + '}-' + titleDesc + '\n';
					wikitext += '* 实际标题为:-{R|' + actualTitle + '}-;当前显示为:-{|' + titleConv + '}-\n';
					titleDeferred.resolve();
				} else {
					retryApiRequest('parse', '{{noteTA/multititle|' + actualTitle + '}}', {
						title: actualTitle,
						variant: 'zh'
					}).then(resultHtml => {
						const $multiTitle = $($.parseHTML(resultHtml)).find('.noteTA-multititle');

						if ($multiTitle.length) {
							/** @type {Record<string, string[]>} */
							const textVariant = {};
							/** @type {Record<string, string|null>} */
							const variantText = {};
							wikitext += '; 本文[[Help:中文维基百科的繁简、地区词处理#條目標題|标题可能经过转换]]\n* 转换标题为:';
							for (const li of $multiTitle.children()) {
								const $li = $(li);
								const variant = $li.attr('data-noteta-multititle-variant');
								assert(variant, 'variant');
								const text = $li.text().trim();
								variantText[variant] = text;
								if (textVariant[text]) {
									textVariant[text].push(variant);
								} else {
									textVariant[text] = [variant];
								}
							}

							const multiTitle = [];
							const titleConverted = variantText[mw.config.get('wgUserVariant')];
							for (const variant in variantText) {
								const text = variantText[variant];
								if (text === null) {
									continue;
								}

								const variants = textVariant[text];
								if (!variants) {
									continue;
								}

								for (const variant of variants) {
									variantText[variant] = null;
								}

								const variantsName = variants.map((variant) => `-{R|{{MediaWiki:Variantname-${variant}}}}-`).join('、');
								multiTitle.push(variantsName + ':-{R|' + text + '}-');
							}
							wikitext += multiTitle.join(';');
							wikitext += '\n* 实际标题为:-{R|' + actualTitle + '}-;当前显示为:-{R|' + titleConverted + '}-\n';
						}
						titleDeferred.resolve();
					}).catch(titleDeferred.reject);
				}

				const deferred = $.Deferred();
				titleDeferred.then(() => {
					const $noteTAgroups = $dom.find('.noteTA-group > *[data-noteta-group]');
					if ($noteTAgroups.length > 1) {
						this.collapse = true;
					}
					for (const ele of $noteTAgroups) {
						const $ele = $(ele);
						switch ($ele.attr('data-noteta-group-source')) {
							case 'template':
								wikitext += '{{CGroup/' + $ele.attr('data-noteta-group') + '}}\n';
								break;
							case 'module':
								wikitext += '{{#invoke:CGroupViewer|dialog|' + $ele.attr('data-noteta-group') + '}}\n';
								break;
							case 'none':
								wikitext += '; 本文使用的公共转换组“' + $ele.attr('data-noteta-group') + '”尚未创建\n';
								wikitext += '* {{edit|Module:CGroup/' + $ele.attr('data-noteta-group') + '|创建公共转换组“' + $ele.attr('data-noteta-group') + '”}}\n';
								break;
							default:
								wikitext += '; 未知公共转换组“' + $ele.attr('data-noteta-group') + '”来源“' + $ele.attr('data-noteta-group-source') + '”\n';
						}
					}

					const $noteTAlocal = $dom.find('.noteTA-local');
					if ($noteTAlocal.length) {
						this.collapse = true;
						wikitext += '<span style="float: right;">{{edit|' + actualTitle + '|section=0}}</span>\n';
						wikitext += '; 本文使用[[Help:中文维基百科的繁简、地区词处理#控制自动转换的代碼|全文手工转换]]\n';
						const $noteTAlocals = $noteTAlocal.children('*[data-noteta-code]');
						for (const that of $noteTAlocals) {
							const $this = $(that);
							const localConv = $this.attr('data-noteta-code');
							let localDesc = $this.attr('data-noteta-desc');
							if (localDesc) {
								localDesc = '(' + localDesc + ')';
							} else {
								localDesc = '';
							}
							wikitext += '* -{D|' + localConv + '}-' + localDesc + '当前显示为:-{' + localConv + '}-\n';
						}
					}

					wikitext += '{{noteTA/footer}}\n';

					this.noteTAParseText = wikitext;

					deferred.resolve(wikitext);
				}).catch(deferred.reject);
				return deferred;
			}

			doExecute() {
				if (this.dataIsLoaded) {
					return $.Deferred().resolve();
				}

				this.$realContent.empty().append(
					$('<p>').text(HanAssist.conv({ hans: '正在加载...', hant: '正在載入...' }))
				);

				return this.getNoteTAParseText()
					.then(wikitext => retryApiRequest('parse', wikitext, {
						title: 'Template:CGroup/-',
						variant: mw.config.get('wgUserVariant')
					}))
					.then(parsedHtml => {
						this.$realContent.empty().html(parsedHtml);
						this.$realContent.find('.mw-collapsible').makeCollapsible();
						this.updateSize();

						this.dataIsLoaded = true;
					})
					.catch(error => {
						if (error instanceof ApiRetryFailError) {
							throw new OO.ui.Error(error.toJQuery(), { recoverable: true });
						} else {
							throw new OO.ui.Error(String(error), { recoverable: false });
						}
					});
			}

			doExecuteWrap() {
				if (!this.executePromise) {
					this.executePromise = this.doExecute();
					delete this.lastError;

					const deferred = $.Deferred();
					this.executePromise
						.then(deferred.resolve)
						.catch(error => {
							if (error instanceof OO.ui.Error) {
								this.lastError = error;
							} else {
								deferred.reject(error);
							}
						})
						.always(() => {
							delete this.executePromise;
						});
					return deferred;
				} else {
					const deferred = $.Deferred();
					this.executePromise
						.then(deferred.resolve)
						.catch(error => {
							if (!(error instanceof OO.ui.Error)) {
								deferred.reject(error);
							} else {
								deferred.resolve();
							}
						})
						.always(() => {
							delete this.executePromise;
						});
					return deferred;
				}
			}

			getSetupProcess(data) {
				return super.getSetupProcess(data).next(() => {
					this.doExecuteWrap();
					this.executeAction('main');
				});
			}

			getActionProcess(action) {
				return super.getActionProcess(action)
					.next(() => {
						if (action === 'main') {
							return this.doExecuteWrap();
						}
					})
					.next(() => {
						if (action === 'main' && this.lastError) {
							return this.lastError;
						}

						return super.getActionProcess(action).execute();
					});
			}
		}

		NoteTAViewer.static = Object.create( OO.ui.ProcessDialog.static );
		NoteTAViewer.static.name = 'NoteTALoader-' + hash;
		NoteTAViewer.static.title = HanAssist.conv({ hans: '字词转换', hant: '字詞轉換' });
		NoteTAViewer.static.actions = [
			{
				label: mw.msg('ooui-dialog-process-dismiss'),
				flags: 'primary'
			}
		];

		const viewer = new NoteTAViewer();
		windowManager.addWindows([viewer]);
		viewerMap.set(hash, viewer);

		return viewer;
	}

	function resetAllViewer() {
		for (const viewer of viewerMap.values()) {
			viewer.destroy();
		}
		viewerMap.clear();
		windowManager.clearWindows();
	}

	const skin = mw.config.get('skin');
	let portletId = null;
	const xNoteTAViewer = 'x-noteTA-viewer';
	let globalInit = () => {};
	let globalDeinit = () => {};
	if (skin === 'vector') {
		portletId = 'p-noteTA';
		let $vectorNoteTATab;
		globalInit = () => {
			if ($vectorNoteTATab) {
				return;
			}
			$vectorNoteTATab = $(mw.util.addPortlet(portletId));
			$('#p-variants').after(
				$vectorNoteTATab
					.removeClass(['mw-portlet-p-noteTA'])
					.addClass(['mw-portlet-noteTA', 'vector-menu-tabs', 'vector-menu-tabs-legacy'])
			);
		};
		globalDeinit = () => {
			if (!$vectorNoteTATab) {
				return;
			}
			$vectorNoteTATab.find('ul').empty();
			mw.util.hidePortlet(portletId);
		};
	} else if (skin === 'vector-2022') {
		portletId = 'p-associated-pages';
		globalDeinit = () => {
			$(document.getElementsByClassName(xNoteTAViewer)).remove();
		};
	}

	function addPortletItem(hash) {
		const $portlet = $(mw.util.addPortletLink(
			portletId,
			'#',
			'汉/漢',
			`ca-noteTA-${hash}`
		));
		$portlet
			.addClass(xNoteTAViewer)
			.find('a')
				.empty()
				.append(
					$('<div>')
						.append(
							$('<span>')
								.css({
									padding: '1px 3px',
									background: '#d3e3f4',
									color: '#000000',
									height: '85%'
								}).text('汉'),
							$('<span>')
								.css({
									padding: '1px 3px',
									background: '#e9e9e9',
									color: '#434343',
									height: '85%'
								}).text('漢')
						)
				);
		return $portlet;
	}

	function noteTAViewer() {
		resetAllViewer();

		globalDeinit();
		globalInit();

		for (const ele of $('.mw-indicator[id^=mw-indicator-noteTA-]')) {
			const hash = ele.id.replace(/^mw-indicator-noteTA-/, '');
			let $ele = $(ele);
			if (portletId) {
				$ele.hide();
				$ele = addPortletItem(hash);
			} else {
				$ele.css('cursor', 'pointer');
			}
			$ele.on('click', (e) => {
				e.preventDefault();
				getViewer(hash).open();
			});
		}
	}

	noteTAViewer.get = getViewer;
	noteTAViewer.reset = resetAllViewer;
	noteTAViewer.globalInit = globalInit;
	noteTAViewer.globalDeinit = globalDeinit;
	noteTAViewer.addPortletItem = addPortletItem;

	mw.libs.noteTAViewer = noteTAViewer;

	mw.hook('wikipage.content').add(function ($content) {
		noteTAViewer();
	});
});
// </nowiki>