User:SunAfterRain/js/noteTA.js
< User:SunAfterRain | js
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// <nowiki>
// Covert From https://zh.wikipedia.org/w/index.php?title=MediaWiki:Gadget-noteTA.js&oldid=63601886
$.when(
$.ready,
mw.loader.using(['mediawiki.api', 'ext.gadget.HanAssist', 'oojs-ui', 'jquery.makeCollapsible'])
).then((_$, require) => {
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 {
get name() {
return 'ApiRetryFailError';
}
/**
* @param {string[]} errors
*/
constructor(errors) {
super(`Api calls failed ${errors.length} time(s) in a row.`);
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);
}
/**
* @template T
* @param {Promise<T>} promise
* @return {JQuery.Promise<T>}
*/
function nativePromiseToJQueryDeferred(promise) {
const deferred = $.Deferred();
promise.then(deferred.resolve, deferred.reject);
return deferred;
}
/**
* @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>');
if ('MutationObserver' in window) {
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() {
if (this.mutationObserver) {
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().toArray()) {
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];
for (const variant of textVariant[text]) {
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.toArray()) {
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();
if (!this.mutationObserver) {
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 new OO.ui.Process()
.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;
let $insertPortlet = null;
const xNoteTAViewer = 'x-noteTA-viewer';
let insertedClass = 'x-noteTA-inserted';
let globalInit = () => {};
let globalDeinit = () => {};
let afterPortletAdd = () => {};
if (
(skin === 'vector-2022' || skin === 'minerva')
&& document.getElementById('p-associated-pages')
) {
portletId = 'p-associated-pages';
globalDeinit = () => {
$(document.getElementsByClassName(xNoteTAViewer)).remove();
};
} else if (skin.startsWith('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(
skin === 'vector'
? ['mw-portlet-noteTA', 'vector-menu-tabs', 'vector-menu-tabs-legacy']
: ['mw-portlet-noteTA', 'vector-menu-tabs']
)
);
};
globalDeinit = () => {
if (!$vectorNoteTATab) {
return;
}
$vectorNoteTATab.find('ul').empty();
mw.util.hidePortlet(portletId);
};
} else if (skin === 'minerva') {
portletId = 'p-views';
globalDeinit = () => {
$(document.getElementsByClassName(xNoteTAViewer)).remove();
};
const $insertBeforeElement = $('#language-selector').next();
afterPortletAdd = ($portlet) => {
$portlet
.insertBefore($insertBeforeElement)
.addClass(['page-actions-menu__list-item']);
};
}
if (portletId) {
$insertPortlet = $(document.getElementById(portletId));
}
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('漢')
)
);
afterPortletAdd($portlet);
$insertPortlet.addClass([insertedClass]);
return $portlet;
}
function noteTAViewer() {
resetAllViewer();
if (portletId) {
$insertPortlet.removeClass([insertedClass]);
}
globalDeinit();
globalInit();
if (skin === 'minerva') {
for (const ele of $('.noteTA[id^=noteTA-]').toArray()) {
const hash = ele.id.replace(/^noteTA-/, '');
const $ele = addPortletItem(hash);
$ele.on('click', (e) => {
e.preventDefault();
getViewer(hash).open();
});
}
} else {
for (const ele of $('.mw-indicator[id^=mw-indicator-noteTA-]').toArray()) {
const hash = ele.id.replace(/^mw-indicator-noteTA-/, '');
let $ele = $(ele);
if (portletId) {
$ele.hide();
$ele = addPortletItem(hash);
} else {
// https://zh.wikipedia.org/w/index.php?title=MediaWiki_talk:Gadget-noteTA.js&oldid=82017438#c-YFdyh000-20240324184400-SunAfterRain-20240324174800
$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>